Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ documentation = "https://docs.rs/http"
repository = "https://github.com/hyperium/http"
license = "MIT OR Apache-2.0"
authors = [
"Alex Crichton <[email protected]>",
"Carl Lerche <[email protected]>",
"Sean McArthur <[email protected]>",
"Alex Crichton <[email protected]>",
"Carl Lerche <[email protected]>",
"Sean McArthur <[email protected]>",
]
description = """
A set of types for representing HTTP requests and responses.
Expand All @@ -25,22 +25,26 @@ rust-version = "1.49.0"

[workspace]
members = [
".",
".",
]
exclude = [
"fuzz",
"benches"
"fuzz",
"benches"
]

[features]
default = ["std"]
std = []
double-write = ["default"]
fasthttp = ["double-write"] # compatible with fasthttp go version

[dependencies]
bytes = "1"
fnv = "1.0.5"
itoa = "1"
trie-rs = "0.4.2"
lazy_static = "1.5"
smallvec = { version = "1.15.0", features = ["specialization"]}

[dev-dependencies]
quickcheck = "1"
Expand Down
105 changes: 105 additions & 0 deletions src/ext/fasthttp/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! consts for fasthttp
use crate::header::{
ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, AUTHORIZATION, CONNECTION, CONTENT_ENCODING,
CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, COOKIE, DATE, HOST, IF_MODIFIED_SINCE,
LAST_MODIFIED, LOCATION, ORIGIN, RANGE, REFERER, SERVER, SET_COOKIE, TRANSFER_ENCODING,
USER_AGENT,
};
use crate::HeaderName;
use lazy_static::lazy_static;
use trie_rs::{Trie, TrieBuilder};

pub(crate) struct StandardHeaders(Trie<u8>);

impl StandardHeaders {
fn new() -> StandardHeaders {
let mut builder = TrieBuilder::new();

// RFC标准header
builder.push(ACCEPT_RANGES);
builder.push(ACCEPT_LANGUAGE);
builder.push(ACCEPT_ENCODING);
builder.push(AUTHORIZATION);
builder.push("expect");
builder.push(CONTENT_TYPE);
builder.push(CONTENT_ENCODING);
builder.push(CONTENT_RANGE);
builder.push(CONTENT_LENGTH);
builder.push(COOKIE);
builder.push(CONNECTION);
builder.push(DATE);
builder.push(HOST);
builder.push(IF_MODIFIED_SINCE);
builder.push(LOCATION);
builder.push(LAST_MODIFIED);
builder.push(REFERER);
builder.push(RANGE);
builder.push(SERVER);
builder.push(SET_COOKIE);
builder.push(TRANSFER_ENCODING);
builder.push(USER_AGENT);
builder.push(ORIGIN);

// 内部标准header
builder.push("x-common-params");
builder.push("use-ppe");
builder.push("tt-logid");
builder.push("tt-env");

let trie = builder.build();

StandardHeaders(trie)
}

pub fn is_match(&self, header_name: &HeaderName) -> bool {
let lowercase_header = header_name.as_str().to_ascii_lowercase();
self.0.exact_match(lowercase_header)
}
}

pub(crate) struct NonAppendHeaders(Trie<u8>);

impl NonAppendHeaders {
fn new() -> NonAppendHeaders {
let mut builder = TrieBuilder::new();

// 不可有多个值的特殊 std header
// 调用 append 等同于 insert
builder.push(CONTENT_LENGTH);
builder.push(CONNECTION);
builder.push(TRANSFER_ENCODING);
builder.push(DATE);

let trie = builder.build();

NonAppendHeaders(trie)
}

pub fn is_match(&self, header_name: &HeaderName) -> bool {
let lowercase_header = header_name.as_str().to_ascii_lowercase();
self.0.exact_match(lowercase_header)
}
}

lazy_static! {
pub(super) static ref STANDARD_HEADERS: StandardHeaders = StandardHeaders::new();
pub(super) static ref NON_APPEND_HEADERS: NonAppendHeaders = NonAppendHeaders::new();
}

/// check if is standard header
pub fn is_standard_header(header_name: &HeaderName) -> bool {
STANDARD_HEADERS.is_match(header_name)
}

/// check if is non-append standard header
pub fn is_non_append_header(header_name: &HeaderName) -> bool {
NON_APPEND_HEADERS.is_match(header_name)
}

#[test]
fn test_is_std_header() {
assert!(is_standard_header(&"Content-Type".parse().unwrap()));
assert!(is_standard_header(&"content-type".parse().unwrap()));
assert!(is_standard_header(&"CONTENT-TYPE".parse().unwrap()));
assert_eq!(is_standard_header(&"content_type".parse().unwrap()), false);
}
131 changes: 131 additions & 0 deletions src/ext/fasthttp/header_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! normalization for fasthttp
use crate::ext::fasthttp::consts::is_standard_header;
use crate::HeaderName;

const TO_LOWER: u8 = b'a' - b'A';

lazy_static::lazy_static! {
static ref TO_LOWER_TABLE: [u8; 256] = {
let mut a = [0u8; 256];
#[allow(warnings)]
for i in 0..256 {
let mut c = i as u8;
if c.is_ascii_uppercase(){
c += TO_LOWER;
}
a[i] = c;
}
a
};

static ref TO_UPPER_TABLE: [u8; 256] = {
let mut a = [0u8; 256];
#[allow(warnings)]
for i in 0..256 {
let mut c = i as u8;
if c.is_ascii_lowercase(){
c -= TO_LOWER;
}
a[i] = c;
}
a
};
}

/// 只normalize标准header,其他自定义header保持原样透传
#[allow(warnings)]
pub(crate) fn normalize_header_key_for_std_header(header_name: HeaderName) -> HeaderName {
if !is_standard_header(&header_name) {
return header_name;
}

normalize_header_key(header_name).into()
}

pub(crate) fn normalize_header_key<K>(header_name: K) -> impl Into<HeaderName>
where
K: Into<HeaderName>,
{
let header_name: HeaderName = header_name.into();
let mut header_name_str = header_name.as_raw_str().to_string();
// let normalized_header_name = header_name.as_str();
let n = header_name_str.len();
if n == 0 {
return header_name;
}

unsafe {
let header_name_bytes = header_name_str.as_bytes_mut();
header_name_bytes[0] = TO_UPPER_TABLE[header_name_bytes[0] as usize];
let mut i = 1;
while i < n {
let p = &mut header_name_bytes[i];
if *p == b'-' {
i += 1;
if i < n {
header_name_bytes[i] = TO_UPPER_TABLE[header_name_bytes[i] as usize];
i += 1;
}
continue;
}
*p = TO_LOWER_TABLE[*p as usize];
i += 1;
}

HeaderName::from_bytes(header_name_bytes).unwrap()
}
}

#[allow(dead_code)]
#[cfg(test)]
mod tests {
use crate::ext::fasthttp::header_name::{
normalize_header_key, normalize_header_key_for_std_header,
};
use crate::HeaderName;
use std::str::FromStr;

#[test]
fn test_normalize_header_key_for_std_header() {
let header_name = HeaderName::from_str("content-type").unwrap();
assert_eq!(
normalize_header_key_for_std_header(header_name).as_raw_str(),
"Content-Type"
);

let header_name = HeaderName::from_str("Content-Type").unwrap();
assert_eq!(
normalize_header_key_for_std_header(header_name).as_raw_str(),
"Content-Type"
);

// 非标准header,不做处理
let header_name = HeaderName::from_str("x-tt-agw").unwrap();
assert_eq!(
normalize_header_key_for_std_header(header_name).as_raw_str(),
"x-tt-agw"
);
}

#[test]
fn test_normalize_header_key() {
let header_name = HeaderName::from_str("content-type").unwrap();
assert_eq!(
normalize_header_key(header_name).into().as_raw_str(),
"Content-Type"
);

let header_name = HeaderName::from_str("Content-Type").unwrap();
assert_eq!(
normalize_header_key(header_name).into().as_raw_str(),
"Content-Type"
);

// 非标准header,不做处理
let header_name = HeaderName::from_str("x-tt-agw").unwrap();
assert_eq!(
normalize_header_key(header_name).into().as_raw_str(),
"X-Tt-Agw"
);
}
}
3 changes: 3 additions & 0 deletions src/ext/fasthttp/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! feature that compatible with fasthttp
pub mod consts;
pub mod header_name;
3 changes: 3 additions & 0 deletions src/ext/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! ext module
#[cfg(feature = "fasthttp")]
pub mod fasthttp;
Loading