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

Add dynamic headers support #51

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
145 changes: 145 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,105 @@ 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)]
#[cfg(feature = "std")]
pub struct DynRequest<'headers> {
method: Option<(usize,usize)>,
path: Option<(usize,usize)>,
version: Option<u8>,
headers: Vec<Header<'headers>>
}

#[cfg(feature = "std")]
impl<'headers> DynRequest<'headers> {
/// Create a request that will allocate headers dynamically
#[inline]
pub fn new(header_capacity: Option<usize>) -> DynRequest<'headers> {
DynRequest {
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<usize>
{
let orig_len = buf.len();
let mut bytes = Bytes::new(buf);
let mut pos = 0;
complete!(skip_empty_lines(&mut bytes));
self.method = match try!(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 try!(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()<bytes.len() {
headers_len += complete!(parse_headers_iter(&mut headers_ref, &mut bytes));
}
Ok(Status::Complete(len + headers_len))
}
}

impl<'h, 'b> Request<'h, 'b> {
/// Creates a new Request, using a slice of headers you allocate.
#[inline]
Expand Down Expand Up @@ -427,6 +526,8 @@ pub struct Header<'a> {
/// ```
pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" };



#[inline]
fn parse_version(bytes: &mut Bytes) -> Result<u8> {
if let Some(mut eight) = bytes.next_8() {
Expand Down Expand Up @@ -498,6 +599,27 @@ 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 {
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 {
Expand All @@ -513,6 +635,29 @@ 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);

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);
Expand Down
34 changes: 34 additions & 0 deletions tests/uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ extern crate httparse;

use httparse::{Error, Request, Status, EMPTY_HEADER};

#[cfg(feature="std")]
use httparse::DynRequest;

const NUM_OF_HEADERS: usize = 4;

macro_rules! req {
Expand All @@ -23,6 +26,25 @@ 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]
#[cfg(feature = "std")]
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,
Expand All @@ -36,6 +58,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! {
Expand Down