1use rama_core::Layer;
2use rama_core::Service;
3use rama_core::error::BoxError;
4use rama_core::error::ErrorContext;
5use rama_core::extensions::ExtensionsRef;
6use rama_core::telemetry::tracing;
7use rama_http_headers::Connection;
8use rama_http_headers::HeaderMapExt;
9use rama_http_headers::Host;
10use rama_http_headers::SecWebSocketKey;
11use rama_http_headers::SecWebSocketVersion;
12use rama_http_headers::Upgrade;
13use rama_http_types::Method;
14use rama_http_types::Request;
15use rama_http_types::Version;
16use rama_http_types::conn::TargetHttpVersion;
17use rama_http_types::header::HOST;
18use rama_http_types::header::{SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_VERSION};
19use rama_http_types::proto::h2::ext::Protocol;
20use rama_net::client::{
21 ConnectionError, ConnectionErrorKind, ConnectorService, EstablishedClientConnection,
22};
23use rama_net::{AuthorityInputExt, Protocol as Scheme, ProtocolInputExt};
24
25use crate::layer::remove_header::{coalesce_cookie_headers, remove_illegal_h2_request_headers};
26use rama_utils::macros::generate_set_and_with;
27
28#[derive(Clone, Debug)]
29pub struct RequestVersionAdapter<S> {
34 inner: S,
35 default_http_version: Option<Version>,
36}
37
38impl<S> RequestVersionAdapter<S> {
39 pub fn new(inner: S) -> Self {
40 Self {
41 inner,
42 default_http_version: None,
43 }
44 }
45
46 generate_set_and_with! {
47 pub fn default_version(mut self, version: Option<Version>) -> Self {
50 self.default_http_version = version;
51 self
52 }
53 }
54}
55
56impl<S, Body> Service<Request<Body>> for RequestVersionAdapter<S>
57where
58 S: ConnectorService<Request<Body>>,
59 Body: Send + 'static,
60{
61 type Output = EstablishedClientConnection<S::Connection, Request<Body>>;
62 type Error = ConnectionError;
63
64 async fn serve(&self, req: Request<Body>) -> Result<Self::Output, Self::Error> {
65 let EstablishedClientConnection {
66 conn,
67 input: mut req,
68 } = self.inner.connect(req).await?;
69
70 let version = req
71 .extensions()
72 .clone_to_if_absent::<TargetHttpVersion>(conn.extensions())
73 .map(|version| version.0);
74
75 match (version, self.default_http_version) {
76 (Some(version), _) => {
77 tracing::trace!(
78 "setting request version to {:?} based on configured TargetHttpVersion (was: {:?})",
79 version,
80 req.version(),
81 );
82 adapt_request_version(&mut req, version).map_err(|error| {
83 ConnectionError::local(error, ConnectionErrorKind::InvalidInput)
84 .context("request version adapter: adapt negotiated HTTP version")
85 })?;
86 }
87 (_, Some(version)) => {
88 tracing::trace!(
89 "setting request version to {:?} based on configured default http version (was: {:?})",
90 version,
91 req.version(),
92 );
93 adapt_request_version(&mut req, version).map_err(|error| {
94 ConnectionError::local(error, ConnectionErrorKind::InvalidInput)
95 .context("request version adapter: adapt default HTTP version")
96 })?;
97
98 conn.extensions().insert(TargetHttpVersion(version));
101 }
102 (None, None) => {
103 tracing::trace!(
104 "no TargetHttpVersion or default http version configured, leaving request version {:?}",
105 req.version(),
106 );
107 }
108 }
109
110 Ok(EstablishedClientConnection { input: req, conn })
111 }
112}
113
114#[derive(Clone, Debug, Default)]
115pub struct RequestVersionAdapterLayer {
120 default_http_version: Option<Version>,
121}
122
123impl RequestVersionAdapterLayer {
124 #[must_use]
125 pub fn new() -> Self {
126 Self {
127 default_http_version: None,
128 }
129 }
130
131 generate_set_and_with! {
132 pub fn default_version(mut self, version: Option<Version>) -> Self {
135 self.default_http_version = version;
136 self
137 }
138 }
139}
140
141impl<S> Layer<S> for RequestVersionAdapterLayer {
142 type Service = RequestVersionAdapter<S>;
143
144 fn layer(&self, inner: S) -> Self::Service {
145 RequestVersionAdapter {
146 inner,
147 default_http_version: self.default_http_version,
148 }
149 }
150}
151
152pub fn adapt_request_version<Body>(
154 request: &mut Request<Body>,
155 target_version: Version,
156) -> Result<(), BoxError> {
157 let request_version = request.version();
158 if request_version == target_version {
159 tracing::trace!(
160 ?target_version,
161 "request version already satisfied, skipping it"
162 );
163 return Ok(());
164 }
165 tracing::trace!(
166 ?request_version,
167 ?target_version,
168 "changing request version"
169 );
170
171 let request_is_h1 = request_version <= Version::HTTP_11;
172 let target_is_h1 = target_version <= Version::HTTP_11;
173
174 match (request_is_h1, target_is_h1) {
177 (true, false) => translate_request_upgrade(request)?,
178 (false, true) => translate_request_downgrade(request)?,
179 (true, true) | (false, false) => {}
180 }
181
182 *request.version_mut() = target_version;
185 ensure_valid_request_for_version(request)?;
186
187 Ok(())
188}
189
190pub fn ensure_valid_request_for_version<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
192 if request.version() <= Version::HTTP_11 {
193 ensure_valid_h1_request(request)
194 } else {
195 ensure_valid_h2_or_h3_request(request)
196 }
197}
198
199pub fn ensure_valid_h1_request<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
202 ensure_h1_host_header(request)?;
203 coalesce_cookie_headers(request.headers_mut());
204 Ok(())
205}
206
207pub fn ensure_valid_h2_or_h3_request<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
211 ensure_h2_or_h3_uri_authority(request)?;
212 remove_illegal_h2_request_headers(request.headers_mut());
213 Ok(())
214}
215
216pub(crate) fn is_websocket_protocol(protocol: &Protocol) -> bool {
218 protocol.as_str().eq_ignore_ascii_case("websocket")
219}
220
221pub(crate) fn request_connect_protocol<Body>(request: &Request<Body>) -> Option<Protocol> {
229 if request.method() == Method::CONNECT
230 && let Some(protocol) = request.extensions().get_ref::<Protocol>()
231 {
232 return Some(protocol.clone());
233 }
234
235 let is_genuine_upgrade = request
236 .headers()
237 .typed_get::<Connection>()
238 .is_some_and(|connection| connection.contains_upgrade());
239 if !is_genuine_upgrade {
240 return None;
241 }
242 let upgrade = request.headers().typed_get::<Upgrade>()?;
243 let token = std::str::from_utf8(upgrade.as_bytes()).ok()?.trim();
244 (!token.is_empty()).then(|| Protocol::from(token))
245}
246
247fn translate_request_upgrade<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
255 match request_connect_protocol(request) {
256 Some(protocol) if is_websocket_protocol(&protocol) => {
257 tracing::trace!("translating h1 websocket upgrade into h2/h3 extended CONNECT");
259 *request.method_mut() = Method::CONNECT;
260 request
261 .extensions()
262 .insert(Protocol::from_static("websocket"));
263 }
264 Some(protocol) => {
265 return Err(BoxError::from(format!(
266 "cannot translate HTTP/1 `Upgrade: {}` into an HTTP/2+ Extended CONNECT: only websocket is supported",
267 protocol.as_str(),
268 )));
269 }
270 None => {}
271 }
272 Ok(())
273}
274
275fn translate_request_downgrade<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
282 match request_connect_protocol(request) {
283 Some(protocol) if is_websocket_protocol(&protocol) => {
284 tracing::trace!("translating h2/h3 extended CONNECT websocket into h1 upgrade");
286 *request.method_mut() = Method::GET;
287
288 let headers = request.headers_mut();
289 headers.typed_insert(Upgrade::websocket());
290 headers.typed_insert(Connection::upgrade());
291 if !headers.contains_key(SEC_WEBSOCKET_KEY) {
292 headers.typed_insert(SecWebSocketKey::random());
293 }
294 if !headers.contains_key(SEC_WEBSOCKET_VERSION) {
295 headers.typed_insert(SecWebSocketVersion::V13);
296 }
297
298 }
302 Some(protocol) => {
303 return Err(BoxError::from(format!(
304 "cannot translate an HTTP/2+ Extended CONNECT `:protocol: {}` request to HTTP/1: only websocket is supported",
305 protocol.as_str(),
306 )));
307 }
308 None => {}
309 }
310 Ok(())
311}
312
313pub fn ensure_h1_host_header<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
320 if request.headers().contains_key(HOST) {
321 return Ok(());
322 }
323 let authority = request
324 .authority()
325 .context("ensure h1 Host header: request has no resolvable authority")?;
326 let protocol = request.protocol().cloned();
327 let authority = authority.without_default_port_for(protocol.as_ref());
330 tracing::trace!("adding Host header {authority} derived from request authority");
331 request.headers_mut().typed_insert(Host::from(authority));
332 Ok(())
333}
334
335pub fn ensure_h2_or_h3_uri_authority<Body>(request: &mut Request<Body>) -> Result<(), BoxError> {
343 if request.uri().host().is_some() {
344 return Ok(());
345 }
346 let authority = request
347 .authority()
348 .context("ensure h2 URI authority: request has no resolvable authority")?;
349 let protocol = request.protocol().cloned();
350 let authority = authority.without_default_port_for(protocol.as_ref());
351 tracing::trace!("materializing authority {authority} and scheme into request URI");
352 let uri = request.uri_mut();
353 uri.set_scheme(protocol.unwrap_or(Scheme::HTTP));
354 uri.set_host(authority.host);
355 uri.set_port(authority.port);
356 Ok(())
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use rama_http_types::header::{CONNECTION, COOKIE, HOST, TRANSFER_ENCODING, UPGRADE};
363
364 #[test]
365 fn test_h1_to_h2_strips_connection_specific_headers() {
366 let mut req = Request::builder()
367 .version(Version::HTTP_11)
368 .uri("https://example.com")
369 .header(HOST, "example.com")
370 .header(CONNECTION, "keep-alive, x-custom")
371 .header("keep-alive", "timeout=5")
372 .header(TRANSFER_ENCODING, "chunked")
373 .header("x-custom", "1")
374 .header("x-keep", "yes")
375 .body(())
376 .unwrap();
377
378 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
379
380 assert_eq!(req.version(), Version::HTTP_2);
381 for illegal in [&HOST, &CONNECTION, &TRANSFER_ENCODING] {
382 assert!(
383 !req.headers().contains_key(illegal),
384 "expected {illegal:?} to be removed"
385 );
386 }
387 assert!(!req.headers().contains_key("x-custom"));
389 assert!(!req.headers().contains_key("keep-alive"));
390 assert_eq!(req.headers().get("x-keep").unwrap(), "yes");
392 }
393
394 #[test]
395 fn test_h1_to_h2_websocket_upgrade_becomes_extended_connect() {
396 let mut req = Request::builder()
397 .version(Version::HTTP_11)
398 .method(Method::GET)
399 .uri("https://example.com/chat")
400 .header(UPGRADE, "websocket")
401 .header(CONNECTION, "Upgrade")
402 .header(SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==")
403 .header(SEC_WEBSOCKET_VERSION, "13")
404 .header("sec-websocket-protocol", "chat")
405 .body(())
406 .unwrap();
407
408 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
409
410 assert_eq!(req.method(), Method::CONNECT);
411 assert_eq!(
412 req.extensions().get_ref::<Protocol>().map(|p| p.as_str()),
413 Some("websocket"),
414 );
415 assert!(!req.headers().contains_key(UPGRADE));
417 assert!(!req.headers().contains_key(CONNECTION));
418 assert!(!req.headers().contains_key(SEC_WEBSOCKET_KEY));
419 assert_eq!(req.headers().get(SEC_WEBSOCKET_VERSION).unwrap(), "13");
421 assert_eq!(req.headers().get("sec-websocket-protocol").unwrap(), "chat");
422 }
423
424 #[test]
425 fn test_h2_to_h1_websocket_connect_becomes_upgrade() {
426 let mut req = Request::builder()
427 .version(Version::HTTP_2)
428 .method(Method::CONNECT)
429 .uri("https://example.com/chat")
430 .header(SEC_WEBSOCKET_VERSION, "13")
431 .body(())
432 .unwrap();
433 req.extensions().insert(Protocol::from_static("websocket"));
434
435 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
436
437 assert_eq!(req.method(), Method::GET);
438 assert_eq!(req.version(), Version::HTTP_11);
439 assert!(
440 req.headers()
441 .typed_get::<Upgrade>()
442 .is_some_and(|u| u.is_websocket())
443 );
444 assert!(
445 req.headers()
446 .typed_get::<Connection>()
447 .is_some_and(|c| c.contains_upgrade())
448 );
449 assert!(req.headers().contains_key(SEC_WEBSOCKET_KEY));
451 assert_eq!(req.headers().get(SEC_WEBSOCKET_VERSION).unwrap(), "13");
452 }
453
454 #[test]
455 fn test_h2_to_h1_non_websocket_connect_untouched() {
456 let mut req = Request::builder()
457 .version(Version::HTTP_2)
458 .method(Method::CONNECT)
459 .uri("example.com:443")
460 .header(HOST, "example.com:443")
461 .body(())
462 .unwrap();
463
464 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
465
466 assert_eq!(req.method(), Method::CONNECT);
468 assert!(!req.headers().contains_key(UPGRADE));
469 assert!(!req.headers().contains_key(SEC_WEBSOCKET_KEY));
470 }
471
472 #[test]
473 fn test_h2_to_h1_adds_host_from_authority() {
474 let mut req = Request::builder()
475 .version(Version::HTTP_2)
476 .uri("https://example.com/path")
477 .body(())
478 .unwrap();
479
480 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
481
482 assert_eq!(req.version(), Version::HTTP_11);
484 assert_eq!(req.headers().get(HOST).unwrap(), "example.com");
485 }
486
487 #[test]
488 fn test_h1_to_h2_materializes_uri_authority_and_strips_host() {
489 let mut req = Request::builder()
490 .version(Version::HTTP_11)
491 .uri("/path")
492 .header(HOST, "example.com")
493 .body(())
494 .unwrap();
495
496 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
497
498 assert_eq!(req.version(), Version::HTTP_2);
499 assert_eq!(req.uri().host_str().as_deref(), Some("example.com"));
501 assert!(!req.headers().contains_key(HOST));
502 }
503
504 #[test]
505 fn test_h1_to_h3_websocket_upgrade_becomes_extended_connect() {
506 let mut req = Request::builder()
507 .version(Version::HTTP_11)
508 .method(Method::GET)
509 .uri("https://example.com/chat")
510 .header(UPGRADE, "websocket")
511 .header(CONNECTION, "Upgrade")
512 .header(SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==")
513 .body(())
514 .unwrap();
515
516 adapt_request_version(&mut req, Version::HTTP_3).unwrap();
517
518 assert_eq!(req.version(), Version::HTTP_3);
521 assert_eq!(req.method(), Method::CONNECT);
522 assert_eq!(
523 req.extensions().get_ref::<Protocol>().map(|p| p.as_str()),
524 Some("websocket"),
525 );
526 assert!(!req.headers().contains_key(UPGRADE));
527 assert!(!req.headers().contains_key(SEC_WEBSOCKET_KEY));
528 }
529
530 #[test]
531 fn test_h2_to_h3_only_changes_version() {
532 let mut req = Request::builder()
533 .version(Version::HTTP_2)
534 .uri("https://example.com/path")
535 .header(COOKIE, "a=1")
536 .header(COOKIE, "b=2")
537 .body(())
538 .unwrap();
539
540 adapt_request_version(&mut req, Version::HTTP_3).unwrap();
541
542 assert_eq!(req.version(), Version::HTTP_3);
545 assert_eq!(req.headers().get_all(COOKIE).iter().count(), 2);
546 }
547
548 #[test]
549 fn test_h1_to_h2_unsupported_upgrade_errors() {
550 let mut req = Request::builder()
551 .version(Version::HTTP_11)
552 .method(Method::GET)
553 .uri("https://example.com/")
554 .header(UPGRADE, "myproto")
555 .header(CONNECTION, "Upgrade")
556 .body(())
557 .unwrap();
558
559 let err = adapt_request_version(&mut req, Version::HTTP_2).unwrap_err();
562 assert!(
563 err.to_string().contains("only websocket is supported"),
564 "{err}"
565 );
566 }
567
568 #[test]
569 fn test_h1_to_h2_upgrade_advertisement_is_not_a_switch() {
570 let mut req = Request::builder()
571 .version(Version::HTTP_11)
572 .method(Method::GET)
573 .uri("https://example.com/")
574 .header(UPGRADE, "h2c")
577 .body(())
578 .unwrap();
579
580 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
581
582 assert_eq!(req.version(), Version::HTTP_2);
583 assert!(!req.headers().contains_key(UPGRADE));
584 }
585
586 #[test]
587 fn test_h2_to_h1_unsupported_extended_connect_errors() {
588 let mut req = Request::builder()
589 .version(Version::HTTP_2)
590 .method(Method::CONNECT)
591 .uri("https://example.com/")
592 .body(())
593 .unwrap();
594 req.extensions()
595 .insert(Protocol::from_static("connect-udp"));
596
597 let err = adapt_request_version(&mut req, Version::HTTP_11).unwrap_err();
598 assert!(
599 err.to_string().contains("only websocket is supported"),
600 "{err}"
601 );
602 }
603
604 #[test]
605 fn test_merge_multiple_cookies_http2_to_http1() {
606 let mut req = Request::builder()
607 .version(Version::HTTP_2)
608 .uri("https://example.com")
609 .header(COOKIE, "a=1")
610 .header(COOKIE, "b=2")
611 .header(COOKIE, "c=3")
612 .body(())
613 .unwrap();
614
615 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
616
617 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
619 assert_eq!(
620 cookie_values.len(),
621 1,
622 "Should have exactly one Cookie header"
623 );
624
625 assert_eq!(cookie_values[0].as_bytes(), b"a=1; b=2; c=3");
627
628 assert_eq!(req.version(), Version::HTTP_11);
630 }
631
632 #[test]
633 fn test_merge_multiple_cookies_http3_to_http1() {
634 let mut req = Request::builder()
635 .version(Version::HTTP_3)
636 .uri("https://example.com")
637 .header(COOKIE, "session=abc123")
638 .header(COOKIE, "token=xyz789")
639 .body(())
640 .unwrap();
641
642 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
643
644 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
645 assert_eq!(
646 cookie_values.len(),
647 1,
648 "Should have exactly one Cookie header"
649 );
650 assert_eq!(cookie_values[0].as_bytes(), b"session=abc123; token=xyz789");
651 }
652
653 #[test]
654 fn test_single_cookie_http2_to_http1_unchanged() {
655 let mut req = Request::builder()
656 .version(Version::HTTP_2)
657 .uri("https://example.com")
658 .header(COOKIE, "single=cookie")
659 .body(())
660 .unwrap();
661
662 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
663
664 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
666 assert_eq!(cookie_values.len(), 1);
667 assert_eq!(cookie_values[0].as_bytes(), b"single=cookie");
668 }
669
670 #[test]
671 fn test_no_merge_http1_to_http2() {
672 let mut req = Request::builder()
673 .version(Version::HTTP_11)
674 .uri("https://example.com")
675 .header(COOKIE, "a=1")
676 .header(COOKIE, "b=2")
677 .body(())
678 .unwrap();
679
680 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
681
682 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
684 assert_eq!(
685 cookie_values.len(),
686 2,
687 "Should preserve multiple headers when converting to HTTP/2"
688 );
689 }
690
691 #[test]
692 fn test_no_cookies_http2_to_http1() {
693 let mut req = Request::builder()
694 .version(Version::HTTP_2)
695 .uri("https://example.com")
696 .body(())
697 .unwrap();
698
699 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
700
701 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
703 assert_eq!(cookie_values.len(), 0);
704 }
705
706 #[test]
707 fn test_merge_preserves_order() {
708 let mut req = Request::builder()
709 .version(Version::HTTP_2)
710 .uri("https://example.com")
711 .header(COOKIE, "first=1")
712 .header(COOKIE, "second=2")
713 .header(COOKIE, "third=3")
714 .header(COOKIE, "fourth=4")
715 .body(())
716 .unwrap();
717
718 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
719
720 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
722 assert_eq!(cookie_values.len(), 1);
723 assert_eq!(
724 cookie_values[0].as_bytes(),
725 b"first=1; second=2; third=3; fourth=4",
726 "Cookie order should be preserved"
727 );
728 }
729
730 #[test]
731 fn test_complex_cookie_values() {
732 let mut req = Request::builder()
733 .version(Version::HTTP_2)
734 .uri("https://example.com")
735 .header(COOKIE, "uaid=abc123def456")
736 .header(COOKIE, "MSCC=NR")
737 .header(COOKIE, "MUID=1234567890ABCDEF")
738 .header(COOKIE, "VAL1=ASD=DSA&HASH=41&LV=41&V=4&LU=41")
739 .header(COOKIE, "empty=")
740 .body(())
741 .unwrap();
742
743 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
744
745 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
747 assert_eq!(cookie_values.len(), 1);
748 assert_eq!(
749 cookie_values[0].as_bytes(),
750 b"uaid=abc123def456; MSCC=NR; MUID=1234567890ABCDEF; VAL1=ASD=DSA&HASH=41&LV=41&V=4&LU=41; empty=",
751 );
752 }
753
754 #[test]
755 fn test_same_version_http2_keeps_multiple_cookies() {
756 let mut req = Request::builder()
757 .version(Version::HTTP_2)
758 .uri("https://example.com")
759 .header(COOKIE, "a=1")
760 .header(COOKIE, "b=2")
761 .body(())
762 .unwrap();
763
764 adapt_request_version(&mut req, Version::HTTP_2).unwrap();
765
766 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
768 assert_eq!(cookie_values.len(), 2);
769 }
770
771 #[test]
772 fn test_same_version_http1_is_noop() {
773 let mut req = Request::builder()
774 .version(Version::HTTP_11)
775 .uri("https://example.com")
776 .header(COOKIE, "a=1")
777 .header(COOKIE, "b=2")
778 .body(())
779 .unwrap();
780
781 adapt_request_version(&mut req, Version::HTTP_11).unwrap();
783
784 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
785 assert_eq!(cookie_values.len(), 2);
786 }
787
788 #[test]
789 fn test_ensure_valid_h1_request_normalizes() {
790 let mut req = Request::builder()
791 .version(Version::HTTP_11)
792 .uri("https://example.com")
793 .header(COOKIE, "a=1")
794 .header(COOKIE, "b=2")
795 .body(())
796 .unwrap();
797
798 ensure_valid_request_for_version(&mut req).unwrap();
801
802 let cookie_values: Vec<_> = req.headers().get_all(COOKIE).iter().collect();
803 assert_eq!(cookie_values.len(), 1);
804 assert_eq!(cookie_values[0].as_bytes(), b"a=1; b=2");
805 assert!(req.headers().contains_key(HOST));
806 }
807}