diff options
Diffstat (limited to 'src/request.rs')
-rw-r--r-- | src/request.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..f5bc6ff --- /dev/null +++ b/src/request.rs @@ -0,0 +1,176 @@ +use std; +use std::cmp::Ordering; +use std::ascii::AsciiExt; +use std::net::IpAddr; + +// XXX: Use an arena allocator for the entire request object? Might be more +// efficient than an allocation for each header. But arenas have their own +// limitations (fatter pointers, unused capacity, narrower lifetimes), so I +// wonder how much it'll actually help in this case. + + +#[derive(Copy,Clone,PartialEq,Eq,Debug)] +pub enum Error { + InvalidMethod, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Error::InvalidMethod => f.write_str("invalid method"), + } + } +} + +pub type Result<T> = std::result::Result<T,Error>; + + + +// A header (name + value) is stored in a single allocation for efficiency. +// The name is lower-cased and stored in front of the buffer, followed by the +// raw value. +// +// If case-preserving header storage turns out to be desirable, then the Eq/Ord +// traits need to be implemented to do case-insensitive comparison and the +// lowercasing in Header::new() should be removed. Everything else should +// adjust automatically. +#[derive(Debug)] +pub struct Header { + namelen: usize, + buf: Box<[u8]>, +} + + +// Ordering and equality is defined on the name of the header. +impl PartialEq for Header { fn eq(&self, other: &Header) -> bool { self.name() == other.name() } } +impl PartialOrd for Header { fn partial_cmp(&self, other: &Header) -> Option<Ordering> { Some(self.cmp(other)) } } +impl Eq for Header { } +impl Ord for Header { fn cmp(&self, other: &Header) -> Ordering { self.name().cmp(other.name()) } } + + +impl Header { + pub fn new(name: &str, value: &[u8]) -> Header { + let mut v = Vec::with_capacity(name.len() + value.len()); + v.extend(name.as_bytes().iter().map(u8::to_ascii_lowercase)); + v.extend_from_slice(value); + Header { + namelen: name.len(), + buf: v.into_boxed_slice(), + } + } + + pub fn name(&self) -> &str { + unsafe { + std::str::from_utf8_unchecked(&self.buf[..self.namelen]) + } + } + + pub fn value(&self) -> &[u8] { + &self.buf[self.namelen..] + } +} + + +pub struct Headers { + pub data: Vec<Header>, +} + + +// Bulk-adding headers efficiently: +// 1. headers.data.push(..) +// 2. headers.data.sort() +// Headers are ordered by name, so a binary search can find stuff. +impl Headers { + pub fn new() -> Headers { + Headers{ data: Vec::new() } + } + + // TODO: + // - insert_sorted(Header) + // - find(&str) -> Iter + // Finds all occurrences of the given header name (case-insensitive) in + // the order that they were added. +} + + + + +// Methods that we support +// Complete list: http://www.iana.org/assignments/http-methods/http-methods.xhtml +#[derive(Debug,Copy,Clone,PartialEq,Eq)] +pub enum Method { + GET, + HEAD, + POST, + PUT, + DELETE, + // CONNECT, // Forget it, we're not that kind of proxy + OPTIONS, + // TRACE, // I'm 12 what is this +} + + +impl std::str::FromStr for Method { + type Err = Error; + fn from_str(s: &str) -> Result<Method> { + match s { + "GET" => Ok(Method::GET), + "HEAD" => Ok(Method::HEAD), + "POST" => Ok(Method::POST), + "PUT" => Ok(Method::PUT), + "DELETE" => Ok(Method::DELETE), + "OPTIONS" => Ok(Method::OPTIONS), + _ => Err(Error::InvalidMethod), + } + } +} + +impl std::fmt::Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + + + +pub struct Request { + pub client: IpAddr, + pub method: Method, + pub version: u8, // 0 for HTTP 1.0, 1 for HTTP 1.1; as in httparse + pub path: Box<str>, + pub headers: Headers, +} + + +impl Request { + pub fn new(client: IpAddr, method: Method, path: &str, version: u8) -> Request { + Request { + client: client, + method: method, + path: String::from(path).into_boxed_str(), + version: version, + headers: Headers::new(), + } + } +} + + + + + +#[test] +fn test_header() { + assert_eq!(std::mem::size_of::<Header>(), 3*std::mem::size_of::<usize>()); + + let h = Header::new("Content-Type", b"text/html; charset=UTF8"); + assert_eq!(h.name(), "content-type"); + assert_eq!(h.value(), b"text/html; charset=UTF8"); + + // Empty name is technically invalid, but let's test for it anyway + let h = Header::new("", b""); + assert_eq!(h.name(), ""); + assert_eq!(h.value(), b""); + + assert_eq!(Header::new("X-Whatever", b"val"), Header::new("x-WHATEVER", b"something else")); + assert!(Header::new("a-Whatever", b"") < Header::new("Xstuff", b"")); +} |