summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2016-09-14 19:02:45 +0200
committerYorhel <git@yorhel.nl>2016-09-14 19:02:45 +0200
commit31915270b3eb7fe211cc500f97dc4a2ebaa3e91d (patch)
tree984271198c7d5e40dc4e5e2444c0fa241539aa34
parent60e0eaab3674e88c7ff13a82ee44d524dbe59477 (diff)
Add initial request parsing + request objectHEADmaster
-rw-r--r--Cargo.lock51
-rw-r--r--Cargo.toml5
-rw-r--r--src/itf_http.rs57
-rw-r--r--src/main.rs3
-rw-r--r--src/request.rs176
5 files changed, 264 insertions, 28 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f670169..a212107 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 541ce84..3117bbe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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""));
+}