summaryrefslogtreecommitdiff
path: root/src/request.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/request.rs')
-rw-r--r--src/request.rs176
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""));
+}