Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
27 changes: 25 additions & 2 deletions ohkami/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub(crate) struct Config {
#[cfg(feature="__rt_native__")]
request_bufsize: std::sync::LazyLock<usize>,

#[cfg(feature="__rt_native__")]
keepalive_timeout: std::sync::LazyLock<u64>,

Expand All @@ -9,6 +12,13 @@ pub(crate) struct Config {

impl Config {
#[cfg(feature="__rt_native__")]
#[inline]
pub(crate) fn request_bufsize(&self) -> usize {
*(&*self.request_bufsize)
}

#[cfg(feature="__rt_native__")]
#[inline]
pub(crate) fn keepalive_timeout(&self) -> u64 {
*(&*self.keepalive_timeout)
}
Expand All @@ -23,15 +33,28 @@ impl Config {
impl Config {
pub(super) const fn new() -> Self {
Self {
#[cfg(feature="__rt_native__")]
request_bufsize: std::sync::LazyLock::new(|| std::env::var("OHKAMI_REQUEST_BUFSIZE")
.ok()
.map(|v| v.parse().ok())
.flatten()
.unwrap_or(1 << 11)
),

#[cfg(feature="__rt_native__")]
keepalive_timeout: std::sync::LazyLock::new(|| std::env::var("OHKAMI_KEEPALIVE_TIMEOUT")
.ok().map(|v| v.parse().ok()).flatten()
.ok()
.map(|v| v.parse().ok())
.flatten()
.unwrap_or(42)
),

#[cfg(feature="__rt_native__")]
#[cfg(feature="ws")]
websocket_timeout: std::sync::LazyLock::new(|| std::env::var("OHKAMI_WEBSOCKET_TIMEOUT")
.ok().map(|v| v.parse().ok()).flatten()
.ok()
.map(|v| v.parse().ok())
.flatten()
.unwrap_or(42)
),
}
Expand Down
4 changes: 2 additions & 2 deletions ohkami/src/fang/builtin/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ impl<Payload: for<'de> Deserialize<'de>> JWT<Payload> {
let mut req = unsafe {Pin::new_unchecked(&mut req)};
crate::__rt__::testing::block_on({
req.as_mut().read(&mut req_bytes)
});
}).unwrap();

assert_eq!(
my_jwt.verified(&req.as_ref()).unwrap(),
Expand All @@ -462,7 +462,7 @@ impl<Payload: for<'de> Deserialize<'de>> JWT<Payload> {
let mut req = unsafe {Pin::new_unchecked(&mut req)};
crate::__rt__::testing::block_on({
req.as_mut().read(&mut req_bytes)
});
}).unwrap();

assert_eq!(
my_jwt.verified(&req.as_ref()).unwrap_err().status,
Expand Down
18 changes: 10 additions & 8 deletions ohkami/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,27 +149,29 @@ mod __rt__ {

#[cfg(test)]
pub(crate) mod testing {
pub(crate) fn block_on(future: impl std::future::Future) {
pub(crate) fn block_on<F: Future>(future: F) -> F::Output {
#[cfg(feature="rt_tokio")]
tokio::runtime::Builder::new_current_thread()
return tokio::runtime::Builder::new_current_thread()
.enable_all()
.build().unwrap()
.build()
.unwrap()
.block_on(future);

#[cfg(feature="rt_async-std")]
async_std::task::block_on(future);
return async_std::task::block_on(future);

#[cfg(feature="rt_smol")]
smol::block_on(future);
return smol::block_on(future);

#[cfg(feature="rt_nio")]
nio::runtime::Builder::new_multi_thread()
return nio::runtime::Builder::new_multi_thread()
.enable_all()
.build().unwrap()
.build()
.unwrap()
.block_on(future);

#[cfg(feature="rt_glommio")]
glommio::LocalExecutor::default().run(future);
return glommio::LocalExecutor::default().run(future);
}

pub(crate) const PORT: u16 = {
Expand Down
159 changes: 118 additions & 41 deletions ohkami/src/request/_test_parse.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#![cfg(all(test, feature="__rt_native__", feature="DEBUG"))]

#[allow(unused)]
use super::{Request, Method, BUF_SIZE, Path, QueryParams, Context};
use super::{Request, Method, Path, QueryParams, Context};

use super::{RequestHeader, RequestHeaders};
use std::pin::Pin;
use ohkami_lib::{Slice, CowSlice};

#[test]
fn parse_path() {
Expand All @@ -18,48 +22,47 @@ fn parse_path() {
assert_eq!(&*path, "/");
}

#[test] fn test_parse_request() {
use super::{RequestHeader, RequestHeaders};
use std::pin::Pin;
use ohkami_lib::{Slice, CowSlice};

fn metadataize(input: &str) -> Box<[u8; BUF_SIZE]> {
let mut buf = [0; BUF_SIZE];
buf[..input.len().min(BUF_SIZE)]
.copy_from_slice(&input.as_bytes()[..input.len().min(BUF_SIZE)]);
Box::new(buf)
}
macro_rules! assert_parse {
($case:expr, $expected:expr) => {
let mut case = $case.as_bytes();

macro_rules! assert_parse {
($case:expr, $expected:expr) => {
let mut case = $case.as_bytes();

let mut actual = Request::init(crate::util::IP_0000);
let mut actual = unsafe {Pin::new_unchecked(&mut actual)};

crate::__rt__::testing::block_on({
actual.as_mut().read(&mut case)
});

let expected = $expected;

println!("<assert_parse>");

let __panic_message = format!("\n\
===== actual =====\n\
{actual:#?}\n\
\n\
===== expected =====\n\
{expected:#?}\n\
\n\
");

if actual.get_mut() != &expected {
panic!("{__panic_message}")
}
};
}
let mut actual = Request::init(crate::util::IP_0000);
let mut actual = unsafe {Pin::new_unchecked(&mut actual)};

let result = crate::__rt__::testing::block_on({
actual.as_mut().read(&mut case)
});

assert_eq!(result, Ok(Some(())));

let expected = $expected;

println!("<assert_parse>");

let __panic_message = format!("\n\
===== actual =====\n\
{actual:#?}\n\
\n\
===== expected =====\n\
{expected:#?}\n\
\n\
");

if actual.get_mut() != &expected {
panic!("{__panic_message}")
}
};
}

fn metadataize(input: &str) -> Box<[u8]> {
let buf_size = crate::CONFIG.request_bufsize();
let mut buf = vec![0; buf_size];
buf[..input.len().min(buf_size)]
.copy_from_slice(&input.as_bytes()[..input.len().min(buf_size)]);
buf.into_boxed_slice()
}

#[test] fn test_parse_request() {
const CASE_1: &str = "\
GET /hello.html HTTP/1.1\r\n\
User-Agent: Mozilla/4.0\r\n\
Expand Down Expand Up @@ -165,3 +168,77 @@ fn parse_path() {
});
}
}

#[test] fn test_parse_request_large() {
const LARGE_TOKEN: &str = "Bearer \
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
This-is-a-sample-of-very-long-bearer-token-in-authorizatino-header-\
for-testing-request-parsing-for-requests-that-have-so-large-header-\
values-that-exceed-pre-allocated-BUF_SIZE-.-This-is-required-,-for-\
example-,-when-some-requests-are-expected-to-have-large-JWT-payloads-.\
";

/* `LARGE_TOKEN` itself is already larger than default `request_bufsize` */
assert!(LARGE_TOKEN.len() > (1 << 11));

/* override `request_bufsize` via environment variable */
unsafe {std::env::set_var("OHKAMI_REQUEST_BUFSIZE", "4096")};

let case = format!("\
GET /posts HTTP/1.1\r\n\
Host: localhost\r\n\
Connection: keep-alive\r\n\
Authorization: {LARGE_TOKEN}\r\n\
\r\n\
");

assert_parse!(case, Request {
__buf__: metadataize(&*case),
method: Method::GET,
path: Path::from_literal("/posts"),
query: QueryParams::new(b""),
headers: RequestHeaders::from_iters([
(RequestHeader::Host, "localhost"),
(RequestHeader::Connection, "keep-alive"),
(RequestHeader::Authorization, LARGE_TOKEN),
], None),
payload: None,
context: Context::init(),
ip: crate::util::IP_0000,
});
}
30 changes: 23 additions & 7 deletions ohkami/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ use {
std::borrow::Cow,
};


#[cfg(feature="__rt_native__")]
pub(crate) const BUF_SIZE: usize = 1 << 10;
#[cfg(feature="__rt_native__")]
/// reject requests having `Content-Length` larger than this limit
/// (as `413 Payload Too Large`) for resource security reason
pub(crate) const PAYLOAD_LIMIT: usize = 1 << 32;

/// # HTTP Request
Expand Down Expand Up @@ -97,7 +96,7 @@ pub(crate) const PAYLOAD_LIMIT: usize = 1 << 32;
/// ```
pub struct Request {
#[cfg(feature="__rt_native__")]
pub(super/* for test */) __buf__: Box<[u8; BUF_SIZE]>,
pub(super/* for test */) __buf__: Box<[u8]>,

#[cfg(feature="rt_worker")]
pub(super/* for test */) __url__: std::mem::MaybeUninit<::worker::Url>,
Expand Down Expand Up @@ -189,7 +188,7 @@ impl Request {
ip: crate::util::IP_0000/* tetative */,

#[cfg(feature="__rt_native__")]
__buf__: Box::new([0; BUF_SIZE]),
__buf__: vec![0u8; crate::CONFIG.request_bufsize()].into_boxed_slice(),
#[cfg(feature="rt_worker")]
__url__: std::mem::MaybeUninit::uninit(),
#[cfg(feature="rt_lambda")]
Expand Down Expand Up @@ -264,9 +263,26 @@ impl Request {

while r.consume("\r\n").is_none() {
let key_bytes = r.read_while(|b| b != &b':');
r.consume(": ").ok_or_else(Response::BadRequest)?;
r.consume(": ").ok_or_else(|| {
crate::WARNING!("\
[Request::read] Header key is not found. \
Maybe request buffer size is not enough. \
Try to set `OHKAMI_REQUEST_BUFSIZE` to larger value \
(default: 2048).\
");
Response::BadRequest()
})?;

let value = CowSlice::Ref(Slice::from_bytes(r.read_while(|b| b != &b'\r')));
r.consume("\r\n").ok_or_else(Response::BadRequest)?;
r.consume("\r\n").ok_or_else(|| {
crate::WARNING!("\
[Request::read] Header value is not found. \
Maybe request buffer size is not enough. \
Try to set `OHKAMI_REQUEST_BUFSIZE` to larger value \
(default: 2048).\
");
Response::BadRequest()
})?;

if let Some(key) = RequestHeader::from_bytes(key_bytes) {
self.headers.append(key, value);
Expand Down