Skip to content

Commit

Permalink
feat(http,client): parse directly into trillium::Headers
Browse files Browse the repository at this point in the history
  • Loading branch information
jbr committed Apr 16, 2024
1 parent ff2a4ad commit e6dfcc2
Show file tree
Hide file tree
Showing 29 changed files with 381 additions and 149 deletions.
1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ categories = ["web-programming", "web-programming::http-client"]
[features]
websockets = ["dep:trillium-websockets", "dep:thiserror"]
json = ["dep:serde_json", "dep:serde", "dep:thiserror"]
parse = ["trillium-http/parse"]

[dependencies]
encoding_rs = "0.8.33"
Expand Down
4 changes: 3 additions & 1 deletion client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{Conn, IntoUrl, Pool, USER_AGENT};
use std::{fmt::Debug, sync::Arc, time::Duration};
use trillium_http::{
transport::BoxedTransport, HeaderName, HeaderValues, Headers, KnownHeaderName, Method,
ReceivedBodyState,
ReceivedBodyState, Version::Http1_1,
};
use trillium_server_common::{
url::{Origin, Url},
Expand Down Expand Up @@ -169,6 +169,8 @@ impl Client {
config: self.config.clone(),
headers_finalized: false,
timeout: self.timeout,
http_version: Http1_1,
max_head_length: 8 * 1024,
}
}

Expand Down
74 changes: 56 additions & 18 deletions client/src/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,19 @@ use std::{
io::{ErrorKind, Write},
ops::{Deref, DerefMut},
pin::Pin,
str::FromStr,
time::Duration,
};
use trillium_http::{
transport::BoxedTransport,
Body, Error, HeaderName, HeaderValue, HeaderValues, Headers,
transport::{BoxedTransport, Transport},
Body, Error, HeaderName, HeaderValues, Headers,
KnownHeaderName::{Connection, ContentLength, Expect, Host, TransferEncoding},
Method, ReceivedBody, ReceivedBodyState, Result, Status, Upgrade,
Method, ReceivedBody, ReceivedBodyState, Result, Status, Upgrade, Version,
};
use trillium_server_common::{
url::{Origin, Url},
ArcedConnector, Connector, Transport,
ArcedConnector, Connector,
};

const MAX_HEADERS: usize = 128;
const MAX_HEAD_LENGTH: usize = 2 * 1024;

/**
A wrapper error for [`trillium_http::Error`] or
[`serde_json::Error`]. Only available when the `json` crate feature is
Expand Down Expand Up @@ -62,6 +58,8 @@ pub struct Conn {
pub(crate) config: ArcedConnector,
pub(crate) headers_finalized: bool,
pub(crate) timeout: Option<Duration>,
pub(crate) http_version: Version,
pub(crate) max_head_length: usize,
}

/// default http user-agent header
Expand All @@ -80,6 +78,8 @@ impl Debug for Conn {
.field("buffer", &String::from_utf8_lossy(&self.buffer))
.field("response_body_state", &self.response_body_state)
.field("config", &self.config)
.field("http_version", &self.http_version)
.field("max_head_length", &self.max_head_length)
.finish()
}
}
Expand Down Expand Up @@ -509,6 +509,14 @@ impl Conn {
self
}

/// returns the http version for this conn.
///
/// prior to conn execution, this reflects the request http version that will be sent, and after
/// execution this reflects the server-indicated http version
pub fn http_version(&self) -> Version {
self.http_version
}

// --- everything below here is private ---

fn finalize_headers(&mut self) -> Result<()> {
Expand Down Expand Up @@ -615,7 +623,7 @@ impl Conn {
}
}

write!(buf, " HTTP/1.1\r\n")?;
write!(buf, " {}\r\n", self.http_version)?;

for (name, values) in &self.request_headers {
if !name.is_valid() {
Expand Down Expand Up @@ -688,25 +696,56 @@ impl Conn {
}
}

if len >= MAX_HEAD_LENGTH {
if len >= self.max_head_length {
return Err(Error::HeadersTooLong);
}
}
}

#[cfg(feature = "parse")]
async fn parse_head(&mut self) -> Result<()> {
let head_offset = self.read_head().await?;

let space = memchr::memchr(b' ', &self.buffer[..head_offset]).ok_or(Error::InvalidHead)?;
self.http_version = std::str::from_utf8(&self.buffer[..space])
.map_err(|_| Error::InvalidHead)?
.parse()
.map_err(|_| Error::InvalidHead)?;
self.status = Some(std::str::from_utf8(&self.buffer[space + 1..space + 4])?.parse()?);
let end_of_first_line = 2 + Finder::new("\r\n")
.find(&self.buffer[..head_offset])
.ok_or(Error::InvalidHead)?;

self.response_headers
.extend_parse(&self.buffer[end_of_first_line..head_offset])
.map_err(|_| Error::InvalidHead)?;

self.buffer.ignore_front(head_offset);

self.validate_response_headers()?;
Ok(())
}

#[cfg(not(feature = "parse"))]
async fn parse_head(&mut self) -> Result<()> {
const MAX_HEADERS: usize = 128;
use crate::HeaderValue;
use httparse::{Error as HttparseError, Response, EMPTY_HEADER};
use std::str::FromStr;

let head_offset = self.read_head().await?;
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
let mut httparse_res = httparse::Response::new(&mut headers);

let mut headers = [EMPTY_HEADER; MAX_HEADERS];
let mut httparse_res = Response::new(&mut headers);
let parse_result =
httparse_res
.parse(&self.buffer[..head_offset])
.map_err(|e| match e {
httparse::Error::HeaderName => Error::InvalidHeaderName,
httparse::Error::HeaderValue => Error::InvalidHeaderValue("unknown".into()),
httparse::Error::Status => Error::InvalidStatus,
httparse::Error::TooManyHeaders => Error::HeadersTooLong,
httparse::Error::Version => Error::InvalidVersion,
HttparseError::HeaderName => Error::InvalidHeaderName,
HttparseError::HeaderValue => Error::InvalidHeaderValue("unknown".into()),
HttparseError::Status => Error::InvalidStatus,
HttparseError::TooManyHeaders => Error::HeadersTooLong,
HttparseError::Version => Error::InvalidVersion,
_ => Error::InvalidHead,
})?;

Expand All @@ -717,7 +756,6 @@ impl Conn {

self.status = httparse_res.code.map(|code| code.try_into().unwrap());

self.response_headers.reserve(httparse_res.headers.len());
for header in httparse_res.headers {
let header_name = HeaderName::from_str(header.name)?;
let header_value = HeaderValue::from(header.value.to_owned());
Expand Down
24 changes: 12 additions & 12 deletions client/tests/one_hundred_continue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ async fn extra_one_hundred_continue() -> TestResult {
POST / HTTP/1.1\r
Host: example.com\r
Accept: */*\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Connection: close\r
Content-Length: 4\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
\r
"};

Expand All @@ -37,9 +37,9 @@ async fn extra_one_hundred_continue() -> TestResult {
let response_head = formatdoc! {"
HTTP/1.1 200 Ok\r
Date: {TEST_DATE}\r
Server: text\r
Connection: close\r
Content-Length: 20\r
Server: text\r
\r
response: 0123456789\
"};
Expand Down Expand Up @@ -71,10 +71,10 @@ async fn one_hundred_continue() -> TestResult {
POST / HTTP/1.1\r
Host: example.com\r
Accept: */*\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Connection: close\r
Content-Length: 4\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
\r
"};

Expand All @@ -87,9 +87,9 @@ async fn one_hundred_continue() -> TestResult {
HTTP/1.1 200 Ok\r
Date: {TEST_DATE}\r
Accept: */*\r
Server: text\r
Connection: close\r
Content-Length: 20\r
Server: text\r
\r
response: 0123456789\
"});
Expand All @@ -115,8 +115,8 @@ async fn empty_body_no_100_continue() -> TestResult {
POST / HTTP/1.1\r
Host: example.com\r
Accept: */*\r
User-Agent: {USER_AGENT}\r
Connection: close\r
User-Agent: {USER_AGENT}\r
\r
"};

Expand All @@ -125,9 +125,9 @@ async fn empty_body_no_100_continue() -> TestResult {
transport.write_all(formatdoc! {"
HTTP/1.1 200 Ok\r
Date: {TEST_DATE}\r
Server: text\r
Connection: close\r
Content-Length: 20\r
Server: text\r
\r
response: 0123456789\
"});
Expand All @@ -145,10 +145,10 @@ async fn two_small_continues() -> TestResult {
POST / HTTP/1.1\r
Host: example.com\r
Accept: */*\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Connection: close\r
Content-Length: 4\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
\r
"};

Expand Down Expand Up @@ -184,10 +184,10 @@ async fn little_continue_big_continue() -> TestResult {
POST / HTTP/1.1\r
Host: example.com\r
Accept: */*\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Connection: close\r
Content-Length: 4\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
\r
"};

Expand Down
1 change: 1 addition & 0 deletions http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ unstable = []
http-compat = ["dep:http0"]
http-compat-1 = ["dep:http1"]
serde = ["dep:serde"]
parse = []

[dependencies]
encoding_rs = "0.8.33"
Expand Down
Loading

0 comments on commit e6dfcc2

Please sign in to comment.