feat(http2): Strip connection headers before sending
Automatically removes "connection" headers before sending over HTTP2. These headers are illegal in HTTP2, and would otherwise cause errors. Closes: #1551
This commit is contained in:
committed by
Sean McArthur
parent
a0a0fcdd9b
commit
f20afba57d
@@ -1,10 +1,13 @@
|
|||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
use h2::{Reason, SendStream};
|
use h2::{Reason, SendStream};
|
||||||
|
use http::header::{
|
||||||
|
HeaderName, CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER,
|
||||||
|
TRANSFER_ENCODING, UPGRADE,
|
||||||
|
};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
use http::header::{CONNECTION, TRANSFER_ENCODING};
|
|
||||||
|
|
||||||
use ::body::Payload;
|
use body::Payload;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod server;
|
mod server;
|
||||||
@@ -13,13 +16,42 @@ pub(crate) use self::client::Client;
|
|||||||
pub(crate) use self::server::Server;
|
pub(crate) use self::server::Server;
|
||||||
|
|
||||||
fn strip_connection_headers(headers: &mut HeaderMap) {
|
fn strip_connection_headers(headers: &mut HeaderMap) {
|
||||||
if headers.remove(TRANSFER_ENCODING).is_some() {
|
// List of connection headers from:
|
||||||
trace!("removed illegal Transfer-Encoding header");
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
|
||||||
|
let connection_headers = [
|
||||||
|
HeaderName::from_lowercase(b"keep-alive").unwrap(),
|
||||||
|
HeaderName::from_lowercase(b"proxy-connection").unwrap(),
|
||||||
|
PROXY_AUTHENTICATE,
|
||||||
|
PROXY_AUTHORIZATION,
|
||||||
|
TE,
|
||||||
|
TRAILER,
|
||||||
|
TRANSFER_ENCODING,
|
||||||
|
UPGRADE,
|
||||||
|
];
|
||||||
|
|
||||||
|
for header in connection_headers.iter() {
|
||||||
|
if headers.remove(header).is_some() {
|
||||||
|
warn!("Connection header illegal in HTTP/2: {}", header.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(header) = headers.remove(CONNECTION) {
|
||||||
|
warn!(
|
||||||
|
"Connection header illegal in HTTP/2: {}",
|
||||||
|
CONNECTION.as_str()
|
||||||
|
);
|
||||||
|
let header_contents = header.to_str().unwrap();
|
||||||
|
|
||||||
|
// A `Connection` header may have a comma-separated list of names of other headers that
|
||||||
|
// are meant for only this specific connection.
|
||||||
|
//
|
||||||
|
// Iterate these names and remove them as headers. Connection-specific headers are
|
||||||
|
// forbidden in HTTP2, as that information has been moved into frame types of the h2
|
||||||
|
// protocol.
|
||||||
|
for name in header_contents.split(',') {
|
||||||
|
let name = name.trim();
|
||||||
|
headers.remove(name);
|
||||||
}
|
}
|
||||||
if headers.contains_key(CONNECTION) {
|
|
||||||
warn!("Connection header illegal in HTTP/2");
|
|
||||||
//TODO: actually remove it, after checking the value
|
|
||||||
//and removing all related headers
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +87,8 @@ where
|
|||||||
|
|
||||||
fn send_eos_frame(&mut self) -> ::Result<()> {
|
fn send_eos_frame(&mut self) -> ::Result<()> {
|
||||||
trace!("send body eos");
|
trace!("send body eos");
|
||||||
self.body_tx.send_data(SendBuf(None), true)
|
self.body_tx
|
||||||
|
.send_data(SendBuf(None), true)
|
||||||
.map_err(::Error::new_body_write)
|
.map_err(::Error::new_body_write)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,13 +127,14 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
let buf = SendBuf(Some(chunk));
|
let buf = SendBuf(Some(chunk));
|
||||||
self.body_tx.send_data(buf, is_eos)
|
self.body_tx
|
||||||
|
.send_data(buf, is_eos)
|
||||||
.map_err(::Error::new_body_write)?;
|
.map_err(::Error::new_body_write)?;
|
||||||
|
|
||||||
if is_eos {
|
if is_eos {
|
||||||
return Ok(Async::Ready(()))
|
return Ok(Async::Ready(()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
None => {
|
None => {
|
||||||
let is_eos = self.stream.is_end_stream();
|
let is_eos = self.stream.is_end_stream();
|
||||||
if is_eos {
|
if is_eos {
|
||||||
@@ -109,19 +143,20 @@ where
|
|||||||
self.data_done = true;
|
self.data_done = true;
|
||||||
// loop again to poll_trailers
|
// loop again to poll_trailers
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) {
|
match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) {
|
||||||
Some(trailers) => {
|
Some(trailers) => {
|
||||||
self.body_tx.send_trailers(trailers)
|
self.body_tx
|
||||||
|
.send_trailers(trailers)
|
||||||
.map_err(::Error::new_body_write)?;
|
.map_err(::Error::new_body_write)?;
|
||||||
return Ok(Async::Ready(()));
|
return Ok(Async::Ready(()));
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
// There were no trailers, so send an empty DATA frame...
|
// There were no trailers, so send an empty DATA frame...
|
||||||
return self.send_eos_frame().map(Async::Ready);
|
return self.send_eos_frame().map(Async::Ready);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,24 +168,16 @@ struct SendBuf<B>(Option<B>);
|
|||||||
impl<B: Buf> Buf for SendBuf<B> {
|
impl<B: Buf> Buf for SendBuf<B> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn remaining(&self) -> usize {
|
fn remaining(&self) -> usize {
|
||||||
self.0
|
self.0.as_ref().map(|b| b.remaining()).unwrap_or(0)
|
||||||
.as_ref()
|
|
||||||
.map(|b| b.remaining())
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bytes(&self) -> &[u8] {
|
fn bytes(&self) -> &[u8] {
|
||||||
self.0
|
self.0.as_ref().map(|b| b.bytes()).unwrap_or(&[])
|
||||||
.as_ref()
|
|
||||||
.map(|b| b.bytes())
|
|
||||||
.unwrap_or(&[])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn advance(&mut self, cnt: usize) {
|
fn advance(&mut self, cnt: usize) {
|
||||||
self.0
|
self.0.as_mut().map(|b| b.advance(cnt));
|
||||||
.as_mut()
|
|
||||||
.map(|b| b.advance(cnt));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,84 @@ t! {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t! {
|
||||||
|
get_strip_connection_header,
|
||||||
|
client:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
// h2 doesn't actually receive the connection header
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
server:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
headers: {
|
||||||
|
// http2 should strip this header
|
||||||
|
"connection" => "close",
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
t! {
|
||||||
|
get_strip_keep_alive_header,
|
||||||
|
client:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
// h2 doesn't actually receive the keep-alive header
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
server:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
headers: {
|
||||||
|
// http2 should strip this header
|
||||||
|
"keep-alive" => "timeout=5, max=1000",
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
t! {
|
||||||
|
get_strip_upgrade_header,
|
||||||
|
client:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
// h2 doesn't actually receive the upgrade header
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
server:
|
||||||
|
request:
|
||||||
|
uri: "/",
|
||||||
|
;
|
||||||
|
response:
|
||||||
|
headers: {
|
||||||
|
// http2 should strip this header
|
||||||
|
"upgrade" => "h2c",
|
||||||
|
},
|
||||||
|
body: "hello world",
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
t! {
|
t! {
|
||||||
get_body_chunked,
|
get_body_chunked,
|
||||||
client:
|
client:
|
||||||
|
|||||||
Reference in New Issue
Block a user