pub mod convert;
use std::io::{BufReader, Cursor, Read};
pub use http::{header, HeaderMap, Method, StatusCode, Uri};
pub type Request = http::Request<Body>;
pub type Response = http::Response<Body>;
pub type ResponseBuilder = http::response::Builder;
pub fn response_builder() -> ResponseBuilder {
http::Response::builder()
}
pub struct Body(BodyInner);
impl Body {
pub fn read_to_vec(self) -> Result<Vec<u8>, WcgiError> {
match self.0 {
BodyInner::Empty => Ok(Vec::new()),
BodyInner::Data(data) => Ok(data.into_inner()),
BodyInner::Stream(mut stream) => {
let mut buffer = Vec::new();
stream
.read_to_end(&mut buffer)
.map_err(|err| WcgiError::wrap(err, "could not read request body"))?;
Ok(buffer)
}
}
}
}
impl Read for Body {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.0 {
BodyInner::Empty => Ok(0),
BodyInner::Data(data) => data.read(buf),
BodyInner::Stream(stream) => stream.read(buf),
}
}
}
impl Body {
pub fn new_reader<I: std::io::Read + 'static>(read: I) -> Self {
Self(BodyInner::Stream(BufReader::new(Box::new(read))))
}
pub fn new_bytes(data: impl Into<Vec<u8>>) -> Self {
Self(BodyInner::Data(Cursor::new(data.into())))
}
pub fn new_text<V: Into<String>>(value: V) -> Self {
Self(BodyInner::Data(Cursor::new(value.into().into_bytes())))
}
pub fn empty() -> Self {
Self(BodyInner::Empty)
}
}
enum BodyInner {
Empty,
Data(Cursor<Vec<u8>>),
Stream(BufReader<Box<dyn Read>>),
}
impl From<()> for Body {
fn from(_value: ()) -> Self {
Body(BodyInner::Empty)
}
}
impl From<Vec<u8>> for Body {
fn from(value: Vec<u8>) -> Self {
Body(BodyInner::Data(Cursor::new(value)))
}
}
impl From<String> for Body {
fn from(value: String) -> Self {
Body(BodyInner::Data(Cursor::new(value.into_bytes())))
}
}
pub trait RequestExt {
fn url(&self) -> url::Url;
}
impl RequestExt for Request {
fn url(&self) -> url::Url {
let host = self
.headers()
.get(header::HOST)
.and_then(|v| v.to_str().ok())
.map(|v| v.trim())
.filter(|v| !v.is_empty())
.unwrap_or("nohost");
let path = self.uri();
let raw = format!("http://{host}{path}");
raw.parse().unwrap()
}
}
#[derive(Debug)]
pub struct WcgiError {
message: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl WcgiError {
#[track_caller]
pub fn wrap<E: std::error::Error + Send + Sync + 'static, M: Into<String>>(
error: E,
message: M,
) -> Self {
Self {
message: message.into(),
source: Some(Box::new(error)),
}
}
#[track_caller]
pub fn msg<M: Into<String>>(message: M) -> Self {
Self {
message: message.into(),
source: None,
}
}
pub fn as_std(self) -> StdWcgiError {
StdWcgiError(self)
}
pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(s) => Some(&**s),
None => None,
}
}
}
impl std::fmt::Display for WcgiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.message)?;
if let Some(source) = &self.source {
write!(f, "{source}")?;
}
Ok(())
}
}
impl<E: std::error::Error + Send + Sync + 'static> From<E> for WcgiError {
#[track_caller]
fn from(value: E) -> Self {
Self {
message: String::new(),
source: Some(Box::new(value)),
}
}
}
pub struct StdWcgiError(WcgiError);
impl std::fmt::Debug for StdWcgiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for StdWcgiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for StdWcgiError {}
impl From<WcgiError> for StdWcgiError {
fn from(value: WcgiError) -> Self {
Self(value)
}
}
pub trait WcgiHandler {
fn handle_request(&self, request: Request) -> Result<Response, WcgiError>;
}
impl<F: Fn(Request) -> Result<Response, WcgiError>> WcgiHandler for F {
fn handle_request(&self, request: Request) -> Result<Response, WcgiError> {
(self)(request)
}
}
pub fn serve_once<H: WcgiHandler>(handler: H) {
let request = convert::extract_request().unwrap();
let response = handler.handle_request(request).unwrap();
convert::send_response_and_terminate(response);
}