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:
Sean McArthur
2018-01-22 10:08:27 -08:00
parent 7d493aafce
commit 36e66a5054
4 changed files with 258 additions and 44 deletions

View File

@@ -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])
}
}
}
}

View File

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