fix(lib): properly handle HTTP/1.0 remotes
- Downgrades internal semantics to HTTP/1.0 if peer sends a message with 1.0 version. - If downgraded, chunked writers become EOF writers, with the connection closing once the writing is complete. - When downgraded, if keep-alive was wanted, the `Connection: keep-alive` header is added. Closes #1304
This commit is contained in:
@@ -17,6 +17,11 @@ enum Kind {
|
||||
///
|
||||
/// Enforces that the body is not longer than the Content-Length header.
|
||||
Length(u64),
|
||||
/// An Encoder for when neither Content-Length nore Chunked encoding is set.
|
||||
///
|
||||
/// This is mostly only used with HTTP/1.0 with a length. This kind requires
|
||||
/// the connection to be closed when the body is finished.
|
||||
Eof
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
@@ -32,6 +37,12 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eof() -> Encoder {
|
||||
Encoder {
|
||||
kind: Kind::Eof,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_eof(&self) -> bool {
|
||||
match self.kind {
|
||||
Kind::Length(0) |
|
||||
@@ -40,7 +51,7 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eof(&self) -> Result<Option<&'static [u8]>, NotEof> {
|
||||
pub fn end(&self) -> Result<Option<&'static [u8]>, NotEof> {
|
||||
match self.kind {
|
||||
Kind::Length(0) => Ok(None),
|
||||
Kind::Chunked(Chunked::Init) => Ok(Some(b"0\r\n\r\n")),
|
||||
@@ -73,6 +84,12 @@ impl Encoder {
|
||||
trace!("encoded {} bytes, remaining = {}", n, remaining);
|
||||
Ok(n)
|
||||
},
|
||||
Kind::Eof => {
|
||||
if msg.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
w.write_atomic(&[msg])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use proto::{MessageHead, RawStatus, Http1Transaction, ParseResult,
|
||||
use proto::h1::{Encoder, Decoder, date};
|
||||
use method::Method;
|
||||
use status::StatusCode;
|
||||
use version::HttpVersion::{Http10, Http11};
|
||||
use version::HttpVersion::{self, Http10, Http11};
|
||||
|
||||
const MAX_HEADERS: usize = 100;
|
||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||
@@ -166,7 +166,7 @@ impl ServerTransaction {
|
||||
};
|
||||
|
||||
if has_body && can_have_body {
|
||||
set_length(&mut head.headers)
|
||||
set_length(head.version, &mut head.headers)
|
||||
} else {
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
if can_have_body {
|
||||
@@ -302,7 +302,7 @@ impl Http1Transaction for ClientTransaction {
|
||||
impl ClientTransaction {
|
||||
fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder {
|
||||
if has_body {
|
||||
set_length(&mut head.headers)
|
||||
set_length(head.version, &mut head.headers)
|
||||
} else {
|
||||
head.headers.remove::<ContentLength>();
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
@@ -311,12 +311,12 @@ impl ClientTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_length(headers: &mut Headers) -> Encoder {
|
||||
fn set_length(version: HttpVersion, headers: &mut Headers) -> Encoder {
|
||||
let len = headers.get::<header::ContentLength>().map(|n| **n);
|
||||
|
||||
if let Some(len) = len {
|
||||
Encoder::length(len)
|
||||
} else {
|
||||
} else if version == Http11 {
|
||||
let encodings = match headers.get_mut::<header::TransferEncoding>() {
|
||||
Some(&mut header::TransferEncoding(ref mut encodings)) => {
|
||||
if encodings.last() != Some(&header::Encoding::Chunked) {
|
||||
@@ -331,6 +331,9 @@ fn set_length(headers: &mut Headers) -> Encoder {
|
||||
headers.set(header::TransferEncoding(vec![header::Encoding::Chunked]));
|
||||
}
|
||||
Encoder::chunked()
|
||||
} else {
|
||||
headers.remove::<TransferEncoding>();
|
||||
Encoder::eof()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user