From af8817975235d22c7a8b76f8b6a7ecbd0ee283fa Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 15:02:04 +1100 Subject: [PATCH 1/7] Add dynamic headers support --- src/lib.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/uri.rs | 32 +++++++++++- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 99e9e09..5f72488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -284,6 +284,103 @@ pub struct Request<'headers, 'buf: 'headers> { pub headers: &'headers mut [Header<'buf>] } +/// A parsed Request that allocates headers dynamically +/// +/// The optional values will be `None` if a parse was not complete, and did not +/// parse the associated property. This allows you to inspect the parts that +/// could be parsed, before reading more, in case you wish to exit early. +/// +/// The fields of this struct are private. The user need to call +/// `get_request` to obtain a static snapshot and check the result. +/// +/// # Example +/// +/// ```no_run +/// let buf = b"GET /404 HTTP/1.1\r\nHost:"; +/// let mut req = httparse::DynRequest::new(None); +/// let res = req.parse(buf).unwrap(); +/// if res.is_partial() { +/// // get a snapshot with statically allocated headers +/// let req = req.get_request(buf); +/// match req.path { +/// Some(ref path) => { +/// // check router for path. +/// // /404 doesn't exist? we could stop parsing +/// }, +/// None => { +/// // must read more and parse again +/// } +/// } +/// } +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct DynRequest<'headers> { + method: Option<(usize,usize)>, + path: Option<(usize,usize)>, + version: Option, + headers: Vec> +} +#[allow(dead_code)] +impl<'headers> DynRequest<'headers> { + /// Create a request that will allocate headers dynamically + #[inline] + pub fn new(header_capacity: Option) -> Self { + Self { + method: None, + path: None, + version: None, + headers: if let Some(cap) = header_capacity { Vec::with_capacity(cap) } else { vec![] } + } + } + /// After a call to `parse`, call this method to obtain the result. + /// + pub fn get_request<'buf>(&'buf mut self, buf: &'buf [u8]) -> Request<'headers, 'buf> { + Request { + method: self.method.and_then(|(start, len)| + std::str::from_utf8(&buf[start..start+len]).ok()), + path: self.path.and_then(|(start, len)| + std::str::from_utf8(&buf[start..start+len]).ok()), + version: self.version, + headers: &mut self.headers + } + } + /// Try to parse a buffer of bytes into the Request. + /// Call `get_request` to check the result. + pub fn parse<'buf: 'headers>(&mut self, buf: &'buf [u8]) -> Result + { + let orig_len = buf.len(); + let mut bytes = Bytes::new(buf); + let mut pos = 0; + complete!(skip_empty_lines(&mut bytes)); + self.method = match parse_token_offset(&mut bytes)? { + Status::Complete((s,l,p)) => { + let r =Some((s+pos, l)); + pos += p; + r + }, + Status::Partial => return Ok(Status::Partial) + }; + self.path = match parse_uri_offset(&mut bytes)? { + Status::Complete((s,l,_)) => { + Some((s+pos, l)) + }, + Status::Partial => return Ok(Status::Partial) + }; + self.version = Some(complete!(parse_version(&mut bytes))); + newline!(bytes); + + let len = orig_len - bytes.len(); + let header_cnt = self.headers.len(); + self.headers.push(EMPTY_HEADER); + let mut headers_len = 0; + let mut headers_ref = &mut self.headers[header_cnt..]; + while bytes.pos() Request<'h, 'b> { /// Creates a new Request, using a slice of headers you allocate. #[inline] @@ -427,6 +524,8 @@ pub struct Header<'a> { /// ``` pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" }; + + #[inline] fn parse_version(bytes: &mut Bytes) -> Result { if let Some(mut eight) = bytes.next_8() { @@ -498,6 +597,26 @@ fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { } } +#[inline] +fn parse_token_offset<'a>(bytes: &mut Bytes<'a>) -> Result<(usize,usize,usize)> { + let start = bytes.pos(); + loop { + let b = next!(bytes); + if b == b' ' { + // we need to keep track of the position of the bytes. + // after slice_skip the position will be reset so + // we have to get it here. + let pos = bytes.pos(); + return Ok(Status::Complete((start, unsafe { + // all bytes up till `i` must have been `is_token`. + str::from_utf8_unchecked(bytes.slice_skip(1)) + }.len(), pos))); + } else if !is_token(b) { + return Err(Error::Token); + } + } +} + #[inline] fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { loop { @@ -513,6 +632,28 @@ fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { } } +#[inline] +fn parse_uri_offset<'a>(bytes: &mut Bytes<'a>) -> Result<(usize, usize, usize)> { + let start = bytes.pos(); + simd::match_uri_vectored(bytes); + + loop { + let b = next!(bytes); + if b == b' ' { + // we need to keep track of the position of the bytes. + // after slice_skip the position will be reset so + // we have to get it here. + let pos = bytes.pos(); + return Ok(Status::Complete((start,unsafe { + // all bytes up till `i` must have been `is_token`. + str::from_utf8_unchecked(bytes.slice_skip(1)) + }.len(), pos))); + } else if !is_uri_token(b) { + return Err(Error::Token); + } + } +} + #[inline] fn parse_uri<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { simd::match_uri_vectored(bytes); diff --git a/tests/uri.rs b/tests/uri.rs index 78ca72a..9bd81e4 100644 --- a/tests/uri.rs +++ b/tests/uri.rs @@ -1,6 +1,6 @@ extern crate httparse; -use httparse::{Error, Request, Status, EMPTY_HEADER}; +use httparse::{Error, Request, DynRequest, Status, EMPTY_HEADER}; const NUM_OF_HEADERS: usize = 4; @@ -23,6 +23,24 @@ macro_rules! req { } ) } +macro_rules! req_dyn { + ($name:ident, $buf:expr, |$arg:ident| $body:expr) => ( + req_dyn! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body } + ); + ($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => ( + #[test] + fn $name() { + let mut req = DynRequest::new(None); + let status = req.parse($buf.as_ref()); + assert_eq!(status, $len); + closure(req.get_request($buf.as_ref())); + + fn closure($arg: Request) { + $body + } + } + ) +} req! { urltest_001, @@ -36,6 +54,18 @@ req! { assert_eq!(req.headers[0].value, b"foo"); } } +req_dyn! { + urltest_001_dyn, + b"GET /bar;par?b HTTP/1.1\r\nHost: foo\r\n\r\n", + |req| { + assert_eq!(req.method.unwrap(), "GET"); + assert_eq!(req.path.unwrap(), "/bar;par?b"); + assert_eq!(req.version.unwrap(), 1); + assert_eq!(req.headers.len(), 1); + assert_eq!(req.headers[0].name, "Host"); + assert_eq!(req.headers[0].value, b"foo"); + } +} req! { From d89ac1a71271b4832b648398bce60f288799beff Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 15:25:52 +1100 Subject: [PATCH 2/7] limit the new ability to be in std --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5f72488..8dc92b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -314,16 +314,18 @@ pub struct Request<'headers, 'buf: 'headers> { /// } /// ``` #[derive(Debug, PartialEq, Clone)] +#[cfg(feature = "std")] pub struct DynRequest<'headers> { method: Option<(usize,usize)>, path: Option<(usize,usize)>, version: Option, headers: Vec> } -#[allow(dead_code)] + +#[cfg(feature = "std")] impl<'headers> DynRequest<'headers> { /// Create a request that will allocate headers dynamically - #[inline] + #[inline] pub fn new(header_capacity: Option) -> Self { Self { method: None, From 3a7e54ffe134d8e4f34545f1e692eb4327c7cc58 Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 15:29:14 +1100 Subject: [PATCH 3/7] limit two more functions in std --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8dc92b7..5f68a37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -600,6 +600,7 @@ fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { } #[inline] +#[cfg(feature = "std")] fn parse_token_offset<'a>(bytes: &mut Bytes<'a>) -> Result<(usize,usize,usize)> { let start = bytes.pos(); loop { @@ -635,6 +636,7 @@ fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { } #[inline] +#[cfg(feature = "std")] fn parse_uri_offset<'a>(bytes: &mut Bytes<'a>) -> Result<(usize, usize, usize)> { let start = bytes.pos(); simd::match_uri_vectored(bytes); From 07f7190ab049e0c85553f0e33475c721bed753f0 Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 15:54:03 +1100 Subject: [PATCH 4/7] add import in tests within std only --- tests/uri.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/uri.rs b/tests/uri.rs index 9bd81e4..92ca1d9 100644 --- a/tests/uri.rs +++ b/tests/uri.rs @@ -1,6 +1,9 @@ extern crate httparse; -use httparse::{Error, Request, DynRequest, Status, EMPTY_HEADER}; +use httparse::{Error, Request, Status, EMPTY_HEADER}; + +#[cfg(feature="std")] +use httparse::DynRequest; const NUM_OF_HEADERS: usize = 4; From 6da4a2d929803ba35019d7920b93be77b6932bba Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 15:57:37 +1100 Subject: [PATCH 5/7] add std requirement to test macro --- tests/uri.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/uri.rs b/tests/uri.rs index 92ca1d9..1db839d 100644 --- a/tests/uri.rs +++ b/tests/uri.rs @@ -32,6 +32,7 @@ macro_rules! req_dyn { ); ($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => ( #[test] + #[cfg(feature = "std")] fn $name() { let mut req = DynRequest::new(None); let status = req.parse($buf.as_ref()); From 884d033a209dad32065ba1ac077e0ded2a106aef Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 16:20:40 +1100 Subject: [PATCH 6/7] add rust 1.10 compatiblity --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5f68a37..bff00e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,7 +354,7 @@ impl<'headers> DynRequest<'headers> { let mut bytes = Bytes::new(buf); let mut pos = 0; complete!(skip_empty_lines(&mut bytes)); - self.method = match parse_token_offset(&mut bytes)? { + self.method = match try!(parse_token_offset(&mut bytes)) { Status::Complete((s,l,p)) => { let r =Some((s+pos, l)); pos += p; @@ -362,7 +362,7 @@ impl<'headers> DynRequest<'headers> { }, Status::Partial => return Ok(Status::Partial) }; - self.path = match parse_uri_offset(&mut bytes)? { + self.path = match try!(parse_uri_offset(&mut bytes)) { Status::Complete((s,l,_)) => { Some((s+pos, l)) }, From d43ed4b6ae71b96ecca9c5426c7bd7f19495b3b8 Mon Sep 17 00:00:00 2001 From: Joe Ren Date: Sat, 26 Jan 2019 16:30:04 +1100 Subject: [PATCH 7/7] Rust 1.10 compatiblity --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bff00e4..861e89a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -326,8 +326,8 @@ pub struct DynRequest<'headers> { impl<'headers> DynRequest<'headers> { /// Create a request that will allocate headers dynamically #[inline] - pub fn new(header_capacity: Option) -> Self { - Self { + pub fn new(header_capacity: Option) -> DynRequest<'headers> { + DynRequest { method: None, path: None, version: None,