diff options
author | Yorhel <git@yorhel.nl> | 2016-09-14 19:02:45 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2016-09-14 19:02:45 +0200 |
commit | 31915270b3eb7fe211cc500f97dc4a2ebaa3e91d (patch) | |
tree | 984271198c7d5e40dc4e5e2444c0fa241539aa34 | |
parent | 60e0eaab3674e88c7ff13a82ee44d524dbe59477 (diff) |
-rw-r--r-- | Cargo.lock | 51 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | src/itf_http.rs | 57 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/request.rs | 176 |
5 files changed, 264 insertions, 28 deletions
@@ -2,10 +2,11 @@ name = "webs" version = "0.1.0" dependencies = [ - "env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.1.2 (git+https://github.com/seanmonstar/httparse)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.0 (git+https://github.com/carllerche/mio)", "netbuf 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -13,7 +14,7 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -31,11 +32,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "env_logger" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -44,6 +45,11 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "httparse" +version = "1.1.2" +source = "git+https://github.com/seanmonstar/httparse#49bf4d81d44dbc736ad48438540bebc5f6d17e22" + +[[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -59,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -72,17 +78,17 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mio" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/carllerche/mio#9f17b70d6fecbf912168267ea74cf536f2cba705" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", @@ -109,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -126,7 +132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -139,19 +145,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "regex" -version = "0.1.73" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -178,7 +184,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -219,24 +225,25 @@ dependencies = [ ] [metadata] -"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" -"checksum env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82dcb9ceed3868a03b335657b85a159736c961900f7e7747d3b0b97b9ccb5ccb" +"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" +"checksum httparse 1.1.2 (git+https://github.com/seanmonstar/httparse)" = "<none>" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" -"checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" +"checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum mio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2dadd39d4b47343e10513ac2a731c979517a4761224ecb6bbd243602300c9537" +"checksum mio 0.6.0 (git+https://github.com/carllerche/mio)" = "<none>" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2" "checksum netbuf 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "464910395746223293a381447af789668a3732e9564778f8bd94aaefb9543952" "checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" -"checksum regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)" = "56b7ee9f764ecf412c6e2fff779bca4b22980517ae335a21aeaf4e32625a5df2" -"checksum regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31040aad7470ad9d8c46302dcffba337bb4289ca5da2e3cd6e37b64109a85199" +"checksum regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)" = "64b03446c466d35b42f2a8b203c8e03ed8b91c0f17b56e1f84f7210a257aa665" +"checksum regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279401017ae31cf4e15344aa3f085d0e2e5c1e70067289ef906906fdbe92c8fd" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" @@ -8,6 +8,9 @@ log = "0.*" env_logger = "0.*" getopts = "0.2" nom = "1.2" -mio = "0.6" +# Need >0.6.0 for a fix to issue #465 +mio = { git = "https://github.com/carllerche/mio" } slab = "0.3.0" netbuf = "0.3.2" +# Need >1.1.2 for Display trait on Error +httparse = { git = "https://github.com/seanmonstar/httparse" } diff --git a/src/itf_http.rs b/src/itf_http.rs index 0d261a2..ec051c7 100644 --- a/src/itf_http.rs +++ b/src/itf_http.rs @@ -1,13 +1,25 @@ use mio::Event; use mio::tcp::TcpStream; use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; +use httparse; +use request; use iostream::IoStream; use listener::Interface; use eventloop::{Machine,Context,TToken,Action}; +const MAX_HEADERS: usize = 128; + +// This isn't really a hard maximum on the length of the headers, it's more a "stop reading stuff +// from the network if we've at least read this many bytes and still haven't received a complete +// header" limit. The header can exceed this limit if the last network read got us more bytes than +// MAX_HEADER_LEN. +const MAX_HEADER_LEN: usize = 4096; + + pub struct ItfHttp { io: IoStream, addr: SocketAddr, @@ -37,6 +49,44 @@ impl ItfHttp { itf: itf, } } + + // TODO: This error value is both ugly, inefficient and doesn't provide the info necessary to + // determine what kind of response we're supposed to give on error. + fn get_req(&mut self) -> Result<Option<request::Request>, String> { + // I'm not totally comfortable with having a fixed maximum on the number of headers and + // with allocating 4 KiB on the stack. But alas, it's what everyone else does. + let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; + + // Some weird indirection here to handle lifetime entanglement with the httparse object, + // input buffer and the headers list. + { + let mut parser = httparse::Request::new(&mut headers); + let res = try!(parser.parse(&self.io.rbuf[..]).map_err(|e| format!("{}", e))); + if let httparse::Status::Complete(size) = res { + Ok(Some((size, request::Request::new( + self.addr.ip(), + try!(request::Method::from_str(parser.method.unwrap()).map_err(|e| format!("{}", e))), + parser.path.unwrap(), + parser.version.unwrap() + )))) + } else { + if self.io.rbuf.len() > MAX_HEADER_LEN { + return Err("Headers too long".to_string()) + } + Ok(None) + } + }.map(|x| x.map(|(size, mut r)| { + for h in &headers[..] { + if *h == httparse::EMPTY_HEADER { + break; + } + r.headers.data.push(request::Header::new(h.name, h.value)); + } + r.headers.data.sort(); + self.io.rbuf.consume(size); + r + })) + } } @@ -44,10 +94,9 @@ impl Machine for ItfHttp { fn handle(&mut self, ctx: &mut Context, ev: Event) -> Option<Action> { try_rm!(self, self.io.handle(ctx, ev)); - // This is where we parse the stuff, generate a request object, throw the request object - // through some handlers to get a response and send it back. But for now let's just act - // like we wanted to be an echo server all along. - let _ = self.io.rbuf.write_to(&mut self.io.wbuf); + if let Some(req) = try_rm!(self, self.get_req()) { + debug!("{} {} {}", self.addr, req.method, req.path); + } self.io.set_ioreg(ctx); None diff --git a/src/main.rs b/src/main.rs index 8672f4b..5cd0a86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ #![allow(unused_mut)] #![allow(unused_imports)] // */ - #[macro_use] extern crate nom; #[macro_use] extern crate log; extern crate env_logger; @@ -12,12 +11,14 @@ extern crate getopts; extern crate mio; extern crate slab; extern crate netbuf; +extern crate httparse; mod iostream; mod config; mod eventloop; mod listener; mod itf_http; +mod request; use std::process::exit; use std::io::prelude::*; 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"")); +} |