1use std::net::SocketAddr;
7
8use bytes::{Bytes, BytesMut};
9use ringline::{ConnCtx, ParseResult};
10
11use crate::error::HttpError;
12use crate::response::Response;
13
14pub struct H1Conn {
16 conn: ConnCtx,
17 host: String,
18}
19
20impl H1Conn {
21 pub async fn connect_tls(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
23 let conn = ringline::connect_tls(addr, host)?.await?;
24 Ok(Self {
25 conn,
26 host: host.to_string(),
27 })
28 }
29
30 pub async fn connect_plain(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
32 let conn = ringline::connect(addr)?.await?;
33 Ok(Self {
34 conn,
35 host: host.to_string(),
36 })
37 }
38
39 pub fn conn(&self) -> ConnCtx {
41 self.conn
42 }
43
44 pub fn close(&self) {
46 self.conn.close();
47 }
48
49 pub fn host(&self) -> &str {
50 &self.host
51 }
52
53 pub async fn send_request(
55 &mut self,
56 method: &str,
57 path: &str,
58 extra_headers: &[(&str, &str)],
59 body: Option<&[u8]>,
60 ) -> Result<Response, HttpError> {
61 let hdr = self
62 .send_and_parse_headers(method, path, extra_headers, body)
63 .await?;
64
65 let mut body_buf = hdr.body_leftover;
66
67 if let Some(cl) = hdr.content_length {
69 while body_buf.len() < cl {
71 let target_len = cl;
72 let n = self
73 .conn
74 .with_data(|data| {
75 body_buf.extend_from_slice(data);
76 if body_buf.len() >= target_len {
77 ParseResult::Consumed(data.len())
78 } else {
79 ParseResult::Consumed(data.len())
81 }
82 })
83 .await;
84
85 if n == 0 {
86 break;
87 }
88 }
89 body_buf.truncate(cl);
90 } else if hdr.chunked {
91 let mut decoded = BytesMut::new();
93 let mut leftover = body_buf.to_vec();
94 body_buf.clear();
95
96 loop {
97 match decode_chunk(&leftover) {
98 ChunkResult::Complete {
99 data,
100 consumed,
101 is_last,
102 } => {
103 decoded.extend_from_slice(data);
104 leftover = leftover[consumed..].to_vec();
105 if is_last {
106 break;
107 }
108 }
109 ChunkResult::NeedMore => {
110 let n = self
112 .conn
113 .with_data(|data| {
114 leftover.extend_from_slice(data);
115 ParseResult::Consumed(data.len())
116 })
117 .await;
118
119 if n == 0 {
120 break;
121 }
122 }
123 }
124 }
125
126 body_buf = decoded;
127 }
128 #[cfg(any(feature = "gzip", feature = "zstd", feature = "brotli"))]
132 if let Some(ref encoding) = hdr.content_encoding {
133 let decompressed = crate::compress::decompress(encoding, &body_buf)?;
134 return Ok(Response::new(
135 hdr.status,
136 hdr.headers,
137 bytes::Bytes::from(decompressed),
138 ));
139 }
140
141 Ok(Response::new(hdr.status, hdr.headers, body_buf.freeze()))
142 }
143
144 pub async fn send_request_streaming(
149 &mut self,
150 method: &str,
151 path: &str,
152 extra_headers: &[(&str, &str)],
153 body: Option<&[u8]>,
154 ) -> Result<H1StreamingResponse<'_>, HttpError> {
155 let hdr = self
156 .send_and_parse_headers(method, path, extra_headers, body)
157 .await?;
158
159 let state = if let Some(cl) = hdr.content_length {
160 H1StreamState::ContentLength {
161 remaining: cl.saturating_sub(hdr.body_leftover.len()),
162 leftover: hdr.body_leftover,
163 }
164 } else if hdr.chunked {
165 H1StreamState::Chunked {
166 leftover: hdr.body_leftover.to_vec(),
167 }
168 } else {
169 H1StreamState::Done
170 };
171
172 Ok(H1StreamingResponse {
173 conn: &mut self.conn,
174 status: hdr.status,
175 headers: hdr.headers,
176 state,
177 })
178 }
179
180 async fn send_and_parse_headers(
182 &mut self,
183 method: &str,
184 path: &str,
185 extra_headers: &[(&str, &str)],
186 body: Option<&[u8]>,
187 ) -> Result<H1HeaderResult, HttpError> {
188 let mut req = Vec::with_capacity(256);
190 req.extend_from_slice(method.as_bytes());
191 req.push(b' ');
192 req.extend_from_slice(path.as_bytes());
193 req.extend_from_slice(b" HTTP/1.1\r\n");
194 req.extend_from_slice(b"host: ");
195 req.extend_from_slice(self.host.as_bytes());
196 req.extend_from_slice(b"\r\n");
197
198 let has_accept_encoding = extra_headers
199 .iter()
200 .any(|(name, _)| name.eq_ignore_ascii_case("accept-encoding"));
201
202 for (name, value) in extra_headers {
203 req.extend_from_slice(name.as_bytes());
204 req.extend_from_slice(b": ");
205 req.extend_from_slice(value.as_bytes());
206 req.extend_from_slice(b"\r\n");
207 }
208
209 if !has_accept_encoding && let Some(ae) = crate::compress::accept_encoding_value() {
212 req.extend_from_slice(b"accept-encoding: ");
213 req.extend_from_slice(ae.as_bytes());
214 req.extend_from_slice(b"\r\n");
215 }
216
217 if let Some(b) = body
218 && !b.is_empty()
219 {
220 req.extend_from_slice(b"content-length: ");
221 req.extend_from_slice(b.len().to_string().as_bytes());
222 req.extend_from_slice(b"\r\n");
223 }
224
225 req.extend_from_slice(b"\r\n");
226
227 if let Some(b) = body
228 && !b.is_empty()
229 {
230 req.extend_from_slice(b);
231 }
232
233 self.conn.send_nowait(&req)?;
234
235 let mut status: u16 = 0;
237 let mut headers: Vec<(String, String)> = Vec::new();
238 let mut content_length: Option<usize> = None;
239 let mut chunked = false;
240 let mut content_encoding: Option<String> = None;
241 let mut headers_done = false;
242 let mut body_leftover = BytesMut::new();
243 let mut parse_error = false;
244
245 while !headers_done {
246 let n = self
247 .conn
248 .with_data(|data| {
249 if let Some(end) = find_header_end(data) {
250 let header_bytes = &data[..end];
251 if let Some(parsed) = parse_response_headers(header_bytes) {
252 status = parsed.status;
253 headers = parsed.headers;
254 content_length = parsed.content_length;
255 chunked = parsed.chunked;
256 content_encoding = parsed.content_encoding;
257 headers_done = true;
258 let header_consumed = end + 4;
259
260 let remaining = &data[header_consumed..];
261 if !remaining.is_empty() {
262 body_leftover.extend_from_slice(remaining);
263 }
264 } else {
265 parse_error = true;
266 }
267 ParseResult::Consumed(data.len())
268 } else {
269 ParseResult::Consumed(0)
270 }
271 })
272 .await;
273
274 if n == 0 {
275 return Err(HttpError::ConnectionClosed);
276 }
277 if parse_error {
278 return Err(HttpError::Parse);
279 }
280 }
281
282 Ok(H1HeaderResult {
283 status,
284 headers,
285 content_length,
286 chunked,
287 content_encoding,
288 body_leftover,
289 })
290 }
291}
292
293struct H1HeaderResult {
295 status: u16,
296 headers: Vec<(String, String)>,
297 content_length: Option<usize>,
298 chunked: bool,
299 #[cfg_attr(
300 not(any(feature = "gzip", feature = "zstd", feature = "brotli")),
301 allow(dead_code)
302 )]
303 content_encoding: Option<String>,
304 body_leftover: BytesMut,
305}
306
307enum H1StreamState {
309 ContentLength {
310 remaining: usize,
311 leftover: BytesMut,
312 },
313 Chunked {
314 leftover: Vec<u8>,
315 },
316 Done,
317}
318
319pub struct H1StreamingResponse<'a> {
323 conn: &'a mut ConnCtx,
324 status: u16,
325 headers: Vec<(String, String)>,
326 state: H1StreamState,
327}
328
329impl<'a> H1StreamingResponse<'a> {
330 pub fn status(&self) -> u16 {
332 self.status
333 }
334
335 pub fn headers(&self) -> &[(String, String)] {
337 &self.headers
338 }
339
340 pub fn header(&self, name: &str) -> Option<&str> {
342 let lower = name.to_ascii_lowercase();
343 self.headers
344 .iter()
345 .find(|(k, _)| k.to_ascii_lowercase() == lower)
346 .map(|(_, v)| v.as_str())
347 }
348
349 pub async fn next_chunk(&mut self) -> Result<Option<Bytes>, HttpError> {
351 loop {
352 match &mut self.state {
353 H1StreamState::ContentLength {
354 remaining,
355 leftover,
356 } => {
357 if !leftover.is_empty() {
359 let chunk = leftover.split().freeze();
360 *remaining = remaining.saturating_sub(chunk.len());
361 return Ok(Some(chunk));
362 }
363 if *remaining == 0 {
364 self.state = H1StreamState::Done;
365 return Ok(None);
366 }
367 let rem = *remaining;
369 let mut got = BytesMut::new();
370 let n = self
371 .conn
372 .with_data(|data| {
373 let take = data.len().min(rem);
374 got.extend_from_slice(&data[..take]);
375 ParseResult::Consumed(take)
376 })
377 .await;
378 if n == 0 {
379 self.state = H1StreamState::Done;
380 return Err(HttpError::ConnectionClosed);
381 }
382 *remaining -= got.len();
383 return Ok(Some(got.freeze()));
384 }
385 H1StreamState::Chunked { leftover } => {
386 match decode_chunk(leftover) {
387 ChunkResult::Complete {
388 data,
389 consumed,
390 is_last,
391 } => {
392 let chunk = Bytes::copy_from_slice(data);
393 *leftover = leftover[consumed..].to_vec();
394 if is_last {
395 self.state = H1StreamState::Done;
396 if chunk.is_empty() {
397 return Ok(None);
398 }
399 return Ok(Some(chunk));
400 }
401 return Ok(Some(chunk));
402 }
403 ChunkResult::NeedMore => {
404 let n = self
406 .conn
407 .with_data(|data| {
408 leftover.extend_from_slice(data);
409 ParseResult::Consumed(data.len())
410 })
411 .await;
412 if n == 0 {
413 self.state = H1StreamState::Done;
414 return Err(HttpError::ConnectionClosed);
415 }
416 }
418 }
419 }
420 H1StreamState::Done => return Ok(None),
421 }
422 }
423 }
424}
425
426fn find_header_end(data: &[u8]) -> Option<usize> {
428 (0..data.len().saturating_sub(3)).find(|&i| {
429 data[i] == b'\r' && data[i + 1] == b'\n' && data[i + 2] == b'\r' && data[i + 3] == b'\n'
430 })
431}
432
433struct ParsedHeaders {
434 status: u16,
435 headers: Vec<(String, String)>,
436 content_length: Option<usize>,
437 chunked: bool,
438 content_encoding: Option<String>,
439}
440
441fn parse_response_headers(data: &[u8]) -> Option<ParsedHeaders> {
443 let text = std::str::from_utf8(data).ok()?;
444 let mut lines = text.split("\r\n");
445
446 let status_line = lines.next()?;
448 let mut parts = status_line.splitn(3, ' ');
449 let _version = parts.next()?;
450 let status_str = parts.next()?;
451 let status: u16 = status_str.parse().ok()?;
452
453 let mut headers = Vec::new();
454 let mut content_length = None;
455 let mut chunked = false;
456 let mut content_encoding = None;
457
458 for line in lines {
459 if line.is_empty() {
460 break;
461 }
462 if let Some((name, value)) = line.split_once(':') {
463 let name = name.trim().to_string();
464 let value = value.trim().to_string();
465
466 if name.eq_ignore_ascii_case("content-length") {
467 content_length = value.parse().ok();
468 }
469 if name.eq_ignore_ascii_case("transfer-encoding")
470 && value.eq_ignore_ascii_case("chunked")
471 {
472 chunked = true;
473 }
474 if name.eq_ignore_ascii_case("content-encoding") {
475 content_encoding = Some(value.clone());
476 }
477
478 headers.push((name, value));
479 }
480 }
481
482 Some(ParsedHeaders {
483 status,
484 headers,
485 content_length,
486 chunked,
487 content_encoding,
488 })
489}
490
491enum ChunkResult<'a> {
492 Complete {
493 data: &'a [u8],
494 consumed: usize,
495 is_last: bool,
496 },
497 NeedMore,
498}
499
500fn decode_chunk(data: &[u8]) -> ChunkResult<'_> {
502 let crlf = match find_crlf(data) {
504 Some(pos) => pos,
505 None => return ChunkResult::NeedMore,
506 };
507
508 let size_str = match std::str::from_utf8(&data[..crlf]) {
509 Ok(s) => s.trim(),
510 Err(_) => return ChunkResult::NeedMore,
511 };
512
513 let size_hex = size_str.split(';').next().unwrap_or("").trim();
515
516 let size = match usize::from_str_radix(size_hex, 16) {
517 Ok(s) => s,
518 Err(_) => return ChunkResult::NeedMore,
519 };
520
521 if size == 0 {
522 let after_zero = crlf + 2;
525 let mut pos = after_zero;
526 loop {
527 match find_crlf(&data[pos..]) {
528 Some(0) => {
529 return ChunkResult::Complete {
531 data: &[],
532 consumed: pos + 2,
533 is_last: true,
534 };
535 }
536 Some(next_crlf) => {
537 pos += next_crlf + 2;
539 }
540 None => return ChunkResult::NeedMore,
541 }
542 }
543 }
544
545 let chunk_start = crlf + 2;
546 let chunk_end = chunk_start + size;
547 let total = chunk_end + 2; if data.len() < total {
550 return ChunkResult::NeedMore;
551 }
552
553 ChunkResult::Complete {
554 data: &data[chunk_start..chunk_end],
555 consumed: total,
556 is_last: false,
557 }
558}
559
560fn find_crlf(data: &[u8]) -> Option<usize> {
561 (0..data.len().saturating_sub(1)).find(|&i| data[i] == b'\r' && data[i + 1] == b'\n')
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 #[test]
569 fn parse_simple_response() {
570 let data = b"HTTP/1.1 200 OK\r\ncontent-length: 5\r\n";
571 let parsed = parse_response_headers(data).unwrap();
572 assert_eq!(parsed.status, 200);
573 assert_eq!(parsed.content_length, Some(5));
574 assert!(!parsed.chunked);
575 assert_eq!(parsed.headers.len(), 1);
576 }
577
578 #[test]
579 fn parse_chunked_response() {
580 let data = b"HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n";
581 let parsed = parse_response_headers(data).unwrap();
582 assert_eq!(parsed.status, 200);
583 assert!(parsed.chunked);
584 assert_eq!(parsed.content_length, None);
585 }
586
587 #[test]
588 fn find_header_end_found() {
589 let data = b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\nbody";
590 assert_eq!(find_header_end(data), Some(34));
591 }
592
593 #[test]
594 fn find_header_end_not_found() {
595 let data = b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n";
596 assert_eq!(find_header_end(data), None);
597 }
598
599 #[test]
600 fn decode_chunk_simple() {
601 let data = b"5\r\nhello\r\n";
602 match decode_chunk(data) {
603 ChunkResult::Complete {
604 data,
605 consumed,
606 is_last,
607 } => {
608 assert_eq!(data, b"hello");
609 assert_eq!(consumed, 10);
610 assert!(!is_last);
611 }
612 ChunkResult::NeedMore => panic!("expected Complete"),
613 }
614 }
615
616 #[test]
617 fn decode_chunk_last_with_empty_trailers() {
618 let data = b"0\r\n\r\n";
620 match decode_chunk(data) {
621 ChunkResult::Complete {
622 is_last, consumed, ..
623 } => {
624 assert!(is_last);
625 assert_eq!(consumed, 5, "should consume 0\\r\\n\\r\\n");
626 }
627 ChunkResult::NeedMore => panic!("expected Complete"),
628 }
629 }
630
631 #[test]
632 fn decode_chunk_last_needs_trailing_crlf() {
633 let data = b"0\r\n";
635 match decode_chunk(data) {
636 ChunkResult::NeedMore => {}
637 ChunkResult::Complete { .. } => panic!("expected NeedMore"),
638 }
639 }
640
641 #[test]
642 fn decode_chunk_last_with_trailers() {
643 let data = b"0\r\nTrailer: value\r\n\r\n";
645 match decode_chunk(data) {
646 ChunkResult::Complete {
647 is_last, consumed, ..
648 } => {
649 assert!(is_last);
650 assert_eq!(
651 consumed,
652 data.len(),
653 "should consume entire trailer section"
654 );
655 }
656 ChunkResult::NeedMore => panic!("expected Complete"),
657 }
658 }
659
660 #[test]
661 fn decode_chunk_last_does_not_consume_next_response() {
662 let data = b"0\r\n\r\nHTTP/1.1 200 OK\r\n";
664 match decode_chunk(data) {
665 ChunkResult::Complete {
666 is_last, consumed, ..
667 } => {
668 assert!(is_last);
669 assert_eq!(consumed, 5, "should only consume 0\\r\\n\\r\\n");
670 assert_eq!(&data[consumed..], b"HTTP/1.1 200 OK\r\n");
671 }
672 ChunkResult::NeedMore => panic!("expected Complete"),
673 }
674 }
675
676 #[test]
677 fn decode_chunk_need_more() {
678 let data = b"5\r\nhel";
679 match decode_chunk(data) {
680 ChunkResult::NeedMore => {}
681 ChunkResult::Complete { .. } => panic!("expected NeedMore"),
682 }
683 }
684
685 #[test]
686 fn parse_malformed_status_line_returns_none() {
687 assert!(parse_response_headers(b"HTTP/1.1 \r\n").is_none());
689 assert!(parse_response_headers(b"NOT-HTTP\r\n").is_none());
691 assert!(parse_response_headers(b"").is_none());
693 assert!(parse_response_headers(b"HTTP/1.1\r\n").is_none());
695 }
696}