Archived
3
0

initial commit

This commit is contained in:
2025-03-04 11:22:53 +01:00
commit 5bb655b7de
32 changed files with 6229 additions and 0 deletions

1130
lib/inferium/src/body.rs Normal file

File diff suppressed because it is too large Load Diff

200
lib/inferium/src/headers.rs Normal file
View File

@@ -0,0 +1,200 @@
use proc::AutoimplHkeys;
/// Accepted valid HTTP header keys
///
/// These can be sent by both the client and the server.
/// The calling implementation shall check and verify the validity of the headers, or may ignore
/// any invalid ones.
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(AutoimplHkeys)]
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub enum HeaderKey {
ACCEPT,
ACCEPT_CHARSET,
ACCEPT_ENCODING,
ACCEPT_LANGUAGE,
ACCEPT_RANGES,
ACCESS_CONTROL_ALLOW_CREDENTIALS,
ACCESS_CONTROL_ALLOW_HEADERS,
ACCESS_CONTROL_ALLOW_METHODS,
ACCESS_CONTROL_ALLOW_ORIGIN,
ACCESS_CONTROL_EXPOSE_HEADERS,
ACCESS_CONTROL_MAX_AGE,
ACCESS_CONTROL_REQUEST_HEADERS,
ACCESS_CONTROL_REQUEST_METHOD,
AGE,
ALLOW,
ALT_SVC,
AUTHORIZATION,
CACHE_CONTROL,
CACHE_STATUS,
CDN_CACHE_CONTROL,
CONNECTION,
CONTENT_DISPOSITION,
CONTENT_ENCODING,
CONTENT_LANGUAGE,
CONTENT_LENGTH,
CONTENT_LOCATION,
CONTENT_RANGE,
CONTENT_SECURITY_POLICY,
CONTENT_SECURITY_POLICY_REPORT_ONLY,
CONTENT_TYPE,
COOKIE,
DNT,
DATE,
ETAG,
EXPECT,
EXPIRES,
FORWARDED,
FROM,
HOST,
IF_MATCH,
IF_MODIFIED_SINCE,
IF_NONE_MATCH,
IF_RANGE,
IF_UNMODIFIED_SINCE,
LAST_MODIFIED,
LINK,
LOCATION,
MAX_FORWARDS,
ORIGIN,
PRAGMA,
PROXY_AUTHENTICATE,
PROXY_AUTHORIZATION,
PUBLIC_KEY_PINS,
PUBLIC_KEY_PINS_REPORT_ONLY,
RANGE,
REFERER,
REFERRER_POLICY,
REFRESH,
RETRY_AFTER,
SEC_WEBSOCKET_ACCEPT,
SEC_WEBSOCKET_EXTENSIONS,
SEC_WEBSOCKET_KEY,
SEC_WEBSOCKET_PROTOCOL,
SEC_WEBSOCKET_VERSION,
SERVER,
SET_COOKIE,
STRICT_TRANSPORT_SECURITY,
TE,
TRAILER,
TRANSFER_ENCODING,
UPGRADE,
UPGRADE_INSECURE_REQUESTS,
USER_AGENT,
VARY,
VIA,
WARNING,
WWW_AUTHENTICATE,
X_CONTENT_TYPE_OPTIONS,
X_DNS_PREFETCH_CONTROL,
X_FRAME_OPTIONS,
X_XSS_PROTECTION,
OTHER(String),
}
/// Type containing all the header values for a given header key.
///
/// Header entries are not omitted if duplicit, but chained to this type.
///
/// Some header keys (such as _cookie_) may require multiple entries. Inferium allows for all
/// header keys to have duplicit entries.
///
/// This type does however provide a way to query the first entry for a given key for easy
/// manipulation.
#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct HeaderValue {
inner: Vec<String>,
}
impl std::str::FromStr for HeaderValue {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !is_valid(s) {
return Err(());
}
Ok(Self {
inner: vec![s.to_string()],
})
}
}
macro_rules! autoimpl_valid_hval {
([$($from:ty),*]) => {
$(autoimpl_valid_hval!($from);)*
};
($from: ty) => {
impl From<$from> for HeaderValue {
fn from(value: $from) -> Self {
Self { inner: vec![value.to_string()] }
}
}
};
}
autoimpl_valid_hval!([usize, u32, u64, u128, i32, i64, i128]);
fn is_valid(v: &str) -> bool {
for ch in v.chars() {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' | ':' | ' ' | ',' | '.' | '=' | '&' | '*' | '/' |
'-' | '!' | '#' | '\'' | '(' | ')' | '+' | ';' | '@' | '[' | ']' | '~' => {},
_ => return false,
}
}
true
}
impl HeaderValue {
#[inline]
pub(crate) fn new(inner: Vec<String>) -> Self {
Self { inner }
}
#[inline]
pub(crate) fn add(&mut self, val: String) {
self.inner.push(val);
}
/// Query the first entry for this header key.
///
/// See the documentation of [`HeaderValue`] for more information.
///
/// # Panics
/// This will panic if this instance of [`HeaderValue`] is in an invalid state, i.e. has no
/// value and hence should not exist as an instance at all.
#[inline]
pub fn get(&self) -> &str {
self.inner.first().expect("invalid header value state")
}
/// Get all the entries for this header key.
///
/// See the documentation of [`HeaderValue`] for more information.
#[inline]
pub fn all(&self) -> &Vec<String> {
&self.inner
}
}
#[cfg(test)]
mod test {
use super::HeaderKey;
#[test]
fn from_cache_control_raw() {
assert_eq!(HeaderKey::from("Cache-Control"), HeaderKey::CACHE_CONTROL);
}
#[test]
fn into_user_agent_raw() {
assert_eq!(String::from(HeaderKey::USER_AGENT), "user-agent");
}
#[test]
fn invalid_raw() {
assert_eq!(HeaderKey::from("cache-agent"), HeaderKey::OTHER("cache-agent".to_string()));
}
}

1013
lib/inferium/src/io.rs Normal file

File diff suppressed because it is too large Load Diff

32
lib/inferium/src/lib.rs Normal file
View File

@@ -0,0 +1,32 @@
pub mod settings;
mod io;
pub use io::{StdInet, StdUnix};
#[cfg(feature = "tokio-net")]
pub use io::TokioInet;
#[cfg(feature = "tokio-unixsocks")]
pub use io::TokioUnix;
#[cfg(feature = "tokio-tls")]
pub use io::TokioRustls;
#[cfg(feature = "testing")]
pub use io::TestSyncStream;
#[cfg(all(feature = "testing", feature = "async"))]
pub use io::TestAsyncStream;
mod proto;
pub use proto::h1;
mod status;
pub use status::Status;
mod headers;
pub use headers::{HeaderKey, HeaderValue};
mod method;
pub use method::Method;
mod path;
pub use path::{HttpPath, HttpPathParseError};
mod body;
pub use body::{Incoming, SizedIn, Outgoing};

View File

@@ -0,0 +1,51 @@
use proc::AutoimplMethods;
#[derive(AutoimplMethods)]
#[allow(non_camel_case_types)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, Clone, Copy)]
pub enum Method {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", <&'static str>::from(*self))
}
}
#[cfg(test)]
mod test {
use crate::method::Method;
#[test]
fn method_from_raw() {
let src = "GET";
assert_eq!(src.parse::<Method>(), Ok(Method::GET));
}
#[test]
fn method_to_raw() {
assert_eq!(<&'static str>::from(Method::GET), "GET");
}
#[test]
fn method_lowercase() {
let src = "get";
assert_eq!(src.parse::<Method>(), Err(()));
}
#[test]
fn method_nonexistent() {
let src = "GOST";
assert_eq!(src.parse::<Method>(), Err(()));
}
}

286
lib/inferium/src/path.rs Normal file
View File

@@ -0,0 +1,286 @@
use std::collections::HashMap;
/// A valid HTTP path with possible GET parameters.
///
/// This struct provides multiple guarantees:
/// - The path is a valid [`String`]
/// - The path's first character is "/"
/// - The path is not empty (the shortest allowed path is "/")
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct HttpPath {
pub(crate) path: String,
pub(crate) params: Option<HashMap<String, String>>,
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub enum HttpPathParseError {
/// The URI could not be converted into a valid [`String`].
InvalidString,
/// The path does not begin with a slash.
NoslashStart,
/// The path contains invalid characters as per
/// [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3).
InvalidPath,
/// The query could not be parsed.
InvalidGetParams,
}
impl std::fmt::Display for HttpPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.serialize_to_string())
}
}
impl std::str::FromStr for HttpPath {
type Err = HttpPathParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.starts_with('/') {
return Err(HttpPathParseError::NoslashStart);
}
let mut value = s.split('?');
let Some(path) = validate_http_path_chars(&mut value.next().unwrap().chars()) else {
return Err(HttpPathParseError::InvalidPath);
};
let params = parse_get_params(value.next())
.map_err(|_| HttpPathParseError::InvalidGetParams)?;
Ok(HttpPath {
path,
params,
})
}
}
impl TryFrom<&[u8]> for HttpPath {
type Error = HttpPathParseError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let value = String::from_utf8(Vec::from(value))
.map_err(|_| HttpPathParseError::InvalidString)?;
if !value.starts_with('/') {
return Err(HttpPathParseError::NoslashStart);
}
let mut value = value.split('?');
let Some(path) = validate_http_path_chars(&mut value.next().unwrap().chars()) else {
return Err(HttpPathParseError::InvalidPath);
};
let params = parse_get_params(value.next())
.map_err(|_| HttpPathParseError::InvalidGetParams)?;
Ok(HttpPath {
path,
params,
})
}
}
fn serialize_params(params: &HashMap<String, String>) -> Vec<u8> {
let mut res = Vec::new();
let mut first = true;
for entry in params.iter() {
if !first {
res.push(b'&');
}
first = false;
res.extend_from_slice(entry.0.as_bytes());
res.push(b'=');
res.extend_from_slice(entry.1.as_bytes());
}
res
}
fn serialize_params_to_string(params: &HashMap<String, String>) -> String {
let mut res = String::new();
let mut first = true;
for entry in params.iter() {
if !first {
res.push('&');
}
first = false;
res.push_str(entry.0);
res.push('=');
res.push_str(entry.1);
}
res
}
impl HttpPath {
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new();
res.extend_from_slice(self.path.as_bytes());
if let Some(ref p) = self.params {
res.push(b'?');
res.extend_from_slice(&serialize_params(p));
}
res
}
fn serialize_to_string(&self) -> String {
let mut res = String::new();
res.push_str(&self.path);
if let Some(ref p) = self.params {
res.push('?');
res.push_str(&serialize_params_to_string(p));
}
res
}
#[inline]
pub fn path(&self) -> &str {
&self.path
}
#[inline]
pub fn params(&self) -> &Option<HashMap<String, String>> {
&self.params
}
}
fn parse_get_params(raw: Option<&str>) ->
Result<Option<HashMap<String, String>>, ()> {
let raw = match raw {
Some(v) => v,
None => return Ok(None),
};
let mut res = HashMap::new();
let mut tmp = String::new();
let mut key = None;
for cur in raw.chars() {
parse_get_params_handle_char(
&mut tmp,
cur,
&mut key,
&mut res,
)?;
}
if let (Some(k), 1..) = (key, tmp.len()) {
res.insert(k.to_string(), tmp);
};
Ok(Some(res))
}
#[inline]
fn parse_get_params_handle_char(
tmp: &mut String,
cur: char,
key: &mut Option<String>,
res: &mut HashMap<String, String>,
) -> Result<(), ()> {
println!("cur: {cur}, key: {key:?}");
match (cur, &key) {
('&', Some(k)) => {
res.insert(k.to_string(), std::mem::take(tmp));
*key = None;
Ok(())
},
('=', None) => {
*key = Some(std::mem::take(tmp));
Ok(())
},
('&', None) => Err(()),
('=', Some(_)) => Err(()),
(c, _) => {
tmp.push(c);
Ok(())
}
}
}
fn validate_http_path_chars<I: Iterator<Item = char>>(iter: &mut I) -> Option<String> {
let mut res = String::with_capacity(iter.size_hint().1?);
for ch in iter {
match ch {
'a'..='z' |
'A'..='Z' |
'0'..='9' |
'/' |
'.' | '-' | '_' | '~' | '!' | '$' | '&' | '\'' |
'(' | ')' | '*' | '+' | ',' | ';' | '=' | ':' | '@' => res.push(ch),
_ => return None,
}
}
Some(res)
}
#[cfg(test)]
mod parse_path {
use std::collections::HashMap;
use super::{HttpPath, HttpPathParseError};
macro_rules! test {
($name: ident, $raw_uri: literal, ok path $path: literal) => {
test!($name, $raw_uri, Ok(HttpPath { path: $path.to_string(), params: None }));
};
(
$name: ident, $raw_uri: literal,
ok path $path: literal params [$($key: ident : $value: literal),* $(,)?]
) => {
test!($name, $raw_uri, Ok(HttpPath {
path: $path.to_string(),
params: Some(HashMap::from([$((stringify!($key).to_string(), $value.to_string())),*])),
}));
};
($name: ident, $raw_uri: literal, $res: expr) => {
#[test]
fn $name() {
let raw = $raw_uri.as_bytes().to_vec();
let raw = raw.as_slice();
assert_eq!(HttpPath::try_from(raw), $res);
}
};
}
test!(valid_path_noparams, "/hello/world", ok path "/hello/world");
test!(invalid_path_noslash, "hello/world", Err(HttpPathParseError::NoslashStart));
test!(valid_singleparam, "/hello/world?hello=world", ok path "/hello/world" params [
hello: "world"
]);
test!(valid_multiparam, "/hello/world?hello=world&how=areyou", ok path "/hello/world" params [
hello: "world",
how: "areyou",
]);
test!(invalid_params, "/hello/world?&", Err(HttpPathParseError::InvalidGetParams));
test!(path_invalid_char, "/hč", Err(HttpPathParseError::InvalidPath));
}
#[cfg(test)]
mod serialize_path {
use std::collections::HashMap;
use super::HttpPath;
macro_rules! test {
(@uri $path: literal, [$(,)?]) => {
HttpPath {
path: $path.to_string(),
params: None,
}
};
(@uri $path: literal, [$($qk: ident : $qv: literal),+$(,)?]) => {
HttpPath {
path: $path.to_string(),
params: Some(HashMap::from([$((stringify!($qk).to_string(), $qv.to_string())),*])),
}
};
($name: ident, $path: literal [$($qk: ident : $qv: literal),*$(,)?], $target: literal) => {
#[test]
fn $name() {
let src = test!(@uri $path, [$($qk:$qv),*]);
assert_eq!(src.serialize(), $target);
}
};
}
test!(simple_slash, "/"[], b"/");
test!(slash_with_singleparam_query, "/"[
action: "none",
], b"/?action=none");
test!(path_with_singleparam_query, "/hello/world"[
hello: "world",
], b"/hello/world?hello=world");
}

View File

@@ -0,0 +1,666 @@
use crate::{
body::{ChunkedIn, Incoming, Outgoing, SizedIn, SizedOut},
headers::HeaderKey, io::{self, PrependableStream, Receive, Send}
};
use super::{
head::{BadRequest, BadResponse, RequestHead, ResponseHead},
stream_handler::{
ExpectedBody,
StreamHandler,
StreamHandlerReceiveError,
StreamHandlerSendError
}
};
#[cfg(feature = "async")]
use {crate::io::{AsyncSend, AsyncReceive}, std::future::Future};
#[derive(Debug)]
enum OutgoingBody {
Sized(usize),
None,
}
/// Synchronous HTTP client handler.
///
/// Calling I/O operations on this will block. Refer to [`AsyncClient`] (with enabled `async`
/// feature) for the asynchronous equivalent.
///
/// You can send request headers and receive responses from the server. Sending requests with body
/// is supported, but relies on the caller to send the body correctly - inferium does not force you
/// to respect the HTTP protocol completely.
///
/// This structure owns the underlying stream and can send multiple requests and responses to the
/// other endpoint no matter the protocol (inferium will allow you to send multiple requests in a
/// single HTTP/1.0 connection if you wish to).
#[derive(Debug)]
pub struct SyncClient<T: Send + Receive> {
handler: StreamHandler<T>,
should_send_body: OutgoingBody,
}
/// Synchronous HTTP server handler.
///
/// Calling I/O operations on this will block. Refer to [`AsyncServer`] (with enabled `async`
/// feature) for the asynchronous equivalent.
///
/// You can receive requests and send response headers to the client. When sending responses with
/// body, the caller must send the body correctly - inferium does not force you to respect the HTTP
/// protocol completely.
///
/// This structure owns the underlying stream and can send multiple requests and responses to the
/// other endpoint no matter the protocol (inferium will allow you to send multiple responses in a
/// single HTTP/1.0 connection if you wish to).
#[derive(Debug)]
pub struct SyncServer<T: Send + Receive> {
handler: StreamHandler<T>,
should_send_body: OutgoingBody,
}
/// Asynchronous HTTP client handler.
///
/// Calling I/O operations on this will not block and return a future. Refer to [`SyncClient`]
/// for the synchronous equivalent.
///
/// You can send request headers and receive responses from the server. Sending requests with body
/// is supported, but relies on the caller to send the body correctly - inferium does not force you
/// to respect the HTTP protocol completely.
///
/// This structure owns the underlying stream and can send multiple requests and responses to the
/// other endpoint no matter the protocol (inferium will allow you to send multiple requests in a
/// single HTTP/1.0 connection if you wish to).
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncClient<T: AsyncSend + AsyncReceive> {
handler: StreamHandler<T>,
should_send_body: OutgoingBody,
}
/// Asynchronous HTTP server handler.
///
/// Calling I/O operations on this will not block and return a future. Refer to [`SyncServer`]
/// for the synchronous equivalent.
///
/// You can receive requests and send response headers to the client. When sending responses with
/// body, the caller must send the body correctly - inferium does not force you to respect the HTTP
/// protocol completely.
///
/// This structure owns the underlying stream and can send multiple requests and responses to the
/// other endpoint no matter the protocol (inferium will allow you to send multiple responses in a
/// single HTTP/1.0 connection if you wish to).
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncServer<T: AsyncSend + AsyncReceive> {
handler: StreamHandler<T>,
should_send_body: OutgoingBody,
}
macro_rules! autoimpl_new {
($for: ident [$(#$attr: tt $of_type: ty),*$(,)?]) => {
$(#$attr impl $for<$of_type> {
pub fn new(stream: $of_type) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
})*
};
($for: ident [$($of_type: ty),*$(,)?]) => {
$(impl $for<$of_type> {
pub fn new(stream: $of_type) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
})*
};
($($for: ident $impl_list: tt),+$(,)?) => {
$(autoimpl_new!($for $impl_list);)+
};
}
autoimpl_new! {
// All supported synchronous I/O streams
SyncClient [ io::StdInet, io::StdUnix ],
SyncServer [ io::StdInet, io::StdUnix ],
// All supported asynchronous I/O streams
AsyncClient [
#[cfg(all(feature = "async", feature = "tokio-net"))] io::TokioInet,
#[cfg(all(feature = "async", feature = "tokio-unixsocks"))] io::TokioUnix,
#[cfg(all(feature = "async", feature = "tokio-tls"))] io::TokioRustls,
],
AsyncServer [
#[cfg(all(feature = "async", feature = "tokio-net"))] io::TokioInet,
#[cfg(all(feature = "async", feature = "tokio-unixsocks"))] io::TokioUnix,
#[cfg(all(feature = "async", feature = "tokio-tls"))] io::TokioRustls,
],
}
#[cfg(any(feature = "testing", test))]
impl<'a, const CHUNK_SIZE: usize> SyncClient<io::TestSyncStream<'a, CHUNK_SIZE>> {
pub fn new(stream: io::TestSyncStream<'a, CHUNK_SIZE>) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
}
#[cfg(all(any(feature = "testing", test), feature = "async"))]
impl<'a, const CHUNK_SIZE: usize> AsyncClient<io::TestAsyncStream<'a, CHUNK_SIZE>> {
pub fn new(stream: io::TestAsyncStream<'a, CHUNK_SIZE>) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
}
#[cfg(any(feature = "testing", test))]
impl<'a, const CHUNK_SIZE: usize> SyncServer<io::TestSyncStream<'a, CHUNK_SIZE>> {
pub fn new(stream: io::TestSyncStream<'a, CHUNK_SIZE>) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
}
#[cfg(all(any(feature = "testing", test), feature = "async"))]
impl<'a, const CHUNK_SIZE: usize> AsyncServer<io::TestAsyncStream<'a, CHUNK_SIZE>> {
pub fn new(stream: io::TestAsyncStream<'a, CHUNK_SIZE>) -> Self {
Self { handler: StreamHandler::new(stream), should_send_body: OutgoingBody::None }
}
}
#[derive(Debug)]
pub enum ClientSendError {
/// This is a usage error.
///
/// It is returned when the caller fails to either send the required body or receive the
/// advertised body from the other endpoint, and thus the protocol state is violated.
StateViolated,
InvalidContentLength,
IO(std::io::Error),
}
#[derive(Debug)]
pub enum ServerSendError {
/// This is a usage error.
///
/// It is returned when the caller fails to either send the required body or receive the
/// advertised body from the other endpoint, and thus the protocol state is violated.
StateViolated,
InvalidContentLength,
IO(std::io::Error),
}
impl From<StreamHandlerSendError> for ClientSendError {
fn from(value: StreamHandlerSendError) -> Self {
match value {
StreamHandlerSendError::RequiresBodyPolling => ClientSendError::StateViolated,
StreamHandlerSendError::IO(e) => ClientSendError::IO(e),
}
}
}
impl From<StreamHandlerSendError> for ServerSendError {
fn from(value: StreamHandlerSendError) -> Self {
match value {
StreamHandlerSendError::RequiresBodyPolling => ServerSendError::StateViolated,
StreamHandlerSendError::IO(e) => ServerSendError::IO(e),
}
}
}
#[derive(Debug)]
pub enum ClientReceiveError {
/// This is a usage error.
///
/// It is returned when the caller fails to either send the required body or receive the
/// advertised body from the other endpoint, and thus the protocol state is violated.
StateViolated,
/// If the response head being received is too large to fit into the pre-allocated buffer.
HeadTooLarge,
/// The response head could not be parsed. If additional details are known - they will be
/// contained in the inner value.
InvalidHead(Option<BadResponse>),
IO(std::io::Error),
}
#[derive(Debug)]
pub enum ServerReceiveError {
/// This is a usage error.
///
/// It is returned when the caller fails to either send the required body or receive the
/// advertised body from the other endpoint, and thus the protocol state is violated.
StateViolated,
/// If the request head being received is too large to fit into the pre-allocated buffer.
HeadTooLarge,
/// The request head could not be parsed. If additional details are known - they will be
/// contained in the inner value.
InvalidHead(Option<BadRequest>),
IO(std::io::Error),
}
impl From<StreamHandlerReceiveError<BadResponse>> for ClientReceiveError {
fn from(value: StreamHandlerReceiveError<BadResponse>) -> Self {
match value {
StreamHandlerReceiveError::IO(e) => Self::IO(e),
StreamHandlerReceiveError::NoData => Self::InvalidHead(None),
StreamHandlerReceiveError::ParsingError(e) => Self::InvalidHead(Some(e)),
StreamHandlerReceiveError::HeaderTooLarge => Self::HeadTooLarge,
StreamHandlerReceiveError::RequiresBodyPolling => Self::StateViolated,
StreamHandlerReceiveError::InvalidExpectedBody => Self::InvalidHead(None),
}
}
}
impl From<StreamHandlerReceiveError<BadRequest>> for ServerReceiveError {
fn from(value: StreamHandlerReceiveError<BadRequest>) -> Self {
match value {
StreamHandlerReceiveError::IO(e) => Self::IO(e),
StreamHandlerReceiveError::NoData => Self::InvalidHead(None),
StreamHandlerReceiveError::ParsingError(e) => Self::InvalidHead(Some(e)),
StreamHandlerReceiveError::HeaderTooLarge => Self::HeadTooLarge,
StreamHandlerReceiveError::RequiresBodyPolling => Self::StateViolated,
StreamHandlerReceiveError::InvalidExpectedBody => Self::InvalidHead(None),
}
}
}
/// A generic error possibly returned when sending a body.
#[derive(Debug)]
pub enum BodySendError {
/// The real body input length to send did not match the body length advertised in the headers.
LengthDiscrepancy,
IO(std::io::Error),
}
impl TryFrom<BodySendError> for ServerSendError {
type Error = ();
fn try_from(value: BodySendError) -> Result<Self, Self::Error> {
match value {
BodySendError::LengthDiscrepancy => Err(()),
BodySendError::IO(e) => Ok(Self::IO(e)),
}
}
}
impl TryFrom<BodySendError> for ClientSendError {
type Error = ();
fn try_from(value: BodySendError) -> Result<Self, Self::Error> {
match value {
BodySendError::LengthDiscrepancy => Err(()),
BodySendError::IO(e) => Ok(Self::IO(e)),
}
}
}
impl From<crate::body::SendError> for BodySendError {
fn from(value: crate::body::SendError) -> Self {
match value {
crate::body::SendError::LengthDiscrepancy => Self::LengthDiscrepancy,
crate::body::SendError::IO(e) => Self::IO(e),
}
}
}
impl From<std::io::Error> for BodySendError {
fn from(value: std::io::Error) -> Self {
Self::IO(value)
}
}
/// A received response with possible incoming body.
#[derive(Debug, PartialEq)]
pub enum Response<'a, T> {
/// The response does not advertise an incoming body in any known way.
HeadersOnly(ResponseHead),
/// The response advertised a body using the `content-length` header.
WithSizedBody((ResponseHead, Incoming<'a, SizedIn<'a, PrependableStream<T>>>)),
/// The response advertised a chunked body using the `transfer-encoding` header.
WithChunkedBody((ResponseHead, Incoming<'a, ChunkedIn<'a, PrependableStream<T>>>)),
}
/// A received request with possible incoming body.
pub enum Request<'a, T> {
/// The request does not advertise an incoming body in any known way.
HeadersOnly(RequestHead),
/// The request advertised a body using the `content-length` header.
WithSizedBody((RequestHead, Incoming<'a, SizedIn<'a, PrependableStream<T>>>)),
/// The request advertised a chunked body using the `transfer-encoding` header.
WithChunkedBody((RequestHead, Incoming<'a, ChunkedIn<'a, PrependableStream<T>>>)),
}
fn get_outgoing_req_content_length(head: &RequestHead) -> Result<Option<usize>, ClientSendError> {
let Some(l) = head.headers.get(&HeaderKey::CONTENT_LENGTH) else {
return Ok(None);
};
Ok(Some(l.get().parse().map_err(|_| ClientSendError::InvalidContentLength)?))
}
fn get_outgoing_res_content_length(head: &ResponseHead) -> Result<Option<usize>, ServerSendError> {
let Some(l) = head.headers.get(&HeaderKey::CONTENT_LENGTH) else {
return Ok(None);
};
Ok(Some(l.get().parse().map_err(|_| ServerSendError::InvalidContentLength)?))
}
fn send_sync<'a, D: Iterator<Item = &'a [u8]>, T: Send>(
data_source: &'a mut D,
stream: &'a mut PrependableStream<T>,
length: usize,
) -> Result<(), crate::body::SendError> {
Outgoing::new(SizedOut::new(data_source, stream, length)).send_all()
}
#[cfg(feature = "async")]
async fn send_async<'a, F: Future<Output = &'a [u8]>, D: Iterator<Item = F>, T: AsyncSend>(
data_source: &'a mut D,
stream: &'a mut PrependableStream<T>,
length: usize,
) -> Result<(), crate::body::SendError> {
Outgoing::new_async(SizedOut::new(data_source, stream, length)).send_all_async().await
}
fn send_sync_slc<T: Send>(
data_source: &[u8], stream: &mut PrependableStream<T>
) -> Result<(), BodySendError> {
let mut ptr = 0;
while ptr < data_source.len() {
ptr += stream.send(&data_source[ptr..])?;
}
Ok(())
}
#[cfg(feature = "async")]
async fn send_async_slc<T: AsyncSend>(
data_source: &[u8], stream: &mut PrependableStream<T>
) -> Result<(), BodySendError> {
let mut ptr = 0;
while ptr < data_source.len() {
ptr += stream.send(&data_source[ptr..]).await?;
}
Ok(())
}
impl<T: Send + Receive> SyncClient<T> {
/// Send an HTTP request to the other endpoint (ideally a server).
///
/// If the `content-length` header is set, this will require you (the caller) to send a body
/// with the advertised content length before sending the next request.
pub fn send_request(&mut self, req_head: &RequestHead) -> Result<(), ClientSendError> {
match self.should_send_body {
OutgoingBody::None => {},
_ => return Err(ClientSendError::StateViolated),
}
if let Some(l) = get_outgoing_req_content_length(req_head)? {
self.should_send_body = OutgoingBody::Sized(l);
}
Ok(self.handler.send_request(req_head)?)
}
/// Send the body to the other endpoint.
///
/// Note that this will only consume the iterator until the desired size is sent.
///
/// If no body should be sent (no prior request has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a loaded source (not an iterator) - refer to
/// [`SyncClient::send_body_bytes`].
pub fn send_body<'a, D: Iterator<Item = &'a [u8]>>(&'a mut self, data_source: &'a mut D) ->
Result<(), BodySendError> {
match self.should_send_body {
OutgoingBody::Sized(l) => send_sync(data_source, &mut self.handler.inner, l)?,
OutgoingBody::None => {},
}
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Send the body to the other endpoint.
///
/// If no body should be sent (no prior request has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a streamed source (an iterator) - refer to
/// [`SyncClient::send_body`].
pub fn send_body_bytes(&mut self, data_source: &[u8]) -> Result<(), BodySendError> {
let advertised_length = match self.should_send_body {
OutgoingBody::Sized(l) => l,
OutgoingBody::None => return Ok(()),
};
if advertised_length != data_source.len() {
return Err(BodySendError::LengthDiscrepancy);
}
send_sync_slc(data_source, &mut self.handler.inner)?;
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Attempt to receive an HTTP response from the other endpoint (ideally a server).
///
/// If a body is advertised by the other endpoint, you (the caller) will then have to poll the
/// returned body object until the expected content length is consumed before receiving another
/// response from the endpoint.
pub fn receive_response(&mut self) -> Result<Response<T>, ClientReceiveError> {
let received = self.handler.receive_response()?;
Ok(match received.1 {
None => Response::HeadersOnly(received.0),
Some(ExpectedBody::Sized(b)) => Response::WithSizedBody((received.0, b)),
Some(ExpectedBody::Chunked(b)) => Response::WithChunkedBody((received.0, b)),
})
}
}
impl<T: Send + Receive> SyncServer<T> {
/// Send an HTTP response to the other endpoint (ideally a client).
///
/// If the `content-length` header is set, this will require you (the caller) to send a body
/// with the advertised content length before sending the next response.
pub fn send_response(&mut self, req_head: &ResponseHead) -> Result<(), ServerSendError> {
match self.should_send_body {
OutgoingBody::None => {},
_ => return Err(ServerSendError::StateViolated),
}
if let Some(l) = get_outgoing_res_content_length(req_head)? {
self.should_send_body = OutgoingBody::Sized(l);
}
Ok(self.handler.send_response(req_head)?)
}
/// Send the body to the other endpoint.
///
/// Note that this will only consume the iterator until the desired size is sent.
///
/// If no body should be sent (no prior response has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a loaded source (not an iterator) - refer to
/// [`SyncServer::send_body_bytes`].
pub fn send_body<'a, D: Iterator<Item = &'a [u8]>>(&'a mut self, data_source: &'a mut D) ->
Result<(), BodySendError> {
match self.should_send_body {
OutgoingBody::Sized(l) => send_sync(data_source, &mut self.handler.inner, l)?,
OutgoingBody::None => (),
}
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Send the body to the other endpoint.
///
/// If no body should be sent (no prior response has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a streamed source (an iterator) - refer to
/// [`SyncServer::send_body`].
pub fn send_body_bytes(&mut self, data_source: &[u8]) -> Result<(), BodySendError> {
let advertised_length = match self.should_send_body {
OutgoingBody::Sized(l) => l,
OutgoingBody::None => return Ok(()),
};
if advertised_length != data_source.len() {
return Err(BodySendError::LengthDiscrepancy);
}
send_sync_slc(data_source, &mut self.handler.inner)?;
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Attempt to receive an HTTP request from the other endpoint (ideally a client).
///
/// If a body is advertised by the other endpoint, you (the caller) will then have to poll the
/// returned body object until the expected content length is consumed before receiving another
/// request from the endpoint.
pub fn receive_request(&mut self) -> Result<Request<T>, ServerReceiveError> {
let received = self.handler.receive_request()?;
Ok(match received.1 {
None => Request::HeadersOnly(received.0),
Some(ExpectedBody::Sized(b)) => Request::WithSizedBody((received.0, b)),
Some(ExpectedBody::Chunked(b)) => Request::WithChunkedBody((received.0, b)),
})
}
}
#[cfg(feature = "async")]
impl<T: AsyncSend + AsyncReceive> AsyncClient<T> {
/// Send an HTTP request to the other endpoint (ideally a server).
///
/// If the `content-length` header is set, this will require you (the caller) to send a body
/// with the advertised content length before sending the next request.
pub async fn send_request(&mut self, req_head: &RequestHead) -> Result<(), ClientSendError> {
match self.should_send_body {
OutgoingBody::None => {},
_ => return Err(ClientSendError::StateViolated),
}
if let Some(l) = get_outgoing_req_content_length(req_head)? {
self.should_send_body = OutgoingBody::Sized(l);
}
Ok(self.handler.send_request_async(req_head).await?)
}
/// Send the body to the other endpoint.
///
/// Note that this will only consume the iterator until the desired size is sent.
///
/// If no body should be sent (no prior request has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a loaded source (not an iterator) - refer to
/// [`AsyncClient::send_body_bytes`].
pub async fn send_body<'a, F: Future<Output = &'a [u8]>, D: Iterator<Item = F>>(
&'a mut self, data_source: &'a mut D
) -> Result<(), BodySendError> {
match self.should_send_body {
OutgoingBody::Sized(l) => send_async(data_source, &mut self.handler.inner, l).await?,
OutgoingBody::None => (),
}
Ok(())
}
/// Send the body to the other endpoint.
///
/// If no body should be sent (no prior request has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a streamed source (an iterator) - refer to
/// [`AsyncClient::send_body`].
pub async fn send_body_bytes(&mut self, data_source: &[u8]) -> Result<(), BodySendError> {
let advertised_length = match self.should_send_body {
OutgoingBody::Sized(l) => l,
OutgoingBody::None => return Ok(()),
};
if advertised_length != data_source.len() {
return Err(BodySendError::LengthDiscrepancy);
}
send_async_slc(data_source, &mut self.handler.inner).await?;
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Attempt to receive an HTTP response from the other endpoint (ideally a server).
///
/// If a body is advertised by the other endpoint, you (the caller) will then have to poll the
/// returned body object until the expected content length is consumed before receiving another
/// response from the endpoint.
pub async fn receive_response(&mut self) -> Result<Response<T>, ClientReceiveError> {
let received = self.handler.receive_response_async().await?;
Ok(match received.1 {
None => Response::HeadersOnly(received.0),
Some(ExpectedBody::Sized(b)) => Response::WithSizedBody((received.0, b)),
Some(ExpectedBody::Chunked(b)) => Response::WithChunkedBody((received.0, b)),
})
}
}
#[cfg(feature = "async")]
impl<T: AsyncSend + AsyncReceive> AsyncServer<T> {
/// Send an HTTP response to the other endpoint (ideally a client).
///
/// If the `content-length` header is set, this will require you (the caller) to send a body
/// with the advertised content length before sending the next response.
pub async fn send_response(&mut self, req_head: &ResponseHead) -> Result<(), ServerSendError> {
match self.should_send_body {
OutgoingBody::None => {},
_ => return Err(ServerSendError::StateViolated),
}
if let Some(l) = get_outgoing_res_content_length(req_head)? {
self.should_send_body = OutgoingBody::Sized(l);
}
Ok(self.handler.send_response_async(req_head).await?)
}
/// Send the body to the other endpoint.
///
/// Note that this will only consume the iterator until the desired size is sent.
///
/// If no body should be sent (no prior response has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a loaded source (not an iterator) - refer to
/// [`AsyncServer::send_body_bytes`].
pub async fn send_body<'a, F: Future<Output = &'a [u8]>, D: Iterator<Item = F>>(
&'a mut self, data_source: &'a mut D
) -> Result<(), BodySendError> {
match self.should_send_body {
OutgoingBody::Sized(l) => send_async(data_source, &mut self.handler.inner, l).await?,
OutgoingBody::None => (),
}
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Send the body to the other endpoint.
///
/// If no body should be sent (no prior response has advertised a successive body), this will
/// immediatelly return with an empty Ok - will not send anything nor consume anything from the
/// iterator.
///
/// If you wish to send a body from a streamed source (an iterator) - refer to
/// [`AsyncServer::send_body`].
pub async fn send_body_bytes(&mut self, data_source: &[u8]) -> Result<(), BodySendError> {
let advertised_length = match self.should_send_body {
OutgoingBody::Sized(l) => l,
OutgoingBody::None => return Ok(()),
};
if advertised_length != data_source.len() {
return Err(BodySendError::LengthDiscrepancy);
}
send_async_slc(data_source, &mut self.handler.inner).await?;
self.should_send_body = OutgoingBody::None;
Ok(())
}
/// Attempt to receive an HTTP request from the other endpoint (ideally a client).
///
/// If a body is advertised by the other endpoint, you (the caller) will then have to poll the
/// returned body object until the expected content length is consumed before receiving another
/// request from the endpoint.
pub async fn receive_request(&mut self) -> Result<Request<T>, ServerReceiveError> {
let received = self.handler.receive_request_async().await?;
Ok(match received.1 {
None => Request::HeadersOnly(received.0),
Some(ExpectedBody::Sized(b)) => Request::WithSizedBody((received.0, b)),
Some(ExpectedBody::Chunked(b)) => Request::WithChunkedBody((received.0, b)),
})
}
}

View File

@@ -0,0 +1,618 @@
use std::collections::HashMap;
use crate::{
headers::{HeaderKey, HeaderValue},
method::Method,
path::{HttpPath, HttpPathParseError},
status::Status
};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ProtocolVariant {
HTTP1_0,
HTTP1_1,
}
impl std::fmt::Display for ProtocolVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HTTP1_0 => write!(f, "HTTP/1.0"),
Self::HTTP1_1 => write!(f, "HTTP/1.1"),
}
}
}
impl ProtocolVariant {
pub(crate) fn text(&self) -> &'static [u8] {
match self {
Self::HTTP1_1 => b"HTTP/1.1",
Self::HTTP1_0 => b"HTTP/1.0",
}
}
}
impl TryFrom<&[u8]> for ProtocolVariant {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"HTTP/1.0" => Ok(Self::HTTP1_0),
b"HTTP/1.1" => Ok(Self::HTTP1_1),
_ => Err(()),
}
}
}
fn serialize_header(header: (&HeaderKey, &HeaderValue)) -> Vec<u8> {
let mut res = Vec::new();
for value in header.1.all() {
res.extend_from_slice(header.0.text());
res.extend_from_slice(b": ");
res.extend_from_slice(value.as_bytes());
res.extend_from_slice(b"\r\n");
}
res
}
fn format_header(header: (&HeaderKey, &HeaderValue)) -> String {
let mut res = String::new();
for value in header.1.all() {
res.push_str(&format!("{}: {}", header.0, value));
}
res
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct RequestHead {
pub(crate) method: Method,
pub(crate) path: HttpPath,
pub(crate) protocol: ProtocolVariant,
pub(crate) headers: HashMap<HeaderKey, HeaderValue>,
}
impl RequestHead {
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
#[inline]
pub fn uri(&self) -> &HttpPath {
&self.path
}
#[inline]
pub fn proto(&self) -> &ProtocolVariant {
&self.protocol
}
#[inline]
pub fn headers(&self) -> &HashMap<HeaderKey, HeaderValue> {
&self.headers
}
}
impl std::fmt::Display for RequestHead {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} {} {}", self.method, self.path, self.protocol)?;
for entry in self.headers.iter() {
writeln!(f, "{}", format_header(entry))?;
}
Ok(())
}
}
impl RequestHead {
pub fn new(
method: Method,
path: HttpPath,
protocol: ProtocolVariant,
headers: HashMap<HeaderKey, HeaderValue>,
) -> Self {
Self { method, path, protocol, headers }
}
}
impl RequestHead {
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new();
res.extend_from_slice(self.method.text());
res.push(b' ');
res.extend_from_slice(&self.path.serialize());
res.push(b' ');
res.extend_from_slice(self.protocol.text());
res.extend_from_slice(b"\r\n");
for entry in self.headers.iter() {
res.extend_from_slice(&serialize_header(entry));
}
res.extend_from_slice(b"\r\n");
res
}
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub enum BadRequest {
HeadLine,
InvalidPath(HttpPathParseError),
InvalidMethod,
InvalidProtocol,
InvalidHeaders,
}
impl From<HttpPathParseError> for BadRequest {
fn from(value: HttpPathParseError) -> Self {
Self::InvalidPath(value)
}
}
pub(super) fn parse_request_head(raw: &[u8]) -> Result<RequestHead, BadRequest> {
const PAT_SPACE: &[u8] = b" ";
const JUT_SPACE: &[usize] = &[0];
const PAT_CRLF: &[u8] = b"\r\n";
const JUT_CRLF: &[usize] = &[0, 0];
// Method
let Some(meth_end) = try_find(raw, PAT_SPACE, JUT_SPACE, &0) else {
return Err(BadRequest::HeadLine);
};
let meth: Method = std::str::from_utf8(&raw[..meth_end])
.map_err(|_| BadRequest::InvalidMethod)?.parse().map_err(|_| BadRequest::InvalidMethod)?;
// Path
let path_start = meth_end + PAT_SPACE.len();
let Some(path_end) = try_find(raw, PAT_SPACE, JUT_SPACE, &path_start) else {
return Err(BadRequest::HeadLine);
};
let path = HttpPath::try_from(&raw[path_start..path_end])?;
// Protocol
let proto_start = path_end + PAT_SPACE.len();
let Some(proto_end) = try_find(raw, PAT_CRLF, JUT_CRLF, &proto_start) else {
return Err(BadRequest::HeadLine);
};
let proto: ProtocolVariant = (&raw[proto_start..proto_end]).try_into()
.map_err(|_| BadRequest::InvalidProtocol)?;
// Headers
let mut res = HashMap::new();
let mut header_start = proto_end + PAT_CRLF.len();
loop {
let Some(header_end) = try_find(raw, PAT_CRLF, JUT_CRLF, &header_start) else {
return Err(BadRequest::InvalidHeaders);
};
if header_start == header_end {
break;
}
parse_header(&raw[header_start..header_end], &mut res)
.map_err(|_| BadRequest::InvalidHeaders)?;
header_start = header_end + PAT_CRLF.len();
}
Ok(RequestHead {
method: meth,
path,
protocol: proto,
headers: res
})
}
#[derive(Debug, PartialEq)]
pub struct ResponseHead {
pub(crate) status: Status,
pub(crate) protocol: ProtocolVariant,
pub(crate) headers: HashMap<HeaderKey, HeaderValue>,
}
impl ResponseHead {
#[inline]
pub fn status(&self) -> &Status {
&self.status
}
#[inline]
pub fn proto(&self) -> &ProtocolVariant {
&self.protocol
}
#[inline]
pub fn headers(&self) -> &HashMap<HeaderKey, HeaderValue> {
&self.headers
}
}
impl ResponseHead {
pub fn new(
status: Status, protocol: ProtocolVariant, headers: HashMap<HeaderKey, HeaderValue>
) -> Self {
Self { status, protocol, headers }
}
}
impl std::fmt::Display for ResponseHead {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} {}", self.protocol, self.status)?;
for entry in self.headers.iter() {
writeln!(f, "{}", format_header(entry))?;
}
Ok(())
}
}
impl ResponseHead {
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new();
res.extend_from_slice(self.protocol.text());
res.push(b' ');
res.extend_from_slice(self.status.num());
res.push(b' ');
res.extend_from_slice(self.status.text());
res.extend_from_slice(b"\r\n");
for header in self.headers.iter() {
res.extend_from_slice(&serialize_header(header));
}
res.extend_from_slice(b"\r\n");
res
}
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub enum BadResponse {
/// The headline (parseable example: `HTTP/1.1 200 OK`) could not be parsed.
HeadLine,
/// The response status code is unknown.
InvalidStatusCode,
/// The advertised protocol is not supported.
InvalidProtocol,
/// Some headers are invalid. Either they have unknown keys or invalid syntax.
InvalidHeaders,
}
pub(super) fn parse_response_head(raw: &[u8]) -> Result<ResponseHead, BadResponse> {
const PAT_SPACE: &[u8] = b" ";
const JUT_SPACE: &[usize] = &[0];
const PAT_CRLF: &[u8] = b"\r\n";
const JUT_CRLF: &[usize] = &[0, 0];
// Protocol
let Some(proto_end) = try_find(raw, PAT_SPACE, JUT_SPACE, &0) else {
return Err(BadResponse::HeadLine);
};
let proto: ProtocolVariant = (&raw[..proto_end]).try_into()
.map_err(|_| BadResponse::InvalidProtocol)?;
// Status code
let status_start = proto_end + PAT_SPACE.len();
let Some(status_end) = try_find(raw, PAT_SPACE, JUT_SPACE, &status_start) else {
return Err(BadResponse::HeadLine);
};
let status: Status = Status::try_from(&raw[status_start..status_end])
.map_err(|_| BadResponse::InvalidStatusCode)?;
let Some(headline_end) = try_find(raw, PAT_CRLF, JUT_CRLF, &(status_end+PAT_SPACE.len())) else {
return Err(BadResponse::HeadLine);
};
// Headers
let mut res = HashMap::new();
let mut header_start = headline_end + PAT_CRLF.len();
loop {
let Some(header_end) = try_find(raw, PAT_CRLF, JUT_CRLF, &header_start) else {
return Err(BadResponse::InvalidHeaders);
};
if header_start == header_end {
break;
}
parse_header(&raw[header_start..header_end], &mut res)
.map_err(|_| BadResponse::InvalidHeaders)?;
header_start = header_end + PAT_CRLF.len();
}
Ok(ResponseHead {
status,
protocol: proto,
headers: res
})
}
fn parse_header(
raw: &[u8],
res: &mut HashMap<HeaderKey, HeaderValue>,
) -> Result<(), ()> {
let delim = try_find(raw, b": ", &[0, 0], &0).ok_or(())?;
let hkey: HeaderKey = std::str::from_utf8(&raw[..delim]).map_err(|_| ())?.into();
let hval: String = String::from_utf8(raw[delim+2..].to_vec()).map_err(|_| ())?;
if hval.is_empty() {
return Err(());
}
let entry = match res.get_mut(&hkey) {
Some(v) => v,
None => {
res.insert(hkey.clone(), HeaderValue::default());
res.get_mut(&hkey).unwrap()
}
};
entry.add(hval);
Ok(())
}
fn try_find(
haystack: &[u8],
pat: &[u8],
jumptable: &[usize],
start_from: &usize,
) -> Option<usize> {
let mut pat_ptr = 0_usize;
for (idx, cur) in haystack[*start_from..].iter().enumerate() {
if pat_ptr >= pat.len() {
return Some(idx + start_from - pat.len());
}
if *cur == pat[pat_ptr] {
pat_ptr += 1;
continue;
}
try_find_update_jumptable(cur, pat, &mut pat_ptr, jumptable);
}
if pat_ptr >= pat.len() {
return Some(haystack.len() - pat.len());
}
None
}
fn try_find_update_jumptable(
hay: &u8,
pat: &[u8],
pat_ptr: &mut usize,
jumptable: &[usize],
) {
while *pat_ptr != 0 {
*pat_ptr = jumptable[*pat_ptr];
if *hay == pat[*pat_ptr] {
*pat_ptr += 1;
break;
}
}
}
#[cfg(test)]
mod patfind {
use super::try_find;
macro_rules! test {
($name: ident, $src: literal, $pat: literal, $jt:tt, $start_from:literal, $res:expr) => {
#[test]
fn $name() {
let src = $src.as_bytes().to_vec();
let src = src.as_slice();
let pat = $pat.as_bytes().to_vec();
let pat = pat.as_slice();
let jt = vec!$jt;
let jt = jt.as_slice();
assert_eq!(try_find(src, pat, jt, &$start_from), $res);
}
}
}
test!(valid_singlebyte, "Hello, world!", ",", [0], 0, Some(5));
test!(valid_multibyte, "Hello, world!", ", ", [0, 0], 0, Some(5));
test!(valid_begin_nostart, "Hello, world!", ", ", [0, 0], 3, Some(5));
test!(valid_begin_startpat, "Hello, world!", ", ", [0, 0], 5, Some(5));
test!(invalid_begin_midpat, "Hello, world!", ", ", [0, 0], 6, None);
test!(recurse_jumptable_01, "AAAAAB", "AAAB", [0, 0, 1, 2], 0, Some(2));
test!(recurse_jumptable_02, "ABABABC", "ABABC", [0, 0, 0, 1, 2], 0, Some(2));
}
#[cfg(test)]
mod parse_request_head {
use std::collections::HashMap;
use crate::{
headers::{HeaderKey, HeaderValue},
method::Method,
path::HttpPath,
proto::h1::head::{BadRequest, ProtocolVariant}
};
use super::{parse_request_head, RequestHead};
macro_rules! test_inner {
(@get_params [$($pk: ident : $pv: literal),+]$(,)?) => {
Some(HashMap::from([$((stringify!($pk).to_string(), $pv.to_string())),*]))
};
(@get_params []) => {
None
};
}
macro_rules! test {
(
$name: ident,
$src: literal,
ok $method: ident $path: literal ? [$($pk: ident : $pv: literal),*$(,)?] $proto: ident,
[$($hk: ident : $hv: literal),*$(,)?]
) => {
test!($name, $src, Ok(RequestHead {
method: Method::$method,
path: HttpPath{ path:$path.to_string(), params:test_inner!(@get_params[$($pk:$pv),*]) },
protocol: ProtocolVariant::$proto,
headers: HashMap::from([$((HeaderKey::$hk,HeaderValue::new(vec![$hv.to_string()]))),*]),
}));
};
($name: ident, $src: literal, $res: expr) => {
#[test]
fn $name() {
let src = $src.as_bytes().to_vec();
let src = src.as_slice();
assert_eq!(parse_request_head(src), $res);
}
}
}
test!(valid_request, "GET /hello HTTP/1.0\r\n\r\n",
ok GET "/hello"?[] HTTP1_0,
[]
);
test!(valid_request_single_header, "GET / HTTP/1.1\r\nUser-Agent: Mozilla/5.0\r\n\r\n",
ok GET "/"?[] HTTP1_1,
[
USER_AGENT: "Mozilla/5.0",
]
);
test!(
valid_request_multi_header,
"GET /api?action=test HTTP/1.1\r\nUser-Agent: Mozilla/5.0\r\nConnection: close\r\n\r\n",
ok GET "/api"?[action: "test"] HTTP1_1,
[
USER_AGENT: "Mozilla/5.0",
CONNECTION: "close",
]
);
test!(invalid_method, "GHOST / HTTP/1.1\r\n\r\n", Err(BadRequest::InvalidMethod));
}
#[cfg(test)]
mod parse_response_head {
use std::collections::HashMap;
use crate::{
headers::{HeaderKey, HeaderValue},
proto::h1::head::{BadResponse, ProtocolVariant}
};
use super::{parse_response_head, ResponseHead, Status};
macro_rules! test {
(
$name: ident,
$src: literal,
ok $proto: ident $status: ident,
[$($hk: ident : $hv: literal),*$(,)?]
) => {
test!($name, $src, Ok(ResponseHead {
status: Status::$status,
protocol: ProtocolVariant::$proto,
headers: HashMap::from([$((HeaderKey::$hk,HeaderValue::new(vec![$hv.to_string()]))),*]),
}));
};
($name: ident, $src: literal, $res: expr) => {
#[test]
fn $name() {
let src = $src.as_bytes().to_vec();
let src = src.as_slice();
assert_eq!(parse_response_head(src), $res);
}
};
}
test!(valid_request, "HTTP/1.1 200 OK\r\n\r\n",
ok HTTP1_1 Ok,
[]
);
test!(valid_request_single_header, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n",
ok HTTP1_0 Ok,
[
CONTENT_TYPE: "text/html",
]
);
test!(
valid_request_multi_header,
"HTTP/1.1 200 OK\r\nServer: inferium\r\nConnection: close\r\n\r\n",
ok HTTP1_1 Ok,
[
SERVER: "inferium",
CONNECTION: "close",
]
);
test!(invalid_protocol, "PROTO 200 OK\r\n\r\n", Err(BadResponse::InvalidProtocol));
test!(invalid_status_code, "HTTP/1.1 42069 OK\r\n\r\n", Err(BadResponse::InvalidStatusCode));
}
#[cfg(test)]
mod construct_outgoing {
use std::collections::HashMap;
use crate::{headers::{HeaderKey, HeaderValue}, status::Status, path::HttpPath, method::Method};
use super::{ResponseHead, RequestHead, ProtocolVariant};
macro_rules! test {
(@headers [$($hk: ident : $hv: literal),*$(,)?]) => {
HashMap::from([$((HeaderKey::$hk, HeaderValue::new(vec![$hv.to_string()]))),*])
};
(@uri $path: literal [$($hk: ident : $hv: literal),+$(,)?]) => {
HttpPath {
path: $path.to_string(),
params: Some(HashMap::from([$((stringify!($hk).to_string(), $hv.to_string())),+])),
}
};
(@uri $path: literal [$(,)?]) => {
HttpPath {
path: $path.to_string(),
params: None,
}
};
($name: ident, res $proto: ident $status: ident, [$($hk: ident : $hv: literal),*$(,)?],
$target: literal
) => {
#[test]
fn $name() {
let src = ResponseHead {
protocol: ProtocolVariant::$proto,
status: Status::$status,
headers: test!(@headers [$($hk:$hv),*]),
};
assert_eq!(src.serialize(), $target);
}
};
(
$name: ident,
req $method: ident $path: literal [$($qk: ident : $qv: literal),*$(,)?] $proto: ident,
[$($hk: ident : $hv: literal),*$(,)?],
$target: literal
) => {
#[test]
fn $name() {
let src = RequestHead {
method: Method::$method,
path: test!(@uri $path [$($qk : $qv),*]),
protocol: ProtocolVariant::$proto,
headers: test!(@headers [$($hk : $hv),*]),
};
assert_eq!(src.serialize(), $target);
}
};
}
test!(response_simple_noheaders, res HTTP1_1 Ok, [], b"HTTP/1.1 200 OK\r\n\r\n");
test!(response_with_single_header_01, res HTTP1_0 NotFound, [
SERVER: "inferium",
], b"HTTP/1.0 404 Not Found\r\nserver: inferium\r\n\r\n");
test!(response_with_single_header_02, res HTTP1_1 Forbidden, [
SERVER: "inferium",
], b"HTTP/1.1 403 Forbidden\r\nserver: inferium\r\n\r\n");
test!(request_simple_noheaders, req GET "/"[] HTTP1_1, [], b"GET / HTTP/1.1\r\n\r\n");
test!(request_simple_single_header, req GET "/"[] HTTP1_1, [
USER_AGENT: "inferium",
], b"GET / HTTP/1.1\r\nuser-agent: inferium\r\n\r\n");
test!(request_path_single_header, req GET "/well/hello/there"[] HTTP1_0, [
USER_AGENT: "inferium",
], b"GET /well/hello/there HTTP/1.0\r\nuser-agent: inferium\r\n\r\n");
test!(request_path_with_query_single_header, req GET "/well/hello/there"[
hello: "world"
] HTTP1_0, [
USER_AGENT: "inferium",
], b"GET /well/hello/there?hello=world HTTP/1.0\r\nuser-agent: inferium\r\n\r\n");
}

View File

@@ -0,0 +1,26 @@
mod head;
mod stream_handler;
mod exports;
pub use exports::{
Request,
Response,
SyncClient,
SyncServer,
ClientSendError,
ClientReceiveError,
ServerSendError,
ServerReceiveError,
BodySendError,
};
#[cfg(feature = "async")]
pub use exports::{
AsyncClient,
AsyncServer,
};
pub use head::{
RequestHead,
ResponseHead,
};
pub use head::ProtocolVariant;

View File

@@ -0,0 +1,600 @@
use crate::{
body::{ChunkedIn, Incoming, SizedIn},
headers::HeaderKey,
io::{PrependableStream, ReaderError, ReaderValue, Receive, Send, SyncReader},
proto::h1::head::{parse_request_head, parse_response_head},
settings::BUF_SIZE_HEAD, HeaderValue
};
#[cfg(feature = "async")]
use crate::io::{AsyncReceive, AsyncSend, AsyncReader};
use super::head::{BadRequest, BadResponse, RequestHead, ResponseHead};
#[derive(Debug)]
pub(super) struct StreamHandler<T> {
pub(super) inner: PrependableStream<T>,
/// Whether the caller has received the entire HTTP body from the other endpoint (if needed).
has_exhausted_body: bool,
}
#[cfg_attr(test, derive(Debug))]
pub(super) enum StreamHandlerReceiveError<T> {
HeaderTooLarge,
RequiresBodyPolling,
ParsingError(T),
InvalidExpectedBody,
NoData,
IO(std::io::Error),
}
impl From<BadRequest> for StreamHandlerReceiveError<BadRequest> {
fn from(value: BadRequest) -> Self {
Self::ParsingError(value)
}
}
impl From<BadResponse> for StreamHandlerReceiveError<BadResponse> {
fn from(value: BadResponse) -> Self {
Self::ParsingError(value)
}
}
#[cfg_attr(test, derive(Debug))]
pub(super) enum StreamHandlerSendError {
RequiresBodyPolling,
IO(std::io::Error),
}
impl From<std::io::Error> for StreamHandlerSendError {
fn from(value: std::io::Error) -> Self {
Self::IO(value)
}
}
impl<T> StreamHandler<T> {
pub(super) fn new(inner: T) -> Self {
Self { inner: PrependableStream::new(inner), has_exhausted_body: true }
}
}
macro_rules! leaky {
($stream: expr, $head: ident, $body: ident) => {{
$stream.prepend_to_read($body);
$head
}};
}
#[cfg_attr(test, derive(Debug))]
pub(super) enum ExpectedBody<'a, T> {
Sized(Incoming<'a, SizedIn<'a, T>>),
Chunked(Incoming<'a, ChunkedIn<'a, T>>),
}
type FullResponse<'a, T> = (ResponseHead, Option<ExpectedBody<'a, PrependableStream<T>>>);
type FullRequest<'a, T> = (RequestHead, Option<ExpectedBody<'a, PrependableStream<T>>>);
fn construct_response_body<T: Receive>(
sh: &mut StreamHandler<T>,
res: ResponseHead,
) -> Result<FullResponse<T>, StreamHandlerReceiveError<BadResponse>> {
if let Some(transfer_encoding) = res.headers.get(&HeaderKey::TRANSFER_ENCODING) {
if transfer_encoding != &HeaderValue::new(vec!["chunked".to_string()]) {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
}
sh.has_exhausted_body = false;
return Ok((res, Some(ExpectedBody::Chunked(Incoming::<ChunkedIn<_>>::new(
ChunkedIn::new(&mut sh.inner), &mut sh.has_exhausted_body
)))));
}
if let Some(content_length) = res.headers.get(&HeaderKey::CONTENT_LENGTH) {
let Ok(content_length): Result<usize, _> = content_length.get().parse() else {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
};
sh.has_exhausted_body = false;
return Ok((res, Some(ExpectedBody::Sized(Incoming::<SizedIn<_>>::new(
SizedIn::new(&mut sh.inner, content_length), &mut sh.has_exhausted_body
)))));
}
Ok((res, None))
}
fn construct_request_body<T: Receive>(
sh: &mut StreamHandler<T>,
req: RequestHead,
) -> Result<FullRequest<T>, StreamHandlerReceiveError<BadRequest>> {
if let Some(transfer_encoding) = req.headers.get(&HeaderKey::TRANSFER_ENCODING) {
if transfer_encoding != &HeaderValue::new(vec!["chunked".to_string()]) {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
}
sh.has_exhausted_body = false;
return Ok((req, Some(ExpectedBody::Chunked(Incoming::<ChunkedIn<_>>::new(
ChunkedIn::new(&mut sh.inner), &mut sh.has_exhausted_body
)))));
}
if let Some(content_length) = req.headers.get(&HeaderKey::CONTENT_LENGTH) {
let Ok(content_length): Result<usize, _> = content_length.get().parse() else {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
};
sh.has_exhausted_body = false;
return Ok((req, Some(ExpectedBody::Sized(Incoming::<SizedIn<_>>::new(
SizedIn::new(&mut sh.inner, content_length), &mut sh.has_exhausted_body
)))));
}
Ok((req, None))
}
#[cfg(feature = "async")]
fn construct_response_body_async<T: AsyncReceive>(
sh: &mut StreamHandler<T>,
res: ResponseHead,
) -> Result<FullResponse<T>, StreamHandlerReceiveError<BadResponse>> {
if let Some(transfer_encoding) = res.headers.get(&HeaderKey::TRANSFER_ENCODING) {
if transfer_encoding != &HeaderValue::new(vec!["chunked".to_string()]) {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
}
sh.has_exhausted_body = false;
return Ok((res, Some(ExpectedBody::Chunked(Incoming::<ChunkedIn<_>>::new_async(
ChunkedIn::new(&mut sh.inner), &mut sh.has_exhausted_body
)))));
}
if let Some(content_length) = res.headers.get(&HeaderKey::CONTENT_LENGTH) {
let Ok(content_length): Result<usize, _> = content_length.get().parse() else {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
};
sh.has_exhausted_body = false;
return Ok((res, Some(ExpectedBody::Sized(Incoming::<SizedIn<_>>::new_async(
SizedIn::new(&mut sh.inner, content_length), &mut sh.has_exhausted_body
)))));
}
Ok((res, None))
}
#[cfg(feature = "async")]
fn construct_request_body_async<T: AsyncReceive>(
sh: &mut StreamHandler<T>,
req: RequestHead,
) -> Result<FullRequest<T>, StreamHandlerReceiveError<BadRequest>> {
if let Some(transfer_encoding) = req.headers.get(&HeaderKey::TRANSFER_ENCODING) {
if transfer_encoding != &HeaderValue::new(vec!["chunked".to_string()]) {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
}
sh.has_exhausted_body = false;
return Ok((req, Some(ExpectedBody::Chunked(Incoming::<ChunkedIn<_>>::new_async(
ChunkedIn::new(&mut sh.inner), &mut sh.has_exhausted_body
)))));
}
if let Some(content_length) = req.headers.get(&HeaderKey::CONTENT_LENGTH) {
let Ok(content_length): Result<usize, _> = content_length.get().parse() else {
return Err(StreamHandlerReceiveError::InvalidExpectedBody);
};
sh.has_exhausted_body = false;
return Ok((req, Some(ExpectedBody::Sized(Incoming::<SizedIn<_>>::new_async(
SizedIn::new(&mut sh.inner, content_length), &mut sh.has_exhausted_body
)))));
}
Ok((req, None))
}
impl<T: Receive> StreamHandler<T> {
pub(super) fn receive_request(&mut self)
-> Result<FullRequest<T>, StreamHandlerReceiveError<BadRequest>> {
if !self.has_exhausted_body {
return Err(StreamHandlerReceiveError::RequiresBodyPolling);
}
let mut buf = [0_u8; BUF_SIZE_HEAD];
let received = {
let mut reader = SyncReader::new(&mut self.inner);
reader.recv_until(b"\r\n\r\n", &[0, 0, 0, 1], &mut buf)
};
let header = match received {
Ok(ReaderValue::ExactRead { up_to_delimiter: h }) => h,
Ok(ReaderValue::LeakyRead { up_to_delimiter: h, rest: b }) => leaky!(self.inner, h, b),
Err(ReaderError::IO(e))=>return Err(StreamHandlerReceiveError::IO(e)),
Err(ReaderError::NoData)=>return Err(StreamHandlerReceiveError::NoData),
Err(ReaderError::BufferOverflow)=>return Err(StreamHandlerReceiveError::HeaderTooLarge),
}.len();
let header = &buf[..header+4];
let header = parse_request_head(header)?;
construct_request_body(self, header)
}
pub(super) fn receive_response(&mut self)
-> Result<FullResponse<T>, StreamHandlerReceiveError<BadResponse>> {
if !self.has_exhausted_body {
return Err(StreamHandlerReceiveError::RequiresBodyPolling);
}
let mut buf = [0_u8; BUF_SIZE_HEAD];
let received = {
let mut reader = SyncReader::new(&mut self.inner);
reader.recv_until(b"\r\n\r\n", &[0, 0, 0, 1], &mut buf)
};
let header = match received {
Ok(ReaderValue::ExactRead { up_to_delimiter: h }) => h,
Ok(ReaderValue::LeakyRead { up_to_delimiter: h, rest: b }) => leaky!(self.inner, h, b),
Err(ReaderError::IO(e))=>return Err(StreamHandlerReceiveError::IO(e)),
Err(ReaderError::NoData)=>return Err(StreamHandlerReceiveError::NoData),
Err(ReaderError::BufferOverflow)=>return Err(StreamHandlerReceiveError::HeaderTooLarge),
}.len();
let header = &buf[..header+4];
let header = parse_response_head(header)?;
construct_response_body(self, header)
}
}
impl<T: Send> StreamHandler<T> {
pub(super) fn send_request(&mut self, req: &RequestHead) -> Result<(), StreamHandlerSendError> {
if !self.has_exhausted_body {
return Err(StreamHandlerSendError::RequiresBodyPolling);
}
let serialized = req.serialize();
let serialized = serialized.as_slice();
let mut ptr = 0;
while ptr < serialized.len() {
ptr += self.inner.send(&serialized[ptr..])?;
}
Ok(())
}
pub(super) fn send_response(
&mut self, res: &ResponseHead
) -> Result<(), StreamHandlerSendError> {
if !self.has_exhausted_body {
return Err(StreamHandlerSendError::RequiresBodyPolling);
}
let serialized = res.serialize();
let serialized = serialized.as_slice();
let mut ptr = 0;
while ptr < serialized.len() {
ptr += self.inner.send(&serialized[ptr..])?;
}
Ok(())
}
}
#[cfg(feature = "async")]
impl<T: AsyncReceive> StreamHandler<T> {
pub(super) async fn receive_request_async(&mut self)
-> Result<FullRequest<T>, StreamHandlerReceiveError<BadRequest>> {
if !self.has_exhausted_body {
return Err(StreamHandlerReceiveError::RequiresBodyPolling);
}
let mut buf = [0_u8; BUF_SIZE_HEAD];
let received = {
let mut reader = AsyncReader::new(&mut self.inner);
reader.recv_until(b"\r\n\r\n", &[0, 0, 0, 1], &mut buf).await
};
let header = match received {
Ok(ReaderValue::ExactRead { up_to_delimiter: h }) => h,
Ok(ReaderValue::LeakyRead { up_to_delimiter: h, rest: b }) => leaky!(self.inner, h, b),
Err(ReaderError::IO(e))=>return Err(StreamHandlerReceiveError::IO(e)),
Err(ReaderError::NoData)=>return Err(StreamHandlerReceiveError::NoData),
Err(ReaderError::BufferOverflow)=>return Err(StreamHandlerReceiveError::HeaderTooLarge),
}.len();
let header = &buf[..header+4];
let header = parse_request_head(header)?;
construct_request_body_async(self, header)
}
pub(super) async fn receive_response_async(&mut self)
-> Result<FullResponse<T>, StreamHandlerReceiveError<BadResponse>> {
if !self.has_exhausted_body {
return Err(StreamHandlerReceiveError::RequiresBodyPolling);
}
let mut buf = [0_u8; BUF_SIZE_HEAD];
let received = {
let mut reader = AsyncReader::new(&mut self.inner);
reader.recv_until(b"\r\n\r\n", &[0, 0, 0, 1], &mut buf).await
};
let header = match received {
Ok(ReaderValue::ExactRead { up_to_delimiter: h }) => h,
Ok(ReaderValue::LeakyRead { up_to_delimiter: h, rest: b }) => leaky!(self.inner, h, b),
Err(ReaderError::IO(e))=>return Err(StreamHandlerReceiveError::IO(e)),
Err(ReaderError::NoData)=>return Err(StreamHandlerReceiveError::NoData),
Err(ReaderError::BufferOverflow)=>return Err(StreamHandlerReceiveError::HeaderTooLarge),
}.len();
let header = &buf[..header+4];
let header = parse_response_head(header)?;
construct_response_body_async(self, header)
}
}
#[cfg(feature = "async")]
impl<T: AsyncSend> StreamHandler<T> {
pub(super) async fn send_request_async(
&mut self, req: &RequestHead
) -> Result<(), StreamHandlerSendError> {
if !self.has_exhausted_body {
return Err(StreamHandlerSendError::RequiresBodyPolling);
}
let serialized = req.serialize();
let serialized = serialized.as_slice();
let mut ptr = 0;
while ptr < serialized.len() {
ptr += self.inner.send(&serialized[ptr..]).await?;
}
Ok(())
}
pub(super) async fn send_response_async(
&mut self, res: &ResponseHead
) -> Result<(), StreamHandlerSendError> {
if !self.has_exhausted_body {
return Err(StreamHandlerSendError::RequiresBodyPolling);
}
let serialized = res.serialize();
let serialized = serialized.as_slice();
let mut ptr = 0;
while ptr < serialized.len() {
ptr += self.inner.send(&serialized[ptr..]).await?;
}
Ok(())
}
}
#[cfg(test)]
mod receive {
use crate::{
io::TestSyncStream,
method::Method,
path::HttpPath,
headers::{HeaderKey, HeaderValue},
proto::h1::head::{RequestHead, ResponseHead, ProtocolVariant},
status::Status,
};
#[cfg(feature = "async")]
use crate::io::TestAsyncStream;
use std::collections::HashMap;
use super::{StreamHandler, ExpectedBody};
macro_rules! test_inner {
(
@request_head $method: ident,
$path: literal,
$proto: ident, [$($qk: ident : $qv: literal),*], [$($hk: ident : $hv: literal),*]
) => {
RequestHead {
method: Method::$method,
path: test_inner!(@http_path $path, [$($qk : $qv),*]),
protocol: ProtocolVariant::$proto,
headers: test_inner!(@headers [$($hk : $hv),*]),
}
};
(
@response_head
$proto: ident, $status: ident, [$($hk: ident : $hv: literal),*]
) => {
ResponseHead {
status: Status::$status,
protocol: ProtocolVariant::$proto,
headers: test_inner!(@headers [$($hk : $hv),*]),
}
};
(@http_path $path: literal, [$($hk: ident : $hv: literal),+]) => {
HttpPath { path: $path.to_string(), params: Some(HashMap::from([$(($hk, $hv)),+])) }
};
(@http_path $path: literal, []) => {
HttpPath { path: $path.to_string(), params: None }
};
(@headers [$($hk: ident : $hv :literal),*]) => {
HashMap::from([$((HeaderKey::$hk, HeaderValue::new(Vec::from([$hv.to_string()])))),*])
};
}
macro_rules! test {
(
$name: ident $name_async: ident,
req $src: literal,
$method: ident $path: literal [$($qk: ident : $qv: literal),*] $proto: ident,
[$($hk: ident : $hv: literal),*$(,)?]
) => {
test!(@inner $name, $name_async, req $src, test_inner!(
@request_head $method, $path, $proto, [$($qk:$qv),*], [$($hk:$hv),*]
));
};
(
$name: ident $name_async: ident,
req $src: literal,
$method: ident $path: literal [$($qk: ident : $qv: literal),*] $proto: ident,
[$($hk: ident : $hv: literal),*$(,)?],
$body: literal
) => {
test!(@inner $name, $name_async, req $src, test_inner!(
@request_head $method, $path, $proto, [$($qk:$qv),*], [$($hk:$hv),*]
), $body);
};
(
$name: ident $name_async: ident,
res $src: literal,
$proto: ident $status: ident,
[$($hk: ident : $hv: literal),*$(,)?]
) => {
test!(@inner $name, $name_async, res $src, test_inner!(
@response_head $proto, $status, [$($hk:$hv),*]
));
};
(
$name: ident $name_async: ident,
res $src: literal,
$proto: ident $status: ident,
[$($hk: ident : $hv: literal),*$(,)?],
$body: literal
) => {
test!(@inner $name, $name_async, res $src, test_inner!(
@response_head $proto, $status, [$($hk:$hv),*]
), $body);
};
(
@inner $name: ident, $name_async: ident, req $src: literal, $res_head: expr
) => {
#[test]
fn $name() {
let mut src = $src.into();
let stream = TestSyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_request().unwrap();
assert_eq!(head, $res_head);
assert!(body.is_none());
}
#[cfg(all(feature = "async", any(feature = "tokio-net", feature = "tokio-unixsocks")))]
#[tokio::test]
async fn $name_async() {
let mut src = $src.into();
let stream = TestAsyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_request_async().await.unwrap();
assert_eq!(head, $res_head);
assert!(body.is_none());
}
};
(
@inner $name: ident, $name_async: ident, req $src: literal, $res_head: expr, $res_body: expr
) => {
#[test]
fn $name() {
let mut src = $src.into();
let stream = TestSyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_request().unwrap();
assert_eq!(head, $res_head);
let Some(ExpectedBody::Sized(mut body)) = body else { panic!("body mismatch"); };
assert_eq!(body.recv_all().unwrap(), $res_body.to_vec());
}
#[cfg(all(feature = "async", any(feature = "tokio-net", feature = "tokio-unixsocks")))]
#[tokio::test]
async fn $name_async() {
let mut src = $src.into();
let stream = TestAsyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_request_async().await.unwrap();
assert_eq!(head, $res_head);
let Some(ExpectedBody::Sized(mut body)) = body else { panic!("body mismatch"); };
assert_eq!(body.recv_all_async().await.unwrap(), $res_body.to_vec());
}
};
(
@inner $name: ident, $name_async: ident, res $src: literal, $res_head: expr
) => {
#[test]
fn $name() {
let mut src = $src.into();
let stream = TestSyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_response().unwrap();
assert_eq!(head, $res_head);
assert!(body.is_none());
}
#[cfg(all(feature = "async", any(feature = "tokio-net", feature = "tokio-unixsocks")))]
#[tokio::test]
async fn $name_async() {
let mut src = $src.into();
let stream = TestAsyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_response_async().await.unwrap();
assert_eq!(head, $res_head);
assert!(body.is_none());
}
};
(
@inner $name: ident, $name_async: ident, res $src: literal, $res_head: expr, $res_body: expr
) => {
#[test]
fn $name() {
let mut src = $src.into();
let stream = TestSyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_response().unwrap();
assert_eq!(head, $res_head);
let Some(ExpectedBody::Sized(mut body)) = body else { panic!("body mismatch"); };
assert_eq!(body.recv_all().unwrap(), $res_body.to_vec());
}
#[cfg(all(feature = "async", any(feature = "tokio-net", feature = "tokio-unixsocks")))]
#[tokio::test]
async fn $name_async() {
let mut src = $src.into();
let stream = TestAsyncStream::<4>::new(&mut src);
let mut handler = StreamHandler::new(stream);
let (head, body) = handler.receive_response_async().await.unwrap();
assert_eq!(head, $res_head);
let Some(ExpectedBody::Sized(mut body)) = body else { panic!("body mismatch"); };
assert_eq!(body.recv_all_async().await.unwrap(), $res_body.to_vec());
}
};
}
test!(
request_valid_no_body async_request_valid_no_body,
req "GET / HTTP/1.1\r\nserver: inferium\r\n\r\n",
GET "/"[] HTTP1_1,
[
SERVER: "inferium",
]
);
test!(
request_valid_body async_request_valid_body,
req "GET / HTTP/1.1\r\ncontent-length: 4\r\n\r\ntest",
GET "/"[] HTTP1_1,
[
CONTENT_LENGTH: "4",
],
b"test"
);
test!(
response_valid_no_body async_response_valid_no_body,
res "HTTP/1.0 200 OK\r\nserver: inferium\r\n\r\n",
HTTP1_0 Ok,
[
SERVER: "inferium",
]
);
test!(
response_valid_body async_response_valid_body,
res "HTTP/1.1 200 OK\r\nserver: inferium\r\nconnection: close\r\ncontent-length: 4\r\n\r\n\
test",
HTTP1_1 Ok,
[
SERVER: "inferium",
CONNECTION: "close",
CONTENT_LENGTH: "4",
],
b"test"
);
test!(
response_short_body async_response_short_body,
res "HTTP/1.1 200 OK\r\nserver: inferium\r\nconnection: close\r\ncontent-length: 4\r\n\r\n\
tes",
HTTP1_1 Ok,
[
SERVER: "inferium",
CONNECTION: "close",
CONTENT_LENGTH: "4",
],
b"tes"
);
test!(
response_long_body async_response_long_body,
res "HTTP/1.1 200 OK\r\nserver: inferium\r\nconnection: close\r\ncontent-length: 4\r\n\r\n\
testing",
HTTP1_1 Ok,
[
SERVER: "inferium",
CONNECTION: "close",
CONTENT_LENGTH: "4",
],
b"test"
);
}

View File

@@ -0,0 +1 @@
pub mod h1;

View File

@@ -0,0 +1,2 @@
pub const BUF_SIZE_HEAD: usize = 8192;
pub const BUF_SIZE_BODY: usize = 4096;

121
lib/inferium/src/status.rs Normal file
View File

@@ -0,0 +1,121 @@
macro_rules! http_status {
(
$(#$objdoc: tt)+
$($ident: ident = ($number: literal, $text: literal)),*$(,)?
) => {
$(#$objdoc)+
#[derive(Debug, PartialEq, Eq)]
pub enum Status {
$($ident),*
}
impl TryFrom<&[u8]> for Status {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value { $($number => Ok(Self::$ident),)* _ => Err(()) }
}
}
impl Status {
pub fn num(&self) -> &[u8] {
match self { $(Self::$ident => $number),* }
}
pub fn text(&self) -> &[u8] {
match self { $(Self::$ident => $text),* }
}
}
impl std::fmt::Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self { $(Self::$ident => write_status(self, f)?),* }
Ok(())
}
}
};
}
fn write_status(this: &Status, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let num = unsafe { std::str::from_utf8_unchecked(this.num()) };
let text = unsafe { std::str::from_utf8_unchecked(this.text()) };
write!(f, "{num} {text}")
}
http_status! {
/// HTTP response status codes and their names
///
/// Numbers and names are represented in byte arrays for faster response parsing and
/// construction.
// Informational
Continue = (b"100", b"Continue"),
SwitchingProtocols = (b"101", b"Switching Protocols"),
Processing = (b"102", b"Processing"),
EarlyHints = (b"103", b"Early Hints"),
// Successful
Ok = (b"200", b"OK"),
Created = (b"201", b"Created"),
Accepted = (b"202", b"Accepted"),
NonAuthoritativeInformation = (b"203", b"Non-Authoritative Information"),
NoContent = (b"204", b"No Content"),
ResetContent = (b"205", b"Reset Content"),
PartialContent = (b"206", b"Partial Content"),
MultiStatus = (b"207", b"Multi-Status"),
AlreadyReported = (b"208", b"AlreadyReported"),
ImUsed = (b"226", b"IM Used"),
// Redirection
MultipleChoices = (b"300", b"Multiple Choices"),
MovedPermanently = (b"301", b"Moved Permanently"),
Found = (b"302", b"Found"),
SeeOther = (b"303", b"See Other"),
NotModified = (b"304", b"Not Modified"),
TemporaryRedirect = (b"307", b"Temporary Redirect"),
PermanentRedirect = (b"308", b"Permanent Redirect"),
// Client errors
BadRequest = (b"400", b"Bad Request"),
Unauthorized = (b"401", b"Unauthorized"),
PaymentRequired = (b"402", b"Payment Required"),
Forbidden = (b"403", b"Forbidden"),
NotFound = (b"404", b"Not Found"),
MethodNotAllowed = (b"405", b"MethodNotAllowed"),
NotAcceptable = (b"406", b"Not Acceptable"),
ProxyAuthenticationRequired = (b"407", b"Proxy Authentication Required"),
RequestTimeout = (b"408", b"Request Timeout"),
Conflict = (b"409", b"Conflict"),
Gone = (b"410", b"Gone"),
LengthRequired = (b"411", b"Length Required"),
PreconditionFailed = (b"412", b"Precondition Failed"),
ContentTooLarge = (b"413", b"Content Too Large"),
UriTooLong = (b"414", b"URI Too Long"),
UnsupportedMediaType = (b"415", b"Unsupported Media Type"),
RangeNotSatisfiable = (b"416", b"Range Not Satisfiable"),
ExpectationFailed = (b"417", b"Expectation Failed"),
ImATeapot = (b"418", b"I'm a teapot"),
MisdirectedRequest = (b"421", b"Misdirected Request"),
UnprocessableContent = (b"422", b"Unprocessable Content"),
Locked = (b"423", b"Locked"),
FailedDependency = (b"424", b"Failed Dependency"),
TooEarly = (b"425", b"Too Early"),
UpgradeRequired = (b"426", b"Upgrade Required"),
PreconditionRequired = (b"428", b"Precondition Required"),
TooManyRequests = (b"429", b"Too Many Requests"),
RequestHeaderFieldsTooLarge = (b"431", b"Request Header Fields Too Large"),
UnavailableForLegalReasons = (b"451", b"Unavailable For Legal Reasons"),
// Server errors
InternalServerError = (b"500", b"Internal Server Error"),
NotImplemented = (b"501", b"Not Implemented"),
BadGateway = (b"502", b"Bad Gateway"),
ServiceUnavailable = (b"503", b"Service Unavailable"),
GatewayTimeout = (b"504", b"Gateway Timeout"),
HttpVersionNotSupported = (b"505", b"HTTP Version Not Supported"),
VariantAlsoNegotiates = (b"506", b"Variant Also Negotiates"),
InsufficientStorage = (b"507", b"Insufficient Storage"),
LoopDetected = (b"508", b"Loop Detected"),
NotExtended = (b"510", b"Not Extended"),
NetworkAuthenticationRequired = (b"511", b"Network Authentication Required"),
}