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