http-fs 2.0.1

HTTP File Service library
Documentation
//!Hyper adaptor
//!
//!Implements `hyper::service::Service` for `StaticFiles` allowing to use it to serve http connections
//!
//!## Usage
//!
//!```rust,ignore
//!use hyper::server::conn::http1;
//!use tokio::net::TcpListener;
//!use pin_project_lite::pin_project;
//!
//!use http_fs::config::{self, StaticFileConfig, TokioWorker};
//!use http_fs::StaticFiles;
//!
//!use std::net::{IpAddr, Ipv4Addr, SocketAddr};
//!use std::path::Path;
//!use core::task;
//!use core::pin::Pin;
//!
//!#[derive(Clone)]
//!pub struct DirectoryConfig;
//!impl StaticFileConfig for DirectoryConfig {
//!    type FileService = config::DefaultConfig;
//!    type DirService = config::DefaultConfig;
//!
//!    fn handle_directory(&self, _path: &Path) -> bool {
//!        true
//!    }
//!}
//!
//!//Fucking retarded hyper traits need wrapper
//!pin_project! {
//!    struct IoWrapper<T> {
//!        io: T,
//!    }
//!}
//!
//!impl<T: tokio::io::AsyncRead + Unpin> hyper::rt::Read for IoWrapper<T> {
//!    fn poll_read(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, mut buf: hyper::rt::ReadBufCursor<'_>) -> task::Poll<Result<(), std::io::Error>> {
//!        let n = unsafe {
//!            let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut());
//!            match tokio::io::AsyncRead::poll_read(Pin::new(self.project().io), ctx, &mut tbuf) {
//!                task::Poll::Ready(Ok(())) => tbuf.filled().len(),
//!                other => return other,
//!            }
//!        };
//!
//!        unsafe {
//!            buf.advance(n);
//!        }
//!        task::Poll::Ready(Ok(()))
//!    }
//!}
//!
//!impl<T: tokio::io::AsyncWrite + Unpin> hyper::rt::Write for IoWrapper<T> {
//!    fn poll_write(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, buf: &[u8]) -> task::Poll<Result<usize, std::io::Error>> {
//!        tokio::io::AsyncWrite::poll_write(Pin::new(self.project().io), ctx, buf)
//!    }
//!
//!    fn poll_flush(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Result<(), std::io::Error>> {
//!        tokio::io::AsyncWrite::poll_flush(Pin::new(self.project().io), ctx)
//!    }
//!
//!    fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Result<(), std::io::Error>> {
//!        tokio::io::AsyncWrite::poll_shutdown(Pin::new(self.project().io), ctx)
//!    }
//!
//!    fn is_write_vectored(&self) -> bool {
//!        tokio::io::AsyncWrite::is_write_vectored(&self.io)
//!    }
//!
//!    fn poll_write_vectored(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, bufs: &[std::io::IoSlice<'_>]) -> task::Poll<Result<usize, std::io::Error>> {
//!        tokio::io::AsyncWrite::poll_write_vectored(Pin::new(self.project().io), ctx, bufs)
//!    }
//!}
//!
//!async fn example() {
//!    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);;
//!    let listener = TcpListener::bind(addr).await.expect("bind");
//!    println!("Listening on http://{}", addr);
//!
//!    let static_files = StaticFiles::new(TokioWorker, DirectoryConfig);
//!    loop {
//!        let (io, _) = listener.accept().await.expect("accept");
//!        let io = IoWrapper {
//!            io,
//!        };
//!
//!        let static_files = static_files.clone();
//!        tokio::task::spawn(async move {
//!            http1::Builder::new().serve_connection(io, static_files).await
//!        });
//!    }
//!}
//!```

use hyper::{Request, Response};
use hyper::body::Incoming;
use hyper::service::Service;

use crate::config::{StaticFileConfig, FileServeConfig, FsTaskSpawner};
use crate::StaticFiles;
use crate::Body;

use std::io;
use core::pin::Pin;
use core::convert::Infallible;
use core::{mem, task, future};

impl<W: FsTaskSpawner, C: FileServeConfig> hyper::body::Body for Body<W, C> {
    type Data = hyper::body::Bytes;
    type Error = io::Error;

    #[inline(always)]
    fn poll_frame(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
        match self.get_mut() {
            Self::Full(bytes) => {
                if bytes.is_empty() {
                    task::Poll::Ready(None)
                } else {
                    let mut result = bytes::Bytes::new();
                    mem::swap(&mut result, bytes);
                    task::Poll::Ready(Some(Ok(hyper::body::Frame::data(result))))
                }
            },
            Self::Chunked(chunked) => match future::Future::poll(Pin::new(chunked), ctx) {
                task::Poll::Pending => task::Poll::Pending,
                task::Poll::Ready(Ok(Some(result))) => {
                    task::Poll::Ready(Some(Ok(hyper::body::Frame::data(result))))
                }
                task::Poll::Ready(Ok(None)) => task::Poll::Ready(None),
                task::Poll::Ready(Err(error)) => task::Poll::Ready(Some(Err(error))),
            }
        }
    }

    #[inline(always)]
    fn is_end_stream(&self) -> bool {
        self.is_finished()
    }

    #[inline(always)]
    fn size_hint(&self) -> hyper::body::SizeHint {
        hyper::body::SizeHint::with_exact(self.len())
    }
}

impl<W: FsTaskSpawner, C: StaticFileConfig> Service<Request<Incoming>> for StaticFiles<W, C> {
    type Response = Response<Body<W, C::FileService>>;
    type Error = Infallible;
    type Future = future::Ready<Result<Self::Response, Self::Error>>;

    #[inline(always)]
    fn call(&self, req: Request<Incoming>) -> Self::Future {
        future::ready(Ok(self.serve_http(req.method(), req.uri(), req.headers())))
    }
}