Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: parse directly into trillium::Headers #635

Merged
merged 4 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,25 @@ jobs:
command: test
args: --workspace

- name: Run tests
- name: Run tests (parse)
uses: actions-rs/cargo@v1
with:
command: test
args: -p trillium-http -p trillium-client --features parse

- name: Run tests (smol)
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features trillium-static/smol,trillium-testing/smol

- name: Run tests
- name: Run tests (tokio)
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features trillium-static/tokio,trillium-testing/tokio

- name: Run tests
- name: Run tests (async-std)
uses: actions-rs/cargo@v1
with:
command: test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Rust Cache
uses: Swatinem/[email protected]
- name: Generate code coverage
run: cargo +nightly tarpaulin --features smol,trillium-testing/smol,trillium-http/serde --workspace --timeout 120 --out xml
run: cargo +nightly tarpaulin
- name: Upload to codecov.io
uses: codecov/codecov-action@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion async-std/src/server/tcp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::AsyncStdTransport;
use async_std::net::{TcpListener, TcpStream};
use async_std::task::{block_on, spawn};
use std::{convert::TryInto, env, future::Future, io::Result, pin::Pin};
use std::{convert::TryInto, env, future::Future, io::Result};
use trillium::Info;
use trillium_server_common::Server;

Expand Down
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
60 changes: 49 additions & 11 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,13 +696,18 @@ impl Conn {
}
}

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

#[cfg(not(feature = "parse"))]
async fn parse_head(&mut self) -> Result<()> {
const MAX_HEADERS: usize = 128;
use crate::HeaderValue;
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);
Expand All @@ -717,7 +730,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 All @@ -730,6 +742,32 @@ impl Conn {
Ok(())
}

#[cfg(feature = "parse")]
async fn parse_head(&mut self) -> Result<()> {
use std::str;

let head_offset = self.read_head().await?;

let space = memchr::memchr(b' ', &self.buffer[..head_offset]).ok_or(Error::InvalidHead)?;
self.http_version = str::from_utf8(&self.buffer[..space])
.map_err(|_| Error::InvalidHead)?
.parse()
.map_err(|_| Error::InvalidHead)?;
self.status = Some(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(())
}

async fn send_body_and_parse_head(&mut self) -> Result<()> {
if self
.request_headers
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
Loading