Fix and optimize HTTP method parsing
The old parser used a manually unrolled state machine and was broken due to upstream rust issues with match statements. The new parser is a read into a stack-allocated buffer followed by a single match on the contents of that buffer. This significantly improves the benchmarks for method reads by almost 30%, in addition to working around the upstream rust issues with reordering match blocks.
This commit is contained in:
committed by
Jonathan Reem
parent
b285451e21
commit
ab396c2394
231
src/http.rs
231
src/http.rs
@@ -262,190 +262,69 @@ pub fn is_token(b: u8) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// omg
|
/// Read bytes from `stream` into `buf` until a space is encountered.
|
||||||
enum MethodState {
|
/// Returns `Ok(true)` if we read until a space,
|
||||||
MsStart,
|
/// `Ok(false)` if we got to the end of `buf` without encountering a space,
|
||||||
MsG,
|
/// otherwise returns any error encountered reading the stream.
|
||||||
MsGE,
|
///
|
||||||
MsGET,
|
/// The remaining contents of `buf` are left untouched.
|
||||||
MsP,
|
fn read_until_space<R: Reader>(stream: &mut R, buf: &mut [u8]) -> HttpResult<bool> {
|
||||||
MsPO,
|
use std::io::BufWriter;
|
||||||
MsPOS,
|
let mut bufwrt = BufWriter::new(buf);
|
||||||
MsPOST,
|
|
||||||
MsPU,
|
|
||||||
MsPUT,
|
|
||||||
MsPA,
|
|
||||||
MsPAT,
|
|
||||||
MsPATC,
|
|
||||||
MsPATCH,
|
|
||||||
MsH,
|
|
||||||
MsHE,
|
|
||||||
MsHEA,
|
|
||||||
MsHEAD,
|
|
||||||
MsD,
|
|
||||||
MsDE,
|
|
||||||
MsDEL,
|
|
||||||
MsDELE,
|
|
||||||
MsDELET,
|
|
||||||
MsDELETE,
|
|
||||||
MsT,
|
|
||||||
MsTR,
|
|
||||||
MsTRA,
|
|
||||||
MsTRAC,
|
|
||||||
MsTRACE,
|
|
||||||
MsO,
|
|
||||||
MsOP,
|
|
||||||
MsOPT,
|
|
||||||
MsOPTI,
|
|
||||||
MsOPTIO,
|
|
||||||
MsOPTION,
|
|
||||||
MsOPTIONS,
|
|
||||||
MsC,
|
|
||||||
MsCO,
|
|
||||||
MsCON,
|
|
||||||
MsCONN,
|
|
||||||
MsCONNE,
|
|
||||||
MsCONNEC,
|
|
||||||
MsCONNECT,
|
|
||||||
MsExt
|
|
||||||
}
|
|
||||||
|
|
||||||
// omg
|
loop {
|
||||||
impl MethodState {
|
let byte = try_io!(stream.read_byte());
|
||||||
fn as_slice(&self) -> &str {
|
|
||||||
match *self {
|
if byte == SP {
|
||||||
MsG => "G",
|
break
|
||||||
MsGE => "GE",
|
// Read to end but there's still more
|
||||||
MsGET => "GET",
|
} else if bufwrt.write_u8(byte).is_err() {
|
||||||
MsP => "P",
|
return Ok(false)
|
||||||
MsPO => "PO",
|
}
|
||||||
MsPOS => "POS",
|
|
||||||
MsPOST => "POST",
|
|
||||||
MsPU => "PU",
|
|
||||||
MsPUT => "PUT",
|
|
||||||
MsPA => "PA",
|
|
||||||
MsPAT => "PAT",
|
|
||||||
MsPATC => "PATC",
|
|
||||||
MsPATCH => "PATCH",
|
|
||||||
MsH => "H",
|
|
||||||
MsHE => "HE",
|
|
||||||
MsHEA => "HEA",
|
|
||||||
MsHEAD => "HEAD",
|
|
||||||
MsD => "D",
|
|
||||||
MsDE => "DE",
|
|
||||||
MsDEL => "DEL",
|
|
||||||
MsDELE => "DELE",
|
|
||||||
MsDELET => "DELET",
|
|
||||||
MsDELETE => "DELETE",
|
|
||||||
MsT => "T",
|
|
||||||
MsTR => "TR",
|
|
||||||
MsTRA => "TRA",
|
|
||||||
MsTRAC => "TRAC",
|
|
||||||
MsTRACE => "TRACE",
|
|
||||||
MsO => "O",
|
|
||||||
MsOP => "OP",
|
|
||||||
MsOPT => "OPT",
|
|
||||||
MsOPTI => "OPTI",
|
|
||||||
MsOPTIO => "OPTIO",
|
|
||||||
MsOPTION => "OPTION",
|
|
||||||
MsOPTIONS => "OPTIONS",
|
|
||||||
MsC => "C",
|
|
||||||
MsCO => "CO",
|
|
||||||
MsCON => "CON",
|
|
||||||
MsCONN => "CONN",
|
|
||||||
MsCONNE => "CONNE",
|
|
||||||
MsCONNEC => "CONNEC",
|
|
||||||
MsCONNECT => "CONNECT",
|
|
||||||
MsStart | MsExt => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a `Method` from a raw stream, such as `GET`.
|
/// Read a `Method` from a raw stream, such as `GET`.
|
||||||
|
/// ### Note:
|
||||||
|
/// Extension methods are only parsed to 16 characters.
|
||||||
pub fn read_method<R: Reader>(stream: &mut R) -> HttpResult<method::Method> {
|
pub fn read_method<R: Reader>(stream: &mut R) -> HttpResult<method::Method> {
|
||||||
let mut s = String::new();
|
let mut buf = [SP, ..16];
|
||||||
let mut state = MsStart;
|
|
||||||
|
|
||||||
// omg
|
if !try!(read_until_space(stream, &mut buf)){
|
||||||
loop {
|
return Err(HttpMethodError);
|
||||||
match (state, try_io!(stream.read_byte())) {
|
|
||||||
(MsStart, b'G') => state = MsG,
|
|
||||||
(MsStart, b'P') => state = MsP,
|
|
||||||
(MsStart, b'H') => state = MsH,
|
|
||||||
(MsStart, b'O') => state = MsO,
|
|
||||||
(MsStart, b'T') => state = MsT,
|
|
||||||
(MsStart, b'C') => state = MsC,
|
|
||||||
(MsStart, b'D') => state = MsD,
|
|
||||||
(MsStart, b@b'A'...b'Z') => {
|
|
||||||
state = MsExt;
|
|
||||||
s.push(b as char)
|
|
||||||
},
|
|
||||||
|
|
||||||
(MsG, b'E') => state = MsGE,
|
|
||||||
(MsGE, b'T') => state = MsGET,
|
|
||||||
|
|
||||||
(MsP, b'O') => state = MsPO,
|
|
||||||
(MsPO, b'S') => state = MsPOS,
|
|
||||||
(MsPOS, b'T') => state = MsPOST,
|
|
||||||
|
|
||||||
(MsP, b'U') => state = MsPU,
|
|
||||||
(MsPU, b'T') => state = MsPUT,
|
|
||||||
|
|
||||||
(MsP, b'A') => state = MsPA,
|
|
||||||
(MsPA, b'T') => state = MsPAT,
|
|
||||||
(MsPAT, b'C') => state = MsPATC,
|
|
||||||
(MsPATC, b'H') => state = MsPATCH,
|
|
||||||
|
|
||||||
(MsH, b'E') => state = MsHE,
|
|
||||||
(MsHE, b'A') => state = MsHEA,
|
|
||||||
(MsHEA, b'D') => state = MsHEAD,
|
|
||||||
|
|
||||||
(MsO, b'P') => state = MsOP,
|
|
||||||
(MsOP, b'T') => state = MsOPT,
|
|
||||||
(MsOPT, b'I') => state = MsOPTI,
|
|
||||||
(MsOPTI, b'O') => state = MsOPTIO,
|
|
||||||
(MsOPTIO, b'N') => state = MsOPTION,
|
|
||||||
(MsOPTION, b'S') => state = MsOPTIONS,
|
|
||||||
|
|
||||||
(MsT, b'R') => state = MsTR,
|
|
||||||
(MsTR, b'A') => state = MsTRA,
|
|
||||||
(MsTRA, b'C') => state = MsTRAC,
|
|
||||||
(MsTRAC, b'E') => state = MsTRACE,
|
|
||||||
|
|
||||||
(MsC, b'O') => state = MsCO,
|
|
||||||
(MsCO, b'N') => state = MsCON,
|
|
||||||
(MsCON, b'N') => state = MsCONN,
|
|
||||||
(MsCONN, b'E') => state = MsCONNE,
|
|
||||||
(MsCONNE, b'C') => state = MsCONNEC,
|
|
||||||
(MsCONNEC, b'T') => state = MsCONNECT,
|
|
||||||
|
|
||||||
(MsD, b'E') => state = MsDE,
|
|
||||||
(MsDE, b'L') => state = MsDEL,
|
|
||||||
(MsDEL, b'E') => state = MsDELE,
|
|
||||||
(MsDELE, b'T') => state = MsDELET,
|
|
||||||
(MsDELET, b'E') => state = MsDELETE,
|
|
||||||
|
|
||||||
(MsExt, b@b'A'...b'Z') => s.push(b as char),
|
|
||||||
|
|
||||||
(_, b@b'A'...b'Z') => {
|
|
||||||
s = state.as_slice().to_string();
|
|
||||||
s.push(b as char);
|
|
||||||
},
|
|
||||||
|
|
||||||
(MsGET, SP) => return Ok(method::Get),
|
|
||||||
(MsPOST, SP) => return Ok(method::Post),
|
|
||||||
(MsPUT, SP) => return Ok(method::Put),
|
|
||||||
(MsPATCH, SP) => return Ok(method::Patch),
|
|
||||||
(MsHEAD, SP) => return Ok(method::Head),
|
|
||||||
(MsDELETE, SP) => return Ok(method::Delete),
|
|
||||||
(MsTRACE, SP) => return Ok(method::Trace),
|
|
||||||
(MsOPTIONS, SP) => return Ok(method::Options),
|
|
||||||
(MsCONNECT, SP) => return Ok(method::Connect),
|
|
||||||
(MsExt, SP) => return Ok(method::Extension(s)),
|
|
||||||
|
|
||||||
(_, _) => return Err(HttpMethodError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("method: {}", buf[].to_ascii());
|
||||||
|
|
||||||
|
let maybe_method = match buf[0..7] {
|
||||||
|
b"GET " => Some(method::Get),
|
||||||
|
b"PUT " => Some(method::Put),
|
||||||
|
b"POST " => Some(method::Post),
|
||||||
|
b"HEAD " => Some(method::Head),
|
||||||
|
b"PATCH " => Some(method::Patch),
|
||||||
|
b"TRACE " => Some(method::Trace),
|
||||||
|
b"DELETE " => Some(method::Delete),
|
||||||
|
b"CONNECT" => Some(method::Connect),
|
||||||
|
b"OPTIONS" => Some(method::Options),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (maybe_method, buf[]) {
|
||||||
|
(Some(method), _) => Ok(method),
|
||||||
|
(None, ext) if is_valid_method(buf) => {
|
||||||
|
use std::str::raw;
|
||||||
|
// We already checked that the buffer is ASCII
|
||||||
|
Ok(method::Extension(unsafe { raw::from_utf8(ext) }.trim().into_string()))
|
||||||
|
},
|
||||||
|
_ => Err(HttpMethodError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_method(buf: &[u8]) -> bool {
|
||||||
|
use std::char;
|
||||||
|
buf.iter().all(|&ch| char::is_uppercase(ch as char) || ch == SP)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a `RequestUri` from a raw stream.
|
/// Read a `RequestUri` from a raw stream.
|
||||||
|
|||||||
Reference in New Issue
Block a user