Support to set header names of `ClientRequest` as Camel-Case

This is the case for supporting to request for servers which don't
perfectly implement the `RFC 7230`. It is important for an app
which uses `ClientRequest` as core part.

* Add field `upper_camel_case_headers` to `ClientRequest`.

* Add function `set_upper_camel_case_headers` to `ClientRequest`
  and `ClientRequestBuilder` to set field `upper_camel_case_headers`.

* Add trait `client::writer::UpperCamelCaseHeader` for
  `http::header::HeaderName`, let it can be converted to Camel-Case
  then writed to buffer.

* Add test `test_client::test_upper_camel_case_headers`.
This commit is contained in:
PeterDing 2019-03-02 18:56:57 +08:00
parent 6d11ee683f
commit 5ec6eaef1b
No known key found for this signature in database
GPG Key ID: 78EE8C2BCA22AEF3
3 changed files with 115 additions and 10 deletions

View File

@ -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.

View File

@ -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,6 +142,18 @@ impl HttpClientWriter {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
}
// 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();
@ -151,6 +163,7 @@ impl HttpClientWriter {
buffer.put_slice(v);
buffer.put_slice(b"\r\n");
}
}
// set date header
if !msg.headers().contains_key(DATE) {
@ -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);

View File

@ -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();