mod parse;
mod read;
#[cfg(test)]
mod tests;
pub use read::ReadRequest;
use std::mem::MaybeUninit;
use std::num::NonZeroU8;
use crate::headers::{Header, HeaderName, HttpVersion, Method, RequestHeader};
pub const DEFAULT_MAX_HEADER_SIZE: usize = 8192;
pub const DEFAULT_MAX_HEADERS: usize = 32;
#[derive(Clone, Copy)]
#[repr(transparent)]
struct HeaderSlot(Option<NonZeroU8>);
impl HeaderSlot {
const EMPTY: Self = Self(None);
#[inline]
const fn new(idx: u8) -> Self {
Self(NonZeroU8::new(idx + 1))
}
#[inline]
const fn get(self) -> Option<u8> {
match self.0 {
Some(v) => Some(v.get() - 1),
None => None,
}
}
#[inline]
const fn is_some(self) -> bool {
self.0.is_some()
}
#[inline]
const fn is_none(self) -> bool {
self.0.is_none()
}
}
pub struct Request<'buf, const MAX_HDRS: usize = DEFAULT_MAX_HEADERS> {
method: Method,
version: HttpVersion,
path: &'buf [u8],
headers: [MaybeUninit<Header<'buf>>; MAX_HDRS],
header_count: usize,
known: [HeaderSlot; RequestHeader::COUNT],
content_length: Option<u64>,
}
impl<const MAX_HDRS: usize> std::fmt::Debug for Request<'_, MAX_HDRS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("method", &self.method)
.field("version", &self.version)
.field("path", &self.path_str())
.field("header_count", &self.header_count)
.finish()
}
}
impl<'buf, const MAX_HDRS: usize> Request<'buf, MAX_HDRS> {
#[inline]
fn headers_init(&self) -> &[Header<'buf>] {
let init = &self.headers[..self.header_count];
unsafe {
&*(std::ptr::from_ref::<[MaybeUninit<Header<'buf>>]>(init) as *const [Header<'buf>])
}
}
#[inline]
#[must_use]
pub const fn method(&self) -> Method {
self.method
}
#[inline]
#[must_use]
pub const fn version(&self) -> HttpVersion {
self.version
}
#[inline]
#[must_use]
pub const fn path(&self) -> &'buf [u8] {
self.path
}
#[inline]
#[must_use]
pub fn path_only(&self) -> &'buf [u8] {
self.path
.iter()
.position(|&b| b == b'?')
.map_or(self.path, |q| &self.path[..q])
}
#[inline]
#[must_use]
pub fn query(&self) -> &'buf [u8] {
self.path
.iter()
.position(|&b| b == b'?')
.map_or(&[][..], |q| &self.path[q + 1..])
}
#[inline]
#[must_use]
pub fn query_pairs(&self) -> crate::query::QueryIter<'buf> {
crate::query::parse(self.query())
}
#[inline]
pub const fn path_str(&self) -> Result<&'buf str, std::str::Utf8Error> {
std::str::from_utf8(self.path)
}
#[inline]
pub fn path_decoded<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], crate::error::Error>
where
'buf: 'a,
{
crate::pct::decode(self.path, crate::pct::Mode::Path, out).map_err(Into::into)
}
#[inline]
pub fn header<'name>(&self, name: impl HeaderName<'name>) -> Option<&'buf [u8]> {
if let Some(slot) = name.known_index() {
return self.known[slot]
.get()
.map(|idx| self.headers_init()[idx as usize].value());
}
let name_bytes = name.as_header_bytes();
let init = self.headers_init();
for h in init {
if h.name().eq_ignore_ascii_case(name_bytes) {
return Some(h.value());
}
}
None
}
#[inline]
pub fn header_str<'name>(
&self,
name: impl HeaderName<'name>,
) -> Result<Option<&'buf str>, std::str::Utf8Error> {
self.header(name)
.map_or(Ok(None), |v| std::str::from_utf8(v).map(Some))
}
#[inline]
#[must_use]
pub const fn header_count(&self) -> usize {
self.header_count
}
#[inline]
#[must_use]
pub fn headers(&self) -> &[Header<'buf>] {
self.headers_init()
}
#[inline]
pub fn content_type(
&self,
) -> Option<Result<crate::media::MediaType<'buf>, crate::error::MediaErrorKind>> {
self.header(RequestHeader::ContentType)
.map(crate::media::MediaType::parse)
}
#[inline]
#[must_use]
pub const fn content_length(&self) -> Option<u64> {
self.content_length
}
#[inline]
#[must_use]
pub const fn body_kind(&self) -> crate::body::BodyKind {
use crate::body::BodyKind;
if self.known[RequestHeader::TransferEncoding as usize].is_some() {
return BodyKind::Chunked;
}
match self.content_length {
Some(len) => BodyKind::ContentLength(len),
None => BodyKind::None,
}
}
}