Skip to content

Commit

Permalink
Refactor: headers (#202)
Browse files Browse the repository at this point in the history
* refactor `mod header`, `push_unchecked!` unsafety

* refactor headers code

* fix cfgs

* fix seprator

* fix bench
  • Loading branch information
kanarus authored Jul 5, 2024
1 parent 0258e3a commit 4485100
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 547 deletions.
7 changes: 4 additions & 3 deletions benches/benches/atoi.rs → benches/benches/itoa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@


mod candiate {#![allow(unused)]
#[inline(always)]
pub fn to_string(n: usize) -> String {
n.to_string()
}

#[inline(always)]
pub fn atoi_01(mut n: usize) -> String {
ohkami_lib::num::atoi(n)
pub fn itoa(mut n: usize) -> String {
ohkami_lib::num::itoa(n)
}
}

Expand All @@ -24,5 +25,5 @@ macro_rules! benchmark {
)*};
} benchmark! {
to_string
atoi_01
itoa
}
36 changes: 36 additions & 0 deletions ohkami/src/header/append.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::borrow::Cow;


pub struct Append(pub(crate) Cow<'static, str>);

/// Passed to `{Request/Response}.headers.set().Name( 〜 )` and
/// append `value` to the header.
///
/// Here appended values are combined by `,`.
///
/// ---
/// *example.rs*
/// ```no_run
/// use ohkami::prelude::*;
/// use ohkami::header::append;
///
/// #[derive(Clone)]
/// struct AppendServer(&'static str);
/// impl FangAction for AppendServer {
/// async fn back<'b>(&'b self, res: &'b mut Response) {
/// res.headers.set()
/// .Server(append(self.0));
/// }
/// }
///
/// #[tokio::main]
/// async fn main() {
/// Ohkami::with(AppendServer("ohkami"),
/// "/".GET(|| async {"Hello, append!"})
/// ).howl("localhost:3000").await
/// }
/// ```
#[inline]
pub fn append(value: impl Into<std::borrow::Cow<'static, str>>) -> Append {
Append(value.into())
}
11 changes: 11 additions & 0 deletions ohkami/src/header/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![allow(non_snake_case)]

mod append;
pub use append::append;
pub(crate) use append::Append;

mod setcookie;
pub(crate) use setcookie::*;

mod standard;
pub(crate) use standard::Standard;
231 changes: 231 additions & 0 deletions ohkami/src/header/setcookie.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use std::borrow::Cow;


#[derive(Debug, PartialEq)]
pub enum SameSitePolicy {
Strict,
Lax,
None,
}
impl SameSitePolicy {
const fn as_str(&self) -> &'static str {
match self {
Self::Strict => "Strict",
Self::Lax => "Lax",
Self::None => "None",
}
}
const fn from_bytes(bytes: &[u8]) -> Option<Self> {
match bytes {
b"Strict" => Some(Self::Strict),
b"Lax" => Some(Self::Lax),
b"None" => Some(Self::None),
_ => None
}
}
}

#[derive(Debug, PartialEq)]
pub struct SetCookie<'c> {
pub(crate) Cookie: (&'c str, Cow<'c, str>),
pub(crate) Expires: Option<Cow<'c, str>>,
pub(crate) MaxAge: Option<u64>,
pub(crate) Domain: Option<Cow<'c, str>>,
pub(crate) Path: Option<Cow<'c, str>>,
pub(crate) Secure: Option<bool>,
pub(crate) HttpOnly: Option<bool>,
pub(crate) SameSite: Option<SameSitePolicy>,
}
impl<'c> SetCookie<'c> {
pub fn Cookie(&self) -> (&str, &str) {
let (name, value) = &self.Cookie;
(name, &value)
}
pub fn Expires(&self) -> Option<&str> {
self.Expires.as_deref()
}
pub const fn MaxAge(&self) -> Option<u64> {
self.MaxAge
}
pub fn Domain(&self) -> Option<&str> {
self.Domain.as_deref()
}
pub fn Path(&self) -> Option<&str> {
self.Path.as_deref()
}
pub const fn Secure(&self) -> Option<bool> {
self.Secure
}
pub const fn HttpOnly(&self) -> Option<bool> {
self.HttpOnly
}
/// `Some`: `"Lax" | "None" | "Strict"`
pub const fn SameSite(&self) -> Option<&'static str> {
match &self.SameSite {
None => None,
Some(policy) => Some(policy.as_str())
}
}

pub(crate) fn from_raw(str: &'c str) -> Result<Self, String> {
let mut r = byte_reader::Reader::new(str.as_bytes());

let mut this = {
let name = std::str::from_utf8(r.read_until(b"=")).map_err(|e| format!("Invalid Cookie name: {e}"))?;
r.consume("=").ok_or_else(|| format!("No `=` found in a `Set-Cookie` header value"))?;
let value = ohkami_lib::percent_decode_utf8({
let mut bytes = r.read_until(b"; ");
let len = bytes.len();
if len >= 2 && bytes[0] == b'"' && bytes[len-1] == b'"' {
bytes = &bytes[1..(len-1)]
}
bytes
}).map_err(|e| format!("Invalid Cookie value: {e}"))?;

Self {
Cookie: (name, value),
Expires: None,
MaxAge: None,
Domain: None,
Path: None,
SameSite: None,
Secure: None,
HttpOnly: None,
}
};

while r.consume("; ").is_some() {
let directive = r.read_until(b"; ");
let mut r = byte_reader::Reader::new(directive);
match r.consume_oneof([
"Expires", "Max-Age", "Domain", "Path", "SameSite", "Secure", "HttpOnly"
]) {
Some(0) => {
r.consume("=").ok_or_else(|| format!("Invalid `Expires`: No `=` found"))?;
let value = std::str::from_utf8(r.read_until(b"; ")).map_err(|e| format!("Invalid `Expires`: {e}"))?;
this.Expires = Some(Cow::Borrowed(value))
},
Some(1) => {
r.consume("=").ok_or_else(|| format!("Invalid `Max-Age`: No `=` found"))?;
let value = r.read_until(b"; ").iter().fold(0, |secs, d| 10*secs + (*d - b'0') as u64);
this.MaxAge = Some(value)
}
Some(2) => {
r.consume("=").ok_or_else(|| format!("Invalid `Domain`: No `=` found"))?;
let value = std::str::from_utf8(r.read_until(b"; ")).map_err(|e| format!("Invalid `Domain`: {e}"))?;
this.Domain = Some(Cow::Borrowed(value))
},
Some(3) => {
r.consume("=").ok_or_else(|| format!("Invalid `Path`: No `=` found"))?;
let value = std::str::from_utf8(r.read_until(b"; ")).map_err(|e| format!("Invalid `Path`: {e}"))?;
this.Path = Some(Cow::Borrowed(value))
}
Some(4) => {
r.consume("=").ok_or_else(|| format!("Invalid `SameSite`: No `=` found"))?;
this.SameSite = SameSitePolicy::from_bytes(r.read_until(b"; "));
}
Some(5) => this.Secure = Some(true),
Some(6) => this.HttpOnly = Some(true),
_ => return Err((|| format!("Unkown directive: `{}`", r.remaining().escape_ascii()))())
}
}

Ok(this)
}
}

pub struct SetCookieBuilder(SetCookie<'static>);
impl SetCookieBuilder {
#[inline]
pub(crate) fn new(cookie_name: &'static str, cookie_value: impl Into<Cow<'static, str>>) -> Self {
Self(SetCookie {
Cookie: (cookie_name, cookie_value.into()),
Expires: None, MaxAge: None, Domain: None, Path: None, Secure: None, HttpOnly: None, SameSite: None,
})
}
pub(crate) fn build(self) -> String {
let mut bytes = Vec::new();

let (name, value) = self.0.Cookie; {
bytes.extend_from_slice(name.as_bytes());
bytes.push(b'=');
bytes.extend_from_slice(ohkami_lib::percent_encode(&value).as_bytes());
}
if let Some(Expires) = self.0.Expires {
bytes.extend_from_slice(b"; Expires=");
bytes.extend_from_slice(Expires.as_bytes());
}
if let Some(MaxAge) = self.0.MaxAge {
bytes.extend_from_slice(b"; Max-Age=");
bytes.extend_from_slice(MaxAge.to_string().as_bytes());
}
if let Some(Domain) = self.0.Domain {
bytes.extend_from_slice(b"; Domain=");
bytes.extend_from_slice(Domain.as_bytes());
}
if let Some(Path) = self.0.Path {
bytes.extend_from_slice(b"; Path=");
bytes.extend_from_slice(Path.as_bytes());
}
if let Some(true) = self.0.Secure {
bytes.extend_from_slice(b"; Secure");
}
if let Some(true) = self.0.HttpOnly {
bytes.extend_from_slice(b"; HttpOnly");
}
if let Some(SameSite) = self.0.SameSite {
bytes.extend_from_slice(b"; SameSite=");
bytes.extend_from_slice(SameSite.as_str().as_bytes());
}

unsafe {// SAFETY: All fields and punctuaters is UTF-8
String::from_utf8_unchecked(bytes)
}
}

#[inline]
pub fn Expires(mut self, Expires: impl Into<Cow<'static, str>>) -> Self {
self.0.Expires = Some(Expires.into());
self
}
#[inline]
pub const fn MaxAge(mut self, MaxAge: u64) -> Self {
self.0.MaxAge = Some(MaxAge);
self
}
#[inline]
pub fn Domain(mut self, Domain: impl Into<Cow<'static, str>>) -> Self {
self.0.Domain = Some(Domain.into());
self
}
#[inline]
pub fn Path(mut self, Path: impl Into<Cow<'static, str>>) -> Self {
self.0.Path = Some(Path.into());
self
}
#[inline]
pub const fn Secure(mut self) -> Self {
self.0.Secure = Some(true);
self
}
#[inline]
pub const fn HttpOnly(mut self) -> Self {
self.0.HttpOnly = Some(true);
self
}
#[inline]
pub const fn SameSiteLax(mut self) -> Self {
self.0.SameSite = Some(SameSitePolicy::Lax);
self
}
#[inline]
pub const fn SameSiteNone(mut self) -> Self {
self.0.SameSite = Some(SameSitePolicy::None);
self
}
#[inline]
pub const fn SameSiteStrict(mut self) -> Self {
self.0.SameSite = Some(SameSitePolicy::Strict);
self
}
}
72 changes: 72 additions & 0 deletions ohkami/src/header/standard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
pub(crate) struct Standard<const N: usize, Value> {
index: [u8; N],
values: Vec<Value>,
}

impl<const N: usize, Value> Standard<N, Value> {
const NULL: u8 = u8::MAX;

#[inline]
pub(crate) fn new() -> Self {
Self {
index: [Self::NULL; N],
values: Vec::with_capacity(N / 4)
}
}

#[inline(always)]
pub(crate) unsafe fn get(&self, index: usize) -> Option<&Value> {
match *self.index.get_unchecked(index) {
Self::NULL => None,
index => Some(self.values.get_unchecked(index as usize))
}
}
#[inline(always)]
pub(crate) unsafe fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
match *self.index.get_unchecked(index) {
Self::NULL => None,
index => Some(self.values.get_unchecked_mut(index as usize))
}
}

#[inline(always)]
pub(crate) unsafe fn delete(&mut self, index: usize) {
*self.index.get_unchecked_mut(index) = Self::NULL
}

#[inline(always)]
pub(crate) unsafe fn set(&mut self, index: usize, value: Value) {
*self.index.get_unchecked_mut(index) = self.values.len() as u8;
self.values.push(value);
}

pub(crate) fn iter(&self) -> impl Iterator<Item = (usize, &Value)> {
self.index.iter()
.enumerate()
.filter(|(_, index)| **index != Self::NULL)
.map(|(h, index)| unsafe {(
h, self.values.get_unchecked(*index as usize)
)})
}
}

const _: () = {
impl<const N: usize, Value: PartialEq> PartialEq for Standard<N, Value> {
fn eq(&self, other: &Self) -> bool {
for i in 0..N {
if unsafe {self.get(i)} != unsafe {other.get(i)} {
return false
}
}; true
}
}

impl<const N: usize, Value: Clone> Clone for Standard<N, Value> {
fn clone(&self) -> Self {
Self {
index: self.index.clone(),
values: self.values.clone(),
}
}
}
};
Loading

0 comments on commit 4485100

Please sign in to comment.