diff --git a/Cargo.lock b/Cargo.lock index ff6c8b58..3656ceef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2079,7 +2079,7 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rama" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "base64 0.22.1", "bytes", @@ -2122,7 +2122,7 @@ dependencies = [ [[package]] name = "rama-cli" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "base64 0.22.1", "bytes", @@ -2139,7 +2139,7 @@ dependencies = [ [[package]] name = "rama-core" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "futures-lite", "opentelemetry", @@ -2158,7 +2158,7 @@ dependencies = [ [[package]] name = "rama-dns" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "hickory-resolver", "rama-core", @@ -2171,7 +2171,7 @@ dependencies = [ [[package]] name = "rama-error" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" [[package]] name = "rama-fuzz" @@ -2188,7 +2188,7 @@ dependencies = [ [[package]] name = "rama-haproxy" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "rama-core", "rama-net", @@ -2199,7 +2199,7 @@ dependencies = [ [[package]] name = "rama-http" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "async-compression", "base64 0.22.1", @@ -2246,7 +2246,7 @@ dependencies = [ [[package]] name = "rama-http-backend" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "h2", "pin-project-lite", @@ -2263,7 +2263,7 @@ dependencies = [ [[package]] name = "rama-http-core" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "atomic-waker", "bytes", @@ -2300,7 +2300,7 @@ dependencies = [ [[package]] name = "rama-http-types" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "bytes", "const_format", @@ -2328,11 +2328,11 @@ dependencies = [ [[package]] name = "rama-macros" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" [[package]] name = "rama-net" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "base64 0.22.1", "boring", @@ -2362,7 +2362,7 @@ dependencies = [ [[package]] name = "rama-proxy" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "arc-swap", "itertools 0.14.0", @@ -2383,11 +2383,11 @@ dependencies = [ [[package]] name = "rama-socks5" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" [[package]] name = "rama-tcp" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "rama-core", "rama-dns", @@ -2400,7 +2400,7 @@ dependencies = [ [[package]] name = "rama-tls" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "boring", "flume", @@ -2425,7 +2425,7 @@ dependencies = [ [[package]] name = "rama-ua" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "rama-core", "rama-utils", @@ -2436,11 +2436,11 @@ dependencies = [ [[package]] name = "rama-udp" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" [[package]] name = "rama-utils" -version = "0.2.0-alpha.5" +version = "0.2.0-alpha.6" dependencies = [ "parking_lot", "pin-project-lite", diff --git a/rama-http/src/io/request.rs b/rama-http/src/io/request.rs index 4d9998ba..c57dfb56 100644 --- a/rama-http/src/io/request.rs +++ b/rama-http/src/io/request.rs @@ -84,8 +84,20 @@ where parts.headers = header_map.clone().consume(&mut parts.extensions); for (name, value) in header_map { - w.write_all(format!("{}: {}\r\n", name, value.to_str()?).as_bytes()) - .await?; + match parts.version { + http::Version::HTTP_2 | http::Version::HTTP_3 => { + // write lower-case for H2/H3 + w.write_all( + format!("{}: {}\r\n", name.header_name().as_str(), value.to_str()?) + .as_bytes(), + ) + .await?; + } + _ => { + w.write_all(format!("{}: {}\r\n", name, value.to_str()?).as_bytes()) + .await?; + } + } } } diff --git a/rama-http/src/io/response.rs b/rama-http/src/io/response.rs index 14a8bb10..516a43e9 100644 --- a/rama-http/src/io/response.rs +++ b/rama-http/src/io/response.rs @@ -72,8 +72,20 @@ where parts.headers = header_map.clone().consume(&mut parts.extensions); for (name, value) in header_map { - w.write_all(format!("{}: {}\r\n", name, value.to_str()?).as_bytes()) - .await?; + match parts.version { + http::Version::HTTP_2 | http::Version::HTTP_3 => { + // write lower-case for H2/H3 + w.write_all( + format!("{}: {}\r\n", name.header_name().as_str(), value.to_str()?) + .as_bytes(), + ) + .await?; + } + _ => { + w.write_all(format!("{}: {}\r\n", name, value.to_str()?).as_bytes()) + .await?; + } + } } } diff --git a/src/cli/args.rs b/src/cli/args.rs index 505ca7af..9e297a57 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -7,6 +7,7 @@ use crate::{ Body, Method, Request, Uri, }, }; +use rama_http::proto::h1::{headers::original::OriginalHttp1Headers, Http1HeaderName}; use rama_utils::macros::match_ignore_ascii_case_str; use serde_json::Value; use std::collections::HashMap; @@ -65,7 +66,7 @@ impl RequestArgsBuilder { method: None, url: arg, query: HashMap::new(), - headers: HashMap::new(), + headers: Vec::new(), body: HashMap::new(), }) } @@ -78,7 +79,7 @@ impl RequestArgsBuilder { method: method.clone(), url: arg, query: HashMap::new(), - headers: HashMap::new(), + headers: Vec::new(), body: HashMap::new(), }), BuilderState::Data { @@ -191,15 +192,24 @@ impl RequestArgsBuilder { } } } + + let mut header_order = OriginalHttp1Headers::with_capacity(headers.len()); for (name, value) in headers { - req = req.header(name, value); + let header_name = Http1HeaderName::try_copy_from_str(name.as_str()) + .context("convert string into Http1HeaderName")?; + req = req.header(header_name.clone(), value); + header_order.push(header_name); } if body.is_empty() { - return req + let mut req = req .body(Body::empty()) .map_err(OpaqueError::from_std) - .context("create request without body"); + .context("create request without body")?; + + req.extensions_mut().insert(header_order); + + return Ok(req); } let ct = content_type.unwrap_or_else(|| { @@ -217,7 +227,9 @@ impl RequestArgsBuilder { let req = if req.headers_ref().is_none() { let req = req.header(CONTENT_TYPE, ct.header_value()); + header_order.push(CONTENT_TYPE.into()); if ct == ContentType::Json { + header_order.push(ACCEPT.into()); req.header(ACCEPT, ct.header_value()) } else { req @@ -226,11 +238,13 @@ impl RequestArgsBuilder { let headers = req.headers_mut().unwrap(); if let Entry::Vacant(entry) = headers.entry(CONTENT_TYPE) { + header_order.push(CONTENT_TYPE.into()); entry.insert(ct.header_value()); } if ct == ContentType::Json { if let Entry::Vacant(entry) = headers.entry(ACCEPT) { + header_order.push(ACCEPT.into()); entry.insert(ct.header_value()); } } @@ -238,11 +252,12 @@ impl RequestArgsBuilder { req }; - match ct { + let mut req = match ct { ContentType::Json => { let body = serde_json::to_string(&body) .map_err(OpaqueError::from_std) .context("serialize form body")?; + header_order.push(CONTENT_LENGTH.into()); req.header(CONTENT_LENGTH, body.len().to_string()) .body(Body::from(body)) } @@ -250,12 +265,17 @@ impl RequestArgsBuilder { let body = serde_html_form::to_string(&body) .map_err(OpaqueError::from_std) .context("serialize json body")?; + header_order.push(CONTENT_LENGTH.into()); req.header(CONTENT_LENGTH, body.len().to_string()) .body(Body::from(body)) } } .map_err(OpaqueError::from_std) - .context("create request with body") + .context("create request with body")?; + + req.extensions_mut().insert(header_order); + + Ok(req) } } } @@ -264,7 +284,7 @@ impl RequestArgsBuilder { fn parse_arg_as_data( arg: String, query: &mut HashMap>, - headers: &mut HashMap, + headers: &mut Vec<(String, String)>, body: &mut HashMap, ) -> Result<(), String> { let mut state = DataParseArgState::None; @@ -307,7 +327,7 @@ fn parse_arg_as_data( } else { // : let value = &value[1..]; - headers.insert(name.to_owned(), value.to_owned()); + headers.push((name.to_owned(), value.to_owned())); } break; } @@ -395,7 +415,7 @@ enum BuilderState { method: Option, url: String, query: HashMap>, - headers: HashMap, + headers: Vec<(String, String)>, body: HashMap, }, Error { @@ -457,13 +477,21 @@ mod tests { for (args, expected_request_str) in [ (vec![":8080"], "GET / HTTP/1.1\r\n\r\n"), (vec!["HeAD", ":8000/foo"], "HEAD /foo HTTP/1.1\r\n\r\n"), + ( + vec![ + "example.com/bar", + "FOO:bar", + "AnSweR:42", + ], + "GET /bar HTTP/1.1\r\nFOO: bar\r\nAnSweR: 42\r\n\r\n", + ), ( vec![ "example.com/foo", "c=d", "Content-Type:application/x-www-form-urlencoded", ], - "POST /foo HTTP/1.1\r\ncontent-type: application/x-www-form-urlencoded\r\ncontent-length: 3\r\n\r\nc=d", + "POST /foo HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\ncontent-length: 3\r\n\r\nc=d", ), ( vec![ @@ -471,7 +499,7 @@ mod tests { "a=b", "Content-Type:application/json", ], - "POST /foo HTTP/1.1\r\ncontent-type: application/json\r\naccept: application/json\r\ncontent-length: 9\r\n\r\n{\"a\":\"b\"}", + "POST /foo HTTP/1.1\r\nContent-Type: application/json\r\naccept: application/json\r\ncontent-length: 9\r\n\r\n{\"a\":\"b\"}", ), ( vec![ @@ -503,7 +531,7 @@ mod tests { ":3000", "Cookie:foo=bar", ], - "GET / HTTP/1.1\r\ncookie: foo=bar\r\n\r\n", + "GET / HTTP/1.1\r\nCookie: foo=bar\r\n\r\n", ), ( vec![