Skip to content

Commit

Permalink
Merge pull request #105 from jbr/update-http-types
Browse files Browse the repository at this point in the history
async-h1 update for http-types 2.0.0
  • Loading branch information
yoshuawuyts authored May 22, 2020
2 parents 428fb9e + 12f4ace commit 7a7b358
Show file tree
Hide file tree
Showing 13 changed files with 57 additions and 84 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ edition = "2018"
url = "2.1.0"
httparse = "1.3.3"
async-std = { version = "1.5.0", features = ["unstable"] }
http-types = "1.2.0"
http-types = "2.0.0"
pin-project-lite = "0.1.1"
byte-pool = "0.2.1"
lazy_static = "1.4.0"
Expand Down
2 changes: 1 addition & 1 deletion examples/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async fn accept(stream: TcpStream) -> http_types::Result<()> {
println!("starting new connection from {}", stream.peer_addr()?);
async_h1::accept(stream.clone(), |_req| async move {
let mut res = Response::new(StatusCode::Ok);
res.insert_header("Content-Type", "text/plain")?;
res.insert_header("Content-Type", "text/plain");
res.set_body("Hello world");
Ok(res)
})
Expand Down
30 changes: 11 additions & 19 deletions src/chunked/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::task::{Context, Poll};
use async_std::io::{self, Read};
use async_std::sync::Arc;
use byte_pool::{Block, BytePool};
use http_types::trailers::{Trailers, TrailersSender};
use http_types::trailers::{Sender, Trailers};

const INITIAL_CAPACITY: usize = 1024 * 4;
const MAX_CAPACITY: usize = 512 * 1024 * 1024; // 512 MiB
Expand All @@ -32,11 +32,11 @@ pub(crate) struct ChunkedDecoder<R: Read> {
/// Current state.
state: State,
/// Trailer channel sender.
trailer_sender: Option<TrailersSender>,
trailer_sender: Option<Sender>,
}

impl<R: Read> ChunkedDecoder<R> {
pub(crate) fn new(inner: R, trailer_sender: TrailersSender) -> Self {
pub(crate) fn new(inner: R, trailer_sender: Sender) -> Self {
ChunkedDecoder {
inner,
buffer: POOL.alloc(INITIAL_CAPACITY),
Expand Down Expand Up @@ -156,7 +156,7 @@ impl<R: Read + Unpin> ChunkedDecoder<R> {
let sender =
sender.expect("invalid chunked state, tried sending multiple trailers");

let fut = Box::pin(sender.send(Ok(headers)));
let fut = Box::pin(sender.send(headers));
Ok(DecodeResult::Some {
read: 0,
new_state: Some(State::TrailerSending(fut)),
Expand Down Expand Up @@ -483,10 +483,7 @@ fn decode_trailer(buffer: Block<'static>, pos: &Range<usize>) -> io::Result<Deco
Ok(Status::Complete((used, headers))) => {
let mut trailers = Trailers::new();
for header in headers {
let value = std::string::String::from_utf8_lossy(header.value).to_string();
if let Err(err) = trailers.insert(header.name, value) {
return Err(io::Error::new(io::ErrorKind::Other, err.to_string()));
}
trailers.insert(header.name, String::from_utf8_lossy(header.value).as_ref());
}

Ok(DecodeResult::Some {
Expand Down Expand Up @@ -527,7 +524,7 @@ mod tests {
);

let (s, _r) = async_std::sync::channel(1);
let sender = TrailersSender::new(s);
let sender = Sender::new(s);
let mut decoder = ChunkedDecoder::new(input, sender);

let mut output = String::new();
Expand All @@ -553,7 +550,7 @@ mod tests {
input.extend("\r\n0\r\n\r\n".as_bytes());

let (s, _r) = async_std::sync::channel(1);
let sender = TrailersSender::new(s);
let sender = Sender::new(s);
let mut decoder = ChunkedDecoder::new(async_std::io::Cursor::new(input), sender);

let mut output = String::new();
Expand Down Expand Up @@ -583,21 +580,16 @@ mod tests {
.as_bytes(),
);
let (s, r) = async_std::sync::channel(1);
let sender = TrailersSender::new(s);
let sender = Sender::new(s);
let mut decoder = ChunkedDecoder::new(input, sender);

let mut output = String::new();
decoder.read_to_string(&mut output).await.unwrap();
assert_eq!(output, "MozillaDeveloperNetwork");

let trailer = r.recv().await.unwrap().unwrap();
assert_eq!(
trailer.iter().collect::<Vec<_>>(),
vec![(
&"Expires".parse().unwrap(),
&vec!["Wed, 21 Oct 2015 07:28:00 GMT".parse().unwrap()],
)]
);
let trailers = r.recv().await.unwrap();
assert_eq!(trailers.iter().count(), 1);
assert_eq!(trailers["Expires"], "Wed, 21 Oct 2015 07:28:00 GMT");
});
}
}
19 changes: 8 additions & 11 deletions src/client/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ use async_std::io::{BufReader, Read};
use async_std::prelude::*;
use http_types::{ensure, ensure_eq, format_err};
use http_types::{
headers::{HeaderName, HeaderValue, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
Body, Response, StatusCode,
};

use std::convert::TryFrom;
use std::str::FromStr;

use crate::chunked::ChunkedDecoder;
use crate::date::fmt_http_date;
Expand Down Expand Up @@ -63,26 +62,24 @@ where

let mut res = Response::new(StatusCode::try_from(code)?);
for header in httparse_res.headers.iter() {
let name = HeaderName::from_str(header.name)?;
let value = HeaderValue::from_str(std::str::from_utf8(header.value)?)?;
res.append_header(name, value)?;
res.append_header(header.name, std::str::from_utf8(header.value)?);
}

if res.header(&DATE).is_none() {
if res.header(DATE).is_none() {
let date = fmt_http_date(std::time::SystemTime::now());
res.insert_header(DATE, &format!("date: {}\r\n", date)[..])?;
res.insert_header(DATE, &format!("date: {}\r\n", date)[..]);
}

let content_length = res.header(&CONTENT_LENGTH);
let transfer_encoding = res.header(&TRANSFER_ENCODING);
let content_length = res.header(CONTENT_LENGTH);
let transfer_encoding = res.header(TRANSFER_ENCODING);

ensure!(
content_length.is_none() || transfer_encoding.is_none(),
"Unexpected Content-Length header"
);

if let Some(encoding) = transfer_encoding {
if !encoding.is_empty() && encoding.last().unwrap().as_str() == "chunked" {
if encoding.last().as_str() == "chunked" {
let trailers_sender = res.send_trailers();
let reader = BufReader::new(ChunkedDecoder::new(reader, trailers_sender));
res.set_body(Body::from_reader(reader, None));
Expand All @@ -94,7 +91,7 @@ where

// Check for Content-Length.
if let Some(len) = content_length {
let len = len.last().unwrap().as_str().parse::<usize>()?;
let len = len.last().as_str().parse::<usize>()?;
res.set_body(Body::from_reader(reader.take(len as u64), Some(len)));
}

Expand Down
4 changes: 2 additions & 2 deletions src/client/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_std::io::{self, Read};
use async_std::prelude::*;
use async_std::task::{Context, Poll};
use http_types::format_err;
use http_types::{Method, Request};
use http_types::{headers::HOST, Method, Request};

use std::pin::Pin;

Expand Down Expand Up @@ -54,7 +54,7 @@ impl Encoder {
log::trace!("> {}", &val);
buf.write_all(val.as_bytes()).await?;

if req.header(&http_types::headers::HOST).is_none() {
if req.header(HOST).is_none() {
// Insert Host header
// Insert host
let host = req.url().host_str();
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
//! println!("starting new connection from {}", stream.peer_addr()?);
//! async_h1::accept(stream.clone(), |_req| async move {
//! let mut res = Response::new(StatusCode::Ok);
//! res.insert_header("Content-Type", "text/plain")?;
//! res.insert_header("Content-Type", "text/plain");
//! res.set_body("Hello");
//! Ok(res)
//! })
Expand Down
60 changes: 23 additions & 37 deletions src/server/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::str::FromStr;

use async_std::io::{BufReader, Read, Write};
use async_std::prelude::*;
use http_types::headers::{HeaderName, HeaderValue, CONTENT_LENGTH, HOST, TRANSFER_ENCODING};
use http_types::headers::{CONTENT_LENGTH, EXPECT, HOST, TRANSFER_ENCODING};
use http_types::{ensure, ensure_eq, format_err};
use http_types::{Body, Method, Request};

Expand Down Expand Up @@ -75,16 +75,14 @@ where
);

for header in httparse_req.headers.iter() {
let name = HeaderName::from_str(header.name)?;
let value = HeaderValue::from_str(std::str::from_utf8(header.value)?)?;
req.insert_header(name, value)?;
req.insert_header(header.name, std::str::from_utf8(header.value)?);
}

set_url_and_port_from_host_header(&mut req)?;
handle_100_continue(&req, &mut io).await?;

let content_length = req.header(&CONTENT_LENGTH);
let transfer_encoding = req.header(&TRANSFER_ENCODING);
let content_length = req.header(CONTENT_LENGTH);
let transfer_encoding = req.header(TRANSFER_ENCODING);

http_types::ensure!(
content_length.is_none() || transfer_encoding.is_none(),
Expand All @@ -93,7 +91,7 @@ where

// Check for Transfer-Encoding
if let Some(encoding) = transfer_encoding {
if !encoding.is_empty() && encoding.last().unwrap().as_str() == "chunked" {
if encoding.last().as_str() == "chunked" {
let trailer_sender = req.send_trailers();
let reader = BufReader::new(ChunkedDecoder::new(reader, trailer_sender));
req.set_body(Body::from_reader(reader, None));
Expand All @@ -104,7 +102,7 @@ where

// Check for Content-Length.
if let Some(len) = content_length {
let len = len.last().unwrap().as_str().parse::<usize>()?;
let len = len.last().as_str().parse::<usize>()?;
req.set_body(Body::from_reader(reader.take(len as u64), Some(len)));
}

Expand All @@ -113,11 +111,11 @@ where

fn set_url_and_port_from_host_header(req: &mut Request) -> http_types::Result<()> {
let host = req
.header(&HOST)
.and_then(|header| header.last()) // There must only exactly one Host header, so this is permissive
.ok_or_else(|| format_err!("Mandatory Host header missing"))?; // https://tools.ietf.org/html/rfc7230#section-5.4
.header(HOST)
.map(|header| header.last()) // There must only exactly one Host header, so this is permissive
.ok_or_else(|| format_err!("Mandatory Host header missing"))? // https://tools.ietf.org/html/rfc7230#section-5.4
.to_string();

let host = host.to_string();
if let Some(colon) = host.find(":") {
req.url_mut().set_host(Some(&host[0..colon]))?;
req.url_mut()
Expand All @@ -130,17 +128,15 @@ fn set_url_and_port_from_host_header(req: &mut Request) -> http_types::Result<()
Ok(())
}

async fn handle_100_continue<IO: Write + Unpin>(
req: &Request,
io: &mut IO,
) -> http_types::Result<()> {
let expect_header_value = req
.header(&HeaderName::from_str("expect").unwrap())
.and_then(|v| v.last())
.map(|v| v.as_str());

if let Some("100-continue") = expect_header_value {
io.write_all("HTTP/1.1 100 Continue\r\n".as_bytes()).await?;
const EXPECT_HEADER_VALUE: &str = "100-continue";
const EXPECT_RESPONSE: &[u8] = b"HTTP/1.1 100 Continue\r\n";

async fn handle_100_continue<IO>(req: &Request, io: &mut IO) -> http_types::Result<()>
where
IO: Write + Unpin,
{
if let Some(EXPECT_HEADER_VALUE) = req.header(EXPECT).map(|h| h.as_str()) {
io.write_all(EXPECT_RESPONSE).await?;
}

Ok(())
Expand All @@ -162,7 +158,7 @@ mod tests {
#[test]
fn handle_100_continue_sends_header_if_expects_is_exactly_right() {
let mut request = Request::new(Method::Get, url::Url::parse("x:").unwrap());
request.append_header("expect", "100-continue").unwrap();
request.append_header("expect", "100-continue");
let mut io = async_std::io::Cursor::new(vec![]);
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
assert_eq!(
Expand All @@ -175,9 +171,7 @@ mod tests {
#[test]
fn handle_100_continue_does_nothing_if_expects_header_is_wrong() {
let mut request = Request::new(Method::Get, url::Url::parse("x:").unwrap());
request
.append_header("expect", "110-extensions-not-allowed")
.unwrap();
request.append_header("expect", "110-extensions-not-allowed");
let mut io = async_std::io::Cursor::new(vec![]);
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
assert_eq!(std::str::from_utf8(&io.into_inner()).unwrap(), "");
Expand Down Expand Up @@ -247,16 +241,8 @@ mod tests {
}

fn request_with_host_header(host: &str) -> Request {
let mut req = Request::new(
Method::Get,
url::Url::parse("http://_")
.unwrap()
.join("/some/path")
.unwrap(),
);

req.insert_header(HOST, host).unwrap();

let mut req = Request::new(Method::Get, url::Url::parse("http://_/some/path").unwrap());
req.insert_header(HOST, host);
req
}
}
7 changes: 4 additions & 3 deletions src/server/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::pin::Pin;
use async_std::io;
use async_std::io::prelude::*;
use async_std::task::{Context, Poll};
use http_types::headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http_types::Response;

use crate::chunked::ChunkedEncoder;
Expand Down Expand Up @@ -138,16 +139,16 @@ impl Encoder {
)?;
}

if self.res.header(&http_types::headers::DATE).is_none() {
if self.res.header(DATE).is_none() {
let date = fmt_http_date(std::time::SystemTime::now());
std::io::Write::write_fmt(&mut self.head, format_args!("date: {}\r\n", date))?;
}

let iter = self
.res
.iter()
.filter(|(h, _)| h != &&http_types::headers::CONTENT_LENGTH)
.filter(|(h, _)| h != &&http_types::headers::TRANSFER_ENCODING);
.filter(|(h, _)| h != &&CONTENT_LENGTH)
.filter(|(h, _)| h != &&TRANSFER_ENCODING);
for (header, values) in iter {
for value in values.iter() {
std::io::Write::write_fmt(
Expand Down
7 changes: 2 additions & 5 deletions tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async fn test_multiple_header_values_for_same_header_name() {

let res = client::decode(response_fixture).await.unwrap();

pretty_assertions::assert_eq!(res.header(&headers::SET_COOKIE).unwrap().len(), 2);
pretty_assertions::assert_eq!(res.header(&headers::SET_COOKIE).unwrap().iter().count(), 2);
}

#[async_std::test]
Expand All @@ -57,10 +57,7 @@ async fn test_response_newlines() {
let res = client::decode(response_fixture).await.unwrap();

pretty_assertions::assert_eq!(
res.header(&headers::CONTENT_LENGTH)
.unwrap()
.last()
.unwrap()
res[headers::CONTENT_LENGTH]
.as_str()
.parse::<usize>()
.unwrap(),
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/request-add-date.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
POST / HTTP/1.1
host: localhost:8080
content-length: 5
content-type: text/plain; charset=utf-8
content-type: text/plain;charset=utf-8

hello
2 changes: 1 addition & 1 deletion tests/fixtures/response-add-date.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
HTTP/1.1 200 OK
content-length: 0
date: {DATE}
content-type: text/plain; charset=utf-8
content-type: text/plain;charset=utf-8

2 changes: 1 addition & 1 deletion tests/fixtures/response-chunked-basic.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
HTTP/1.1 200 OK
transfer-encoding: chunked
date: {DATE}
content-type: text/plain
content-type: text/plain;charset=utf-8

7
Mozilla
Expand Down
Loading

0 comments on commit 7a7b358

Please sign in to comment.