use std::fmt::{self, Debug};
use http::{StatusCode, Uri, Version};
use tracing::debug;
use buffet::Piece;
mod headers;
pub use headers::*;
mod method;
pub use method::*;
use crate::{error::NeverError, util::ReadAndParseError};
#[derive(Clone)]
pub struct Request {
pub method: Method,
pub uri: Uri,
pub version: Version,
pub headers: Headers,
}
impl Default for Request {
fn default() -> Self {
Self {
method: Method::Get,
uri: "/".parse().unwrap(),
version: Version::HTTP_11,
headers: Default::default(),
}
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Request")
.field("method", &self.method)
.field("uri", &self.uri)
.field("version", &self.version)
.finish()?;
for (name, value) in &self.headers {
debug!(%name, value = ?std::str::from_utf8(value), "header");
}
Ok(())
}
}
#[derive(Clone)]
pub struct Response {
pub version: Version,
pub status: StatusCode,
pub headers: Headers,
}
impl Default for Response {
fn default() -> Self {
Self {
version: Version::HTTP_11,
status: StatusCode::OK,
headers: Default::default(),
}
}
}
impl Response {
pub(crate) fn debug_print(&self) {
debug!(code = %self.status, version = ?self.version, "got response");
for (name, value) in &self.headers {
debug!(%name, value = ?std::str::from_utf8(value), "got header");
}
}
pub fn means_empty_body(&self) -> bool {
matches!(
self.status,
StatusCode::NO_CONTENT | StatusCode::NOT_MODIFIED
)
}
}
pub enum BodyChunk {
Chunk(Piece),
Done {
trailers: Option<Box<Headers>>,
},
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BodyError {
#[error("next_chunk() was called after an error was returned")]
CalledNextChunkAfterError,
#[error("connection closed while reading chunk size")]
ClosedWhileReadingChunkSize,
#[error("invalid chunk size")]
InvalidChunkSize,
#[error("connection closed while reading chunk data")]
ClosedWhileReadingChunkData,
#[error("connection closed while reading content-length body")]
ClosedWhileReadingContentLength,
#[error("error while reading chunk data: {0}")]
ErrorWhileReadingChunkData(std::io::Error),
#[error("connection closed while reading chunk terminator")]
ClosedWhileReadingChunkTerminator,
#[error("invalid chunk terminator: {0}")]
InvalidChunkTerminator(#[from] ReadAndParseError),
#[error("write_chunk called when no body was expected")]
CalledWriteBodyChunkWhenNoBodyWasExpected,
#[error("allocation failed: {0}")]
Alloc(#[from] buffet::bufpool::Error),
#[error("I/O error while writing: {0}")]
WriteError(std::io::Error),
}
impl AsRef<dyn std::error::Error> for BodyError {
fn as_ref(&self) -> &(dyn std::error::Error + 'static) {
self
}
}
#[allow(async_fn_in_trait)] pub trait Body: Debug
where
Self: Sized,
{
type Error: std::error::Error + 'static;
fn content_len(&self) -> Option<u64>;
fn eof(&self) -> bool;
async fn next_chunk(&mut self) -> Result<BodyChunk, Self::Error>;
}
impl Body for () {
type Error = NeverError;
fn content_len(&self) -> Option<u64> {
Some(0)
}
fn eof(&self) -> bool {
true
}
async fn next_chunk(&mut self) -> Result<BodyChunk, Self::Error> {
Ok(BodyChunk::Done { trailers: None })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServeOutcome {
ClientRequestedConnectionClose,
ServerRequestedConnectionClose,
ClientClosedConnectionBetweenRequests,
ClientDidntSpeakHttp11,
RequestHeadersTooLargeOnHttp1Conn,
ClientDidntSpeakHttp2,
SuccessfulHttp2GracefulShutdown,
}
pub struct SinglePieceBody {
content_len: u64,
piece: Option<Piece>,
}
impl<T> From<T> for SinglePieceBody
where
T: Into<Piece>,
{
fn from(piece: T) -> Self {
Self::new(piece.into())
}
}
impl fmt::Debug for SinglePieceBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug_struct = f.debug_struct("SinglePieceBody");
debug_struct.field("content_len", &self.content_len);
if let Some(piece) = &self.piece {
match std::str::from_utf8(piece.as_ref()) {
Ok(utf8_str) => debug_struct.field("piece", &utf8_str),
Err(_) => debug_struct.field("piece", &"(non-utf8 string)"),
};
} else {
debug_struct.field("piece", &"(none)");
}
debug_struct.finish()
}
}
impl SinglePieceBody {
pub(crate) fn new(piece: Piece) -> Self {
let content_len = piece.len() as u64;
Self {
content_len,
piece: Some(piece),
}
}
}
impl Body for SinglePieceBody {
type Error = NeverError;
fn content_len(&self) -> Option<u64> {
Some(self.content_len)
}
fn eof(&self) -> bool {
self.piece.is_none()
}
async fn next_chunk(&mut self) -> Result<BodyChunk, Self::Error> {
tracing::trace!( has_piece = %self.piece.is_some(), "SinglePieceBody::next_chunk");
if let Some(piece) = self.piece.take() {
Ok(BodyChunk::Chunk(piece))
} else {
Ok(BodyChunk::Done { trailers: None })
}
}
}