diff --git a/src/client/request.rs b/src/client/request.rs index ad08ad135..8bd17c0e5 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -54,6 +54,7 @@ pub struct ClientRequest { method: Method, version: Version, headers: HeaderMap, + upper_camel_case_headers: bool, body: Body, chunked: bool, upgrade: bool, @@ -77,6 +78,7 @@ impl Default for ClientRequest { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), + upper_camel_case_headers: false, body: Body::Empty, chunked: false, upgrade: false, @@ -190,6 +192,19 @@ impl ClientRequest { &mut self.headers } + /// is to uppercase headers with Camel-Case + /// default is `false` + #[inline] + pub fn upper_camel_case_headers(&self) -> bool { + self.upper_camel_case_headers + } + + /// Set `true` to send headers which are uppercased with Camel-Case. + #[inline] + pub fn set_upper_camel_case_headers(&mut self, value: bool) { + self.upper_camel_case_headers = value; + } + /// is chunked encoding enabled #[inline] pub fn chunked(&self) -> bool { @@ -439,6 +454,15 @@ impl ClientRequestBuilder { self } + /// Set `true` to send headers which are uppercased with Camel-Case. + #[inline] + pub fn set_upper_camel_case_headers(&mut self, value: bool) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.upper_camel_case_headers = value; + } + self + } + /// Set content encoding. /// /// By default `ContentEncoding::Identity` is used. diff --git a/src/client/writer.rs b/src/client/writer.rs index 321753bbf..df545ea78 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -16,7 +16,7 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::Compression; use futures::{Async, Poll}; use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, + HeaderName, HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; use http::{HttpTryFrom, Version}; use time::{self, Duration}; @@ -142,14 +142,27 @@ impl HttpClientWriter { buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); } - for (key, value) in msg.headers() { - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - buffer.reserve(k.len() + v.len() + 4); - buffer.put_slice(k); - buffer.put_slice(b": "); - buffer.put_slice(v); - buffer.put_slice(b"\r\n"); + // to use Camel-Case headers + if msg.upper_camel_case_headers() { + for (key, value) in msg.headers() { + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + buffer.reserve(k.len() + v.len() + 4); + key.to_upper_camel_case(buffer); + buffer.put_slice(b": "); + buffer.put_slice(v); + buffer.put_slice(b"\r\n"); + } + } else { + for (key, value) in msg.headers() { + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + buffer.reserve(k.len() + v.len() + 4); + buffer.put_slice(k); + buffer.put_slice(b": "); + buffer.put_slice(v); + buffer.put_slice(b"\r\n"); + } } // set date header @@ -216,6 +229,38 @@ impl HttpClientWriter { } } +trait UpperCamelCaseHeader { + fn to_upper_camel_case(&self, buffer: &mut BytesMut); +} + +impl UpperCamelCaseHeader for HeaderName { + + /// Let header name to be as upper Camel-Case, then write it to buffer. + fn to_upper_camel_case(&self, buffer: &mut BytesMut) { + let key = self.as_str().as_bytes(); + let mut key_iter = key.iter(); + + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer.put_slice(&[*c ^ b' ']); + } + } else { + return; + } + + while let Some(c) = key_iter.next() { + buffer.put_slice(&[*c]); + if *c == b'-' { + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer.put_slice(&[*c ^ b' ']); + } + } + } + } + } +} + fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { let version = req.version(); let mut body = req.replace_body(Body::Empty); diff --git a/tests/test_client.rs b/tests/test_client.rs index 9808f3e6f..b82c22ed3 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -11,7 +11,7 @@ extern crate tokio_uds; use std::io::{Read, Write}; use std::{net, thread}; -use bytes::Bytes; +use bytes::{Bytes, BytesMut, BufMut}; use flate2::read::GzDecoder; use futures::stream::once; use futures::Future; @@ -477,6 +477,42 @@ fn test_default_headers() { ))); } +#[test] +fn test_upper_camel_case_headers() { + let addr = test::TestServer::unused_addr(); + + thread::spawn(move || { + let lst = net::TcpListener::bind(addr).unwrap(); + + for stream in lst.incoming() { + let mut stream = stream.unwrap(); + let mut b = [0; 1000]; + let _ = stream.read(&mut b).unwrap(); + let mut v = BytesMut::from("HTTP/1.1 200 OK\r\nconnection: close\r\n\r\n"); + v.reserve(b.len()); + v.put_slice(&b); + let _ = stream.write_all(&v); + } + }); + + let mut sys = actix::System::new("test"); + + // client request + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .header("User-Agent", "test") + .header("Accept-Encoding", "over_test") + .no_default_headers() + .set_upper_camel_case_headers(true) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(&&bytes[..62], &&b"GET / HTTP/1.1\r\nUser-Agent: test\r\nAccept-Encoding: over_test\r\n"[..]); +} + #[test] fn client_read_until_eof() { let addr = test::TestServer::unused_addr();