1use std::collections::{HashMap, VecDeque};
8use std::net::SocketAddr;
9
10use bytes::{Bytes, BytesMut};
11use ringline::{ConnCtx, ParseResult};
12use ringline_h2::hpack::HeaderField;
13use ringline_h2::settings::Settings;
14use ringline_h2::{H2Connection, H2Event};
15
16use crate::error::HttpError;
17use crate::response::Response;
18
19struct PendingStream {
21 status: Option<u16>,
22 headers: Vec<(String, String)>,
23 body: BytesMut,
24 done: bool,
25 error: Option<HttpError>,
28 streaming: bool,
30 chunks: VecDeque<Bytes>,
32 content_encoding: Option<String>,
34 header_bytes: usize,
37 body_bytes: usize,
40}
41
42impl PendingStream {
43 fn new() -> Self {
44 Self {
45 status: None,
46 headers: Vec::new(),
47 body: BytesMut::new(),
48 done: false,
49 error: None,
50 streaming: false,
51 chunks: VecDeque::new(),
52 content_encoding: None,
53 header_bytes: 0,
54 body_bytes: 0,
55 }
56 }
57
58 fn fail(&mut self, err: HttpError) {
61 if self.error.is_none() {
62 self.error = Some(err);
63 }
64 self.done = true;
65 }
66
67 #[cfg_attr(
68 not(any(feature = "gzip", feature = "zstd", feature = "brotli")),
69 allow(unused_variables)
70 )]
71 fn into_response(self, max_decompressed_size: usize) -> Result<Response, HttpError> {
72 if let Some(err) = self.error {
73 return Err(err);
74 }
75 let status = self
79 .status
80 .ok_or_else(|| HttpError::InvalidMessage("response missing :status".into()))?;
81
82 if let Some(ref encoding) = self.content_encoding
84 && !is_identity_encoding(encoding)
85 {
86 #[cfg(any(feature = "gzip", feature = "zstd", feature = "brotli"))]
87 {
88 let decompressed =
89 crate::compress::decompress(encoding, &self.body, max_decompressed_size)?;
90 return Ok(Response::new(
91 status,
92 self.headers,
93 Bytes::from(decompressed),
94 ));
95 }
96 #[cfg(not(any(feature = "gzip", feature = "zstd", feature = "brotli")))]
101 {
102 return Err(HttpError::InvalidMessage(format!(
103 "unsupported content-encoding `{encoding}` (no decompressor compiled in)"
104 )));
105 }
106 }
107
108 Ok(Response::new(status, self.headers, self.body.freeze()))
109 }
110}
111
112fn is_identity_encoding(encoding: &str) -> bool {
115 let e = encoding.trim();
116 e.is_empty() || e.eq_ignore_ascii_case("identity")
117}
118
119fn parse_status(value: &[u8]) -> Option<u16> {
126 if value.len() != 3 {
127 return None;
128 }
129 if !value.iter().all(|b| b.is_ascii_digit()) {
130 return None;
131 }
132 let s = std::str::from_utf8(value).ok()?;
133 let n: u16 = s.parse().ok()?;
134 if (100..=999).contains(&n) {
135 Some(n)
136 } else {
137 None
138 }
139}
140
141fn handle_response_headers(
144 ps: &mut PendingStream,
145 headers: &[HeaderField],
146 end_stream: bool,
147 max_header_section: usize,
148) {
149 for h in headers {
150 if h.name == b":status" {
151 match parse_status(&h.value) {
152 Some(n) => ps.status = Some(n),
153 None => {
154 ps.fail(HttpError::InvalidMessage(format!(
155 "invalid :status `{}`",
156 String::from_utf8_lossy(&h.value)
157 )));
158 return;
159 }
160 }
161 continue;
162 }
163
164 let add = h.name.len().saturating_add(h.value.len());
168 ps.header_bytes = ps.header_bytes.saturating_add(add);
169 if ps.header_bytes > max_header_section {
170 ps.fail(HttpError::MaxSizeExceeded(format!(
171 "response header section exceeds {max_header_section} bytes"
172 )));
173 return;
174 }
175
176 let name = String::from_utf8_lossy(&h.name).into_owned();
177 let value = String::from_utf8_lossy(&h.value).into_owned();
178 if name.eq_ignore_ascii_case("content-encoding") {
179 ps.content_encoding = Some(value.clone());
180 }
181 ps.headers.push((name, value));
182 }
183 if end_stream {
184 if ps.status.is_none() && ps.error.is_none() {
187 ps.fail(HttpError::InvalidMessage(
188 "response HEADERS missing :status".into(),
189 ));
190 } else {
191 ps.done = true;
192 }
193 }
194}
195
196fn handle_response_data(
199 ps: &mut PendingStream,
200 payload: Vec<u8>,
201 end_stream: bool,
202 max_body_size: usize,
203) {
204 let new_total = ps.body_bytes.saturating_add(payload.len());
205 if new_total > max_body_size {
206 ps.fail(HttpError::MaxSizeExceeded(format!(
207 "response body exceeds {max_body_size} bytes"
208 )));
209 return;
210 }
211 ps.body_bytes = new_total;
212 if ps.streaming {
213 ps.chunks.push_back(Bytes::from(payload));
214 } else {
215 ps.body.extend_from_slice(&payload);
216 }
217 if end_stream {
218 ps.done = true;
219 }
220}
221
222struct BlockedSend {
224 stream_id: u32,
225 data: Vec<u8>,
226 end_stream: bool,
227}
228
229pub struct H2AsyncConn {
234 conn: ConnCtx,
235 h2: H2Connection,
236 pending_streams: HashMap<u32, PendingStream>,
237 blocked_sends: VecDeque<BlockedSend>,
238 completed: VecDeque<(u32, Result<Response, HttpError>)>,
242 settings_acked: bool,
243 max_decompressed_size: usize,
245 max_header_section: usize,
248 max_body_size: usize,
251 goaway_received: bool,
254}
255
256impl H2AsyncConn {
257 pub fn set_max_decompressed_size(&mut self, n: usize) {
260 self.max_decompressed_size = n;
261 }
262
263 pub fn set_max_header_section(&mut self, n: usize) {
266 self.max_header_section = n;
267 }
268
269 pub fn set_max_body_size(&mut self, n: usize) {
272 self.max_body_size = n;
273 }
274
275 pub fn peer_will_close(&self) -> bool {
279 self.goaway_received
280 }
281}
282
283impl H2AsyncConn {
284 pub async fn connect(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
289 let conn = ringline::connect_tls(addr, host)?.await?;
290 Self::from_conn(conn).await
291 }
292
293 pub async fn connect_with_timeout(
295 addr: SocketAddr,
296 host: &str,
297 timeout_ms: u64,
298 ) -> Result<Self, HttpError> {
299 let conn = ringline::connect_tls_with_timeout(addr, host, timeout_ms)?.await?;
300 Self::from_conn(conn).await
301 }
302
303 pub async fn from_conn(conn: ConnCtx) -> Result<Self, HttpError> {
307 let h2 = H2Connection::new(Settings::client_default());
308
309 let mut this = Self {
310 conn,
311 h2,
312 pending_streams: HashMap::new(),
313 blocked_sends: VecDeque::new(),
314 completed: VecDeque::new(),
315 settings_acked: false,
316 max_decompressed_size: crate::compress::DEFAULT_MAX_DECOMPRESSED_SIZE,
317 max_header_section: crate::h1_conn::DEFAULT_MAX_HEADER_SECTION,
318 max_body_size: crate::h1_conn::DEFAULT_MAX_BODY_SIZE,
319 goaway_received: false,
320 };
321
322 this.flush_pending_send()?;
324
325 while !this.settings_acked {
327 this.pump_once().await?;
328 }
329
330 Ok(this)
331 }
332
333 pub fn close(&self) {
335 self.conn.close();
336 }
337
338 pub fn conn(&self) -> ConnCtx {
339 self.conn
340 }
341
342 pub fn pending_count(&self) -> usize {
344 self.pending_streams.len()
345 }
346
347 pub async fn send_request(
351 &mut self,
352 method: &str,
353 path: &str,
354 host: &str,
355 extra_headers: &[(&str, &str)],
356 body: Option<&[u8]>,
357 ) -> Result<Response, HttpError> {
358 let stream_id = self.fire_request(method, path, host, extra_headers, body)?;
359 self.recv_stream(stream_id).await
360 }
361
362 pub fn fire_request(
369 &mut self,
370 method: &str,
371 path: &str,
372 host: &str,
373 extra_headers: &[(&str, &str)],
374 body: Option<&[u8]>,
375 ) -> Result<u32, HttpError> {
376 let has_body = body.is_some_and(|b| !b.is_empty());
377
378 let mut headers = vec![
379 HeaderField::new(b":method", method.as_bytes()),
380 HeaderField::new(b":path", path.as_bytes()),
381 HeaderField::new(b":scheme", b"https"),
382 HeaderField::new(b":authority", host.as_bytes()),
383 ];
384
385 let has_accept_encoding = extra_headers
386 .iter()
387 .any(|(name, _)| name.eq_ignore_ascii_case("accept-encoding"));
388
389 for (name, value) in extra_headers {
390 headers.push(HeaderField::new(name.as_bytes(), value.as_bytes()));
391 }
392
393 if !has_accept_encoding && let Some(ae) = crate::compress::accept_encoding_value() {
396 headers.push(HeaderField::new(b"accept-encoding", ae.as_bytes()));
397 }
398
399 let end_stream = !has_body;
400 let stream_id = self.h2.send_request(&headers, end_stream)?;
401
402 if let Some(data) = body
403 && !data.is_empty()
404 && let Err(e) = self.h2.send_data(stream_id, data, true)
405 {
406 if matches!(e, ringline_h2::H2Error::FlowControlError) {
407 self.blocked_sends.push_back(BlockedSend {
408 stream_id,
409 data: data.to_vec(),
410 end_stream: true,
411 });
412 } else {
413 return Err(HttpError::H2(e));
414 }
415 }
416
417 self.pending_streams.insert(stream_id, PendingStream::new());
418
419 self.flush_pending_send()?;
421
422 Ok(stream_id)
423 }
424
425 pub async fn recv(&mut self) -> Result<(u32, Response), HttpError> {
430 if let Some((id, result)) = self.completed.pop_front() {
432 return result.map(|r| (id, r));
433 }
434
435 loop {
436 self.pump_once().await?;
437
438 if let Some((id, result)) = self.completed.pop_front() {
439 return result.map(|r| (id, r));
440 }
441 }
442 }
443
444 pub async fn recv_stream(&mut self, stream_id: u32) -> Result<Response, HttpError> {
446 if let Some(idx) = self.completed.iter().position(|(sid, _)| *sid == stream_id) {
448 let (_, result) = self.completed.remove(idx).unwrap();
449 return result;
450 }
451
452 loop {
453 self.pump_once().await?;
454
455 if let Some(idx) = self.completed.iter().position(|(sid, _)| *sid == stream_id) {
457 let (_, result) = self.completed.remove(idx).unwrap();
458 return result;
459 }
460 }
461 }
462
463 async fn pump_once(&mut self) -> Result<(), HttpError> {
472 self.flush_pending_send()?;
474
475 let h2 = &mut self.h2;
477 let pending = &mut self.pending_streams;
478 let blocked = &mut self.blocked_sends;
479 let settings_acked = &mut self.settings_acked;
480 let goaway_received = &mut self.goaway_received;
481 let max_header_section = self.max_header_section;
482 let max_body_size = self.max_body_size;
483 let mut connection_error: Option<HttpError> = None;
487
488 let n = self
489 .conn
490 .with_data(|data| {
491 if let Err(e) = h2.recv(data) {
493 connection_error = Some(HttpError::H2(e));
494 return ParseResult::Consumed(data.len());
495 }
496
497 while let Some(event) = h2.poll_event() {
499 match event {
500 H2Event::SettingsAcknowledged => {
501 *settings_acked = true;
502 }
503 H2Event::Response {
504 stream_id,
505 headers,
506 end_stream,
507 } => {
508 if let Some(ps) = pending.get_mut(&stream_id) {
509 handle_response_headers(
510 ps,
511 &headers,
512 end_stream,
513 max_header_section,
514 );
515 }
516 }
517 H2Event::Data {
518 stream_id,
519 data: payload,
520 end_stream,
521 } => {
522 if let Some(ps) = pending.get_mut(&stream_id) {
523 handle_response_data(ps, payload, end_stream, max_body_size);
524 }
525 }
526 H2Event::Trailers { stream_id, .. } => {
527 if let Some(ps) = pending.get_mut(&stream_id) {
528 ps.done = true;
529 }
530 }
531 H2Event::StreamReset {
532 stream_id,
533 error_code,
534 } => {
535 if let Some(ps) = pending.get_mut(&stream_id) {
536 ps.fail(HttpError::H2(ringline_h2::H2Error::StreamError(
537 stream_id, error_code,
538 )));
539 }
540 }
541 H2Event::GoAway { .. } => {
542 *goaway_received = true;
543 for ps in pending.values_mut() {
550 ps.done = true;
551 }
552 }
553 H2Event::Error(e) => {
554 connection_error = Some(HttpError::H2(e));
559 }
560 H2Event::PingAcknowledged { .. } => {}
561 }
562 }
563
564 let mut retry = VecDeque::new();
566 std::mem::swap(blocked, &mut retry);
567 for bs in retry {
568 match h2.send_data(bs.stream_id, &bs.data, bs.end_stream) {
569 Ok(()) => {}
570 Err(ringline_h2::H2Error::FlowControlError) => {
571 blocked.push_back(bs);
573 }
574 Err(e) => {
575 if let Some(ps) = pending.get_mut(&bs.stream_id) {
579 ps.fail(HttpError::H2(e));
580 }
581 }
582 }
583 }
584
585 ParseResult::Consumed(data.len())
587 })
588 .await;
589
590 if let Some(e) = connection_error {
591 return Err(e);
592 }
593 if n == 0 {
594 return Err(HttpError::ConnectionClosed);
595 }
596
597 let done_ids: Vec<u32> = self
599 .pending_streams
600 .iter()
601 .filter(|(_, ps)| ps.done && !ps.streaming)
602 .map(|(id, _)| *id)
603 .collect();
604 for id in done_ids {
605 if let Some(ps) = self.pending_streams.remove(&id) {
606 let result = ps.into_response(self.max_decompressed_size);
607 self.completed.push_back((id, result));
608 }
609 }
610
611 self.flush_pending_send()?;
613
614 Ok(())
615 }
616
617 pub async fn send_request_streaming(
624 &mut self,
625 method: &str,
626 path: &str,
627 host: &str,
628 extra_headers: &[(&str, &str)],
629 body: Option<&[u8]>,
630 ) -> Result<H2StreamingResponse<'_>, HttpError> {
631 let stream_id = self.fire_request(method, path, host, extra_headers, body)?;
632
633 if let Some(ps) = self.pending_streams.get_mut(&stream_id) {
635 ps.streaming = true;
636 }
637
638 loop {
640 if let Some(ps) = self.pending_streams.get_mut(&stream_id) {
641 if let Some(err) = ps.error.take() {
642 self.pending_streams.remove(&stream_id);
643 return Err(err);
644 }
645 if ps.status.is_some() {
646 break;
647 }
648 } else {
649 return Err(HttpError::Protocol("stream vanished".into()));
650 }
651
652 self.pump_once().await?;
653 }
654
655 Ok(H2StreamingResponse {
656 conn: self,
657 stream_id,
658 })
659 }
660
661 fn flush_pending_send(&mut self) -> Result<(), HttpError> {
663 let pending = self.h2.take_pending_send();
664 if !pending.is_empty() {
665 self.conn.send_nowait(&pending)?;
666 }
667 Ok(())
668 }
669}
670
671pub struct H2StreamingResponse<'a> {
677 conn: &'a mut H2AsyncConn,
678 stream_id: u32,
679}
680
681impl<'a> H2StreamingResponse<'a> {
682 pub fn status(&self) -> u16 {
684 self.conn
685 .pending_streams
686 .get(&self.stream_id)
687 .and_then(|ps| ps.status)
688 .unwrap_or(0)
689 }
690
691 pub fn headers(&self) -> &[(String, String)] {
693 self.conn
694 .pending_streams
695 .get(&self.stream_id)
696 .map(|ps| ps.headers.as_slice())
697 .unwrap_or(&[])
698 }
699
700 pub fn header(&self, name: &str) -> Option<&str> {
702 let lower = name.to_ascii_lowercase();
703 self.headers()
704 .iter()
705 .find(|(k, _)| k.to_ascii_lowercase() == lower)
706 .map(|(_, v)| v.as_str())
707 }
708
709 pub async fn next_chunk(&mut self) -> Result<Option<Bytes>, HttpError> {
711 loop {
712 if let Some(ps) = self.conn.pending_streams.get_mut(&self.stream_id) {
713 if let Some(err) = ps.error.take() {
717 self.conn.pending_streams.remove(&self.stream_id);
718 return Err(err);
719 }
720 if let Some(chunk) = ps.chunks.pop_front() {
722 return Ok(Some(chunk));
723 }
724 if ps.done {
726 self.conn.pending_streams.remove(&self.stream_id);
727 return Ok(None);
728 }
729 } else {
730 return Ok(None);
731 }
732
733 self.conn.pump_once().await?;
735 }
736 }
737}
738
739impl Drop for H2StreamingResponse<'_> {
740 fn drop(&mut self) {
741 self.conn.pending_streams.remove(&self.stream_id);
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
752
753 fn header(name: &[u8], value: &[u8]) -> HeaderField {
754 HeaderField::new(name, value)
755 }
756
757 #[test]
760 fn parse_status_accepts_three_digit_codes() {
761 assert_eq!(parse_status(b"200"), Some(200));
762 assert_eq!(parse_status(b"404"), Some(404));
763 assert_eq!(parse_status(b"100"), Some(100));
764 assert_eq!(parse_status(b"599"), Some(599));
765 assert_eq!(parse_status(b"999"), Some(999));
766 }
767
768 #[test]
769 fn parse_status_rejects_wrong_length() {
770 assert_eq!(parse_status(b""), None);
771 assert_eq!(parse_status(b"2"), None);
772 assert_eq!(parse_status(b"20"), None);
773 assert_eq!(parse_status(b"2000"), None);
774 }
775
776 #[test]
777 fn parse_status_rejects_non_digits() {
778 assert_eq!(parse_status(b"abc"), None);
779 assert_eq!(parse_status(b"2x0"), None);
780 assert_eq!(parse_status(b"-10"), None);
781 }
782
783 #[test]
784 fn parse_status_rejects_out_of_range() {
785 assert_eq!(parse_status(b"099"), None);
787 }
789
790 #[test]
793 fn invalid_status_fails_stream() {
794 let mut ps = PendingStream::new();
795 let headers = vec![header(b":status", b"oops")];
796 handle_response_headers(&mut ps, &headers, true, 64 * 1024);
797 assert!(ps.error.is_some());
798 match ps.error.unwrap() {
799 HttpError::InvalidMessage(_) => {}
800 other => panic!("expected InvalidMessage, got {other:?}"),
801 }
802 }
803
804 #[test]
805 fn missing_status_with_end_stream_fails() {
806 let mut ps = PendingStream::new();
807 let headers = vec![header(b"content-type", b"text/plain")];
808 handle_response_headers(&mut ps, &headers, true, 64 * 1024);
809 assert!(matches!(ps.error, Some(HttpError::InvalidMessage(_))));
810 }
811
812 #[test]
813 fn oversize_header_section_fails_stream() {
814 let mut ps = PendingStream::new();
815 let big_value = vec![b'x'; 200];
817 let headers = vec![header(b":status", b"200"), header(b"x", &big_value)];
818 handle_response_headers(&mut ps, &headers, false, 100);
819 assert!(matches!(ps.error, Some(HttpError::MaxSizeExceeded(_))));
820 }
821
822 #[test]
823 fn valid_status_accepts_and_collects_headers() {
824 let mut ps = PendingStream::new();
825 let headers = vec![header(b":status", b"204"), header(b"x-custom", b"value")];
826 handle_response_headers(&mut ps, &headers, true, 64 * 1024);
827 assert_eq!(ps.status, Some(204));
828 assert!(ps.error.is_none());
829 assert!(ps.done);
830 assert_eq!(
831 ps.headers,
832 vec![("x-custom".to_string(), "value".to_string())]
833 );
834 }
835
836 #[test]
839 fn oversize_body_fails_stream() {
840 let mut ps = PendingStream::new();
841 let payload = vec![0u8; 200];
842 handle_response_data(&mut ps, payload, false, 100);
843 assert!(matches!(ps.error, Some(HttpError::MaxSizeExceeded(_))));
844 }
845
846 #[test]
847 fn body_within_cap_accumulates() {
848 let mut ps = PendingStream::new();
849 handle_response_data(&mut ps, vec![1, 2, 3], false, 100);
850 handle_response_data(&mut ps, vec![4, 5], true, 100);
851 assert!(ps.error.is_none());
852 assert!(ps.done);
853 assert_eq!(&ps.body[..], &[1, 2, 3, 4, 5]);
854 }
855
856 #[test]
859 fn into_response_surfaces_stream_error() {
860 let mut ps = PendingStream::new();
861 ps.fail(HttpError::InvalidMessage("test".into()));
862 let res = ps.into_response(usize::MAX);
863 assert!(matches!(res, Err(HttpError::InvalidMessage(_))));
864 }
865
866 #[cfg(not(any(feature = "gzip", feature = "zstd", feature = "brotli")))]
867 #[test]
868 fn into_response_rejects_unsupported_content_encoding_without_features() {
869 let mut ps = PendingStream::new();
870 ps.status = Some(200);
871 ps.content_encoding = Some("gzip".into());
872 ps.body.extend_from_slice(&[1, 2, 3]);
873 let res = ps.into_response(usize::MAX);
874 match res {
875 Err(HttpError::InvalidMessage(msg)) => assert!(msg.contains("gzip")),
876 other => panic!("expected InvalidMessage for gzip without features, got {other:?}"),
877 }
878 }
879
880 #[test]
881 fn into_response_identity_encoding_returns_body_as_is() {
882 let mut ps = PendingStream::new();
883 ps.status = Some(200);
884 ps.content_encoding = Some("identity".into());
885 ps.body.extend_from_slice(&[1, 2, 3]);
886 let res = ps.into_response(usize::MAX);
887 let resp = res.expect("identity encoding must not error");
888 assert_eq!(resp.status(), 200);
889 assert_eq!(resp.bytes().as_ref(), &[1, 2, 3]);
890 }
891
892 #[test]
895 fn is_identity_encoding_recognises_variants() {
896 assert!(is_identity_encoding(""));
897 assert!(is_identity_encoding("identity"));
898 assert!(is_identity_encoding("Identity"));
899 assert!(is_identity_encoding(" identity "));
900 assert!(!is_identity_encoding("gzip"));
901 assert!(!is_identity_encoding("br"));
902 }
903}