diff --git a/Cargo.toml b/Cargo.toml index dd191626..7c8c9fad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,10 @@ doc-comment = "0.3" name = "header_map" path = "benches/header_map/mod.rs" +[[bench]] +name = "header_name" +path = "benches/header_name.rs" + [[bench]] name = "header_value" path = "benches/header_value.rs" diff --git a/benches/header_name.rs b/benches/header_name.rs new file mode 100644 index 00000000..d65f7d94 --- /dev/null +++ b/benches/header_name.rs @@ -0,0 +1,157 @@ +#![feature(test)] + +extern crate bytes; +extern crate http; +extern crate test; + +use http::header::HeaderName; +use test::Bencher; + +fn make_all_known_headers() -> Vec> { + // Standard request headers + vec![b"A-IM".to_vec(), + b"Accept".to_vec(), + b"Accept-Charset".to_vec(), + b"Accept-Datetime".to_vec(), + b"Accept-Encoding".to_vec(), + b"Accept-Language".to_vec(), + b"Access-Control-Request-Method".to_vec(), + b"Authorization".to_vec(), + b"Cache-Control".to_vec(), + b"Connection".to_vec(), + b"Permanent".to_vec(), + b"Content-Length".to_vec(), + b"Content-MD5".to_vec(), + b"Content-Type".to_vec(), + b"Cookie".to_vec(), + b"Date".to_vec(), + b"Expect".to_vec(), + b"Forwarded".to_vec(), + b"From".to_vec(), + b"Host".to_vec(), + b"Permanent".to_vec(), + b"HTTP2-Settings".to_vec(), + b"If-Match".to_vec(), + b"If-Modified-Since".to_vec(), + b"If-None-Match".to_vec(), + b"If-Range".to_vec(), + b"If-Unmodified-Since".to_vec(), + b"Max-Forwards".to_vec(), + b"Origin".to_vec(), + b"Pragma".to_vec(), + b"Proxy-Authorization".to_vec(), + b"Range".to_vec(), + b"Referer".to_vec(), + b"TE".to_vec(), + b"User-Agent".to_vec(), + b"Upgrade".to_vec(), + b"Via".to_vec(), + b"Warning".to_vec(), + // common_non_standard + b"Upgrade-Insecure-Requests".to_vec(), + b"Upgrade-Insecure-Requests".to_vec(), + b"X-Requested-With".to_vec(), + b"DNT".to_vec(), + b"X-Forwarded-For".to_vec(), + b"X-Forwarded-Host".to_vec(), + b"X-Forwarded-Proto".to_vec(), + b"Front-End-Https".to_vec(), + b"X-Http-Method-Override".to_vec(), + b"X-ATT-DeviceId".to_vec(), + b"X-Wap-Profile".to_vec(), + b"Proxy-Connection".to_vec(), + b"X-UIDH".to_vec(), + b"X-Csrf-Token".to_vec(), + b"X-Request-ID".to_vec(), + b"X-Correlation-ID".to_vec(), + b"Save-Data".to_vec(), + // standard_response_headers + b"Accept-Patch".to_vec(), + b"Accept-Ranges".to_vec(), + b"Access-Control-Allow-Credentials".to_vec(), + b"Access-Control-Allow-Headers".to_vec(), + b"Access-Control-Allow-Methods".to_vec(), + b"Access-Control-Allow-Origin".to_vec(), + b"Access-Control-Expose-Headers".to_vec(), + b"Access-Control-Max-Age".to_vec(), + b"Age".to_vec(), + b"Allow".to_vec(), + b"Alt-Svc".to_vec(), + b"Cache-Control".to_vec(), + b"Connection".to_vec(), + b"Content-Disposition".to_vec(), + b"Content-Encoding".to_vec(), + b"Content-Language".to_vec(), + b"Content-Length".to_vec(), + b"Content-Location".to_vec(), + b"Content-MD5".to_vec(), + b"Content-Range".to_vec(), + b"Content-Type".to_vec(), + b"Date".to_vec(), + b"Delta-Base".to_vec(), + b"ETag".to_vec(), + b"Expires".to_vec(), + b"IM".to_vec(), + b"Last-Modified".to_vec(), + b"Link".to_vec(), + b"Location".to_vec(), + b"P3P".to_vec(), + b"Permanent".to_vec(), + b"Pragma".to_vec(), + b"Proxy-Authenticate".to_vec(), + b"Public-Key-Pins".to_vec(), + b"Retry-After".to_vec(), + b"Server".to_vec(), + b"Set-Cookie".to_vec(), + b"Strict-Transport-Security".to_vec(), + b"Tk".to_vec(), + b"Trailer".to_vec(), + b"Transfer-Encoding".to_vec(), + b"Upgrade".to_vec(), + b"Vary".to_vec(), + b"Via".to_vec(), + b"Warning".to_vec(), + b"WWW-Authenticate".to_vec(), + b"X-Frame-Options".to_vec(), + // common_non_standard_response + b"Content-Security-Policy".to_vec(), + b"Refresh".to_vec(), + b"Status".to_vec(), + b"Timing-Allow-Origin".to_vec(), + b"X-Content-Duration".to_vec(), + b"X-Content-Security-Policy".to_vec(), + b"X-Content-Type-Options".to_vec(), + b"X-Correlation-ID".to_vec(), + b"X-Powered-By".to_vec(), + b"X-Request-ID".to_vec(), + b"X-UA-Compatible".to_vec(), + b"X-WebKit-CSP".to_vec(), + b"X-XSS-Protection".to_vec(), + ] +} + +#[bench] +fn header_name_easy(b: &mut Bencher) { + let name = b"Content-type"; + b.iter(|| { + HeaderName::from_bytes(&name[..]).unwrap(); + }); +} + +#[bench] +fn header_name_bad(b: &mut Bencher) { + let name = b"bad header name"; + b.iter(|| { + HeaderName::from_bytes(&name[..]).expect_err("Bad header name"); + }); +} + +#[bench] +fn header_name_various(b: &mut Bencher) { + let all_known_headers = make_all_known_headers(); + b.iter(|| { + for name in &all_known_headers{ + HeaderName::from_bytes(name.as_slice()).unwrap(); + } + }); +} diff --git a/src/header/name.rs b/src/header/name.rs index ea5c1af3..2879c0ff 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1043,6 +1043,7 @@ const HEADER_CHARS_H2: [u8; 256] = [ 0, 0, 0, 0, 0, 0 // 25x ]; +#[cfg(any(not(debug_assertions), not(target_arch = "wasm32")))] macro_rules! eq { (($($cmp:expr,)*) $v:ident[$n:expr] ==) => { $($cmp) && * @@ -1058,6 +1059,10 @@ macro_rules! eq { }; } +#[cfg(any(not(debug_assertions), not(target_arch = "wasm32")))] +/// This version is best under optimized mode, however in a wasm debug compile, +/// the `eq` macro expands to 1 + 1 + 1 + 1... and wasm explodes when this chain gets too long +/// See https://github.com/DenisKolodin/yew/issues/478 fn parse_hdr<'a>(data: &'a [u8], b: &'a mut [u8; 64], table: &[u8; 256]) -> Result, InvalidHeaderName> { @@ -1520,6 +1525,128 @@ fn parse_hdr<'a>(data: &'a [u8], b: &'a mut [u8; 64], table: &[u8; 256]) } } +#[cfg(all(debug_assertions, target_arch = "wasm32"))] +/// This version works best in debug mode in wasm +fn parse_hdr<'a>( + data: &'a [u8], + b: &'a mut [u8; 64], + table: &[u8; 256], +) -> Result, InvalidHeaderName> { + use self::StandardHeader::*; + + let len = data.len(); + + let validate = |buf: &'a [u8], len: usize| { + let buf = &buf[..len]; + if buf.iter().any(|&b| b == 0) { + Err(InvalidHeaderName::new()) + } else { + Ok(HdrName::custom(buf, true)) + } + }; + + assert!( + len < super::MAX_HEADER_NAME_LEN, + "header name too long -- max length is {}", + super::MAX_HEADER_NAME_LEN + ); + + match len { + 0 => Err(InvalidHeaderName::new()), + len if len > 64 => Ok(HdrName::custom(data, false)), + len => { + // Read from data into the buffer - transforming using `table` as we go + data.iter().zip(b.iter_mut()).for_each(|(index, out)| *out = table[*index as usize]); + match &b[0..len] { + b"te" => Ok(Te.into()), + b"age" => Ok(Age.into()), + b"via" => Ok(Via.into()), + b"dnt" => Ok(Dnt.into()), + b"date" => Ok(Date.into()), + b"etag" => Ok(Etag.into()), + b"from" => Ok(From.into()), + b"host" => Ok(Host.into()), + b"link" => Ok(Link.into()), + b"vary" => Ok(Vary.into()), + b"allow" => Ok(Allow.into()), + b"range" => Ok(Range.into()), + b"accept" => Ok(Accept.into()), + b"cookie" => Ok(Cookie.into()), + b"expect" => Ok(Expect.into()), + b"origin" => Ok(Origin.into()), + b"pragma" => Ok(Pragma.into()), + b"server" => Ok(Server.into()), + b"alt-svc" => Ok(AltSvc.into()), + b"expires" => Ok(Expires.into()), + b"referer" => Ok(Referer.into()), + b"refresh" => Ok(Refresh.into()), + b"trailer" => Ok(Trailer.into()), + b"upgrade" => Ok(Upgrade.into()), + b"warning" => Ok(Warning.into()), + b"if-match" => Ok(IfMatch.into()), + b"if-range" => Ok(IfRange.into()), + b"location" => Ok(Location.into()), + b"forwarded" => Ok(Forwarded.into()), + b"connection" => Ok(Connection.into()), + b"set-cookie" => Ok(SetCookie.into()), + b"user-agent" => Ok(UserAgent.into()), + b"retry-after" => Ok(RetryAfter.into()), + b"content-type" => Ok(ContentType.into()), + b"max-forwards" => Ok(MaxForwards.into()), + b"accept-ranges" => Ok(AcceptRanges.into()), + b"authorization" => Ok(Authorization.into()), + b"cache-control" => Ok(CacheControl.into()), + b"content-range" => Ok(ContentRange.into()), + b"if-none-match" => Ok(IfNoneMatch.into()), + b"last-modified" => Ok(LastModified.into()), + b"accept-charset" => Ok(AcceptCharset.into()), + b"content-length" => Ok(ContentLength.into()), + b"accept-encoding" => Ok(AcceptEncoding.into()), + b"accept-language" => Ok(AcceptLanguage.into()), + b"public-key-pins" => Ok(PublicKeyPins.into()), + b"x-frame-options" => Ok(XFrameOptions.into()), + b"referrer-policy" => Ok(ReferrerPolicy.into()), + b"content-language" => Ok(ContentLanguage.into()), + b"content-location" => Ok(ContentLocation.into()), + b"content-encoding" => Ok(ContentEncoding.into()), + b"www-authenticate" => Ok(WwwAuthenticate.into()), + b"x-xss-protection" => Ok(XXssProtection.into()), + b"transfer-encoding" => Ok(TransferEncoding.into()), + b"if-modified-since" => Ok(IfModifiedSince.into()), + b"sec-websocket-key" => Ok(SecWebSocketKey.into()), + b"proxy-authenticate" => Ok(ProxyAuthenticate.into()), + b"content-disposition" => Ok(ContentDisposition.into()), + b"if-unmodified-since" => Ok(IfUnmodifiedSince.into()), + b"proxy-authorization" => Ok(ProxyAuthorization.into()), + b"sec-websocket-accept" => Ok(SecWebSocketAccept.into()), + b"sec-websocket-version" => Ok(SecWebSocketVersion.into()), + b"access-control-max-age" => Ok(AccessControlMaxAge.into()), + b"x-content-type-options" => Ok(XContentTypeOptions.into()), + b"x-dns-prefetch-control" => Ok(XDnsPrefetchControl.into()), + b"sec-websocket-protocol" => Ok(SecWebSocketProtocol.into()), + b"content-security-policy" => Ok(ContentSecurityPolicy.into()), + b"sec-websocket-extensions" => Ok(SecWebSocketExtensions.into()), + b"strict-transport-security" => Ok(StrictTransportSecurity.into()), + b"upgrade-insecure-requests" => Ok(UpgradeInsecureRequests.into()), + b"access-control-allow-origin" => Ok(AccessControlAllowOrigin.into()), + b"public-key-pins-report-only" => Ok(PublicKeyPinsReportOnly.into()), + b"access-control-allow-headers" => Ok(AccessControlAllowHeaders.into()), + b"access-control-allow-methods" => Ok(AccessControlAllowMethods.into()), + b"access-control-expose-headers" => Ok(AccessControlExposeHeaders.into()), + b"access-control-request-method" => Ok(AccessControlRequestMethod.into()), + b"access-control-request-headers" => Ok(AccessControlRequestHeaders.into()), + b"access-control-allow-credentials" => Ok(AccessControlAllowCredentials.into()), + b"content-security-policy-report-only" => { + Ok(ContentSecurityPolicyReportOnly.into()) + } + other => validate(other, len), + } + } + } +} + + + impl<'a> From for HdrName<'a> { fn from(hdr: StandardHeader) -> HdrName<'a> { HdrName { inner: Repr::Standard(hdr) }