http_fs/adaptors/hyper.rs
1//!Hyper adaptor
2//!
3//!Implements `hyper::service::Service` for `StaticFiles` allowing to use it to serve http connections
4//!
5//!## Usage
6//!
7//!```rust,ignore
8//!use hyper::server::conn::http1;
9//!use tokio::net::TcpListener;
10//!use pin_project_lite::pin_project;
11//!
12//!use http_fs::config::{self, StaticFileConfig, TokioWorker};
13//!use http_fs::StaticFiles;
14//!
15//!use std::net::{IpAddr, Ipv4Addr, SocketAddr};
16//!use std::path::Path;
17//!use core::task;
18//!use core::pin::Pin;
19//!
20//!#[derive(Clone)]
21//!pub struct DirectoryConfig;
22//!impl StaticFileConfig for DirectoryConfig {
23//! type FileService = config::DefaultConfig;
24//! type DirService = config::DefaultConfig;
25//!
26//! fn handle_directory(&self, _path: &Path) -> bool {
27//! true
28//! }
29//!}
30//!
31//!//Fucking retarded hyper traits need wrapper
32//!pin_project! {
33//! struct IoWrapper<T> {
34//! io: T,
35//! }
36//!}
37//!
38//!impl<T: tokio::io::AsyncRead + Unpin> hyper::rt::Read for IoWrapper<T> {
39//! fn poll_read(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, mut buf: hyper::rt::ReadBufCursor<'_>) -> task::Poll<Result<(), std::io::Error>> {
40//! let n = unsafe {
41//! let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut());
42//! match tokio::io::AsyncRead::poll_read(Pin::new(self.project().io), ctx, &mut tbuf) {
43//! task::Poll::Ready(Ok(())) => tbuf.filled().len(),
44//! other => return other,
45//! }
46//! };
47//!
48//! unsafe {
49//! buf.advance(n);
50//! }
51//! task::Poll::Ready(Ok(()))
52//! }
53//!}
54//!
55//!impl<T: tokio::io::AsyncWrite + Unpin> hyper::rt::Write for IoWrapper<T> {
56//! fn poll_write(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, buf: &[u8]) -> task::Poll<Result<usize, std::io::Error>> {
57//! tokio::io::AsyncWrite::poll_write(Pin::new(self.project().io), ctx, buf)
58//! }
59//!
60//! fn poll_flush(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Result<(), std::io::Error>> {
61//! tokio::io::AsyncWrite::poll_flush(Pin::new(self.project().io), ctx)
62//! }
63//!
64//! fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Result<(), std::io::Error>> {
65//! tokio::io::AsyncWrite::poll_shutdown(Pin::new(self.project().io), ctx)
66//! }
67//!
68//! fn is_write_vectored(&self) -> bool {
69//! tokio::io::AsyncWrite::is_write_vectored(&self.io)
70//! }
71//!
72//! fn poll_write_vectored(self: Pin<&mut Self>, ctx: &mut task::Context<'_>, bufs: &[std::io::IoSlice<'_>]) -> task::Poll<Result<usize, std::io::Error>> {
73//! tokio::io::AsyncWrite::poll_write_vectored(Pin::new(self.project().io), ctx, bufs)
74//! }
75//!}
76//!
77//!async fn example() {
78//! let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);;
79//! let listener = TcpListener::bind(addr).await.expect("bind");
80//! println!("Listening on http://{}", addr);
81//!
82//! let static_files = StaticFiles::new(TokioWorker, DirectoryConfig);
83//! loop {
84//! let (io, _) = listener.accept().await.expect("accept");
85//! let io = IoWrapper {
86//! io,
87//! };
88//!
89//! let static_files = static_files.clone();
90//! tokio::task::spawn(async move {
91//! http1::Builder::new().serve_connection(io, static_files).await
92//! });
93//! }
94//!}
95//!```
96
97use hyper::{Request, Response};
98use hyper::body::Incoming;
99use hyper::service::Service;
100
101use crate::config::{StaticFileConfig, FileServeConfig, FsTaskSpawner};
102use crate::StaticFiles;
103use crate::Body;
104
105use std::io;
106use core::pin::Pin;
107use core::convert::Infallible;
108use core::{mem, task, future};
109
110impl<W: FsTaskSpawner, C: FileServeConfig> hyper::body::Body for Body<W, C> {
111 type Data = hyper::body::Bytes;
112 type Error = io::Error;
113
114 #[inline(always)]
115 fn poll_frame(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
116 match self.get_mut() {
117 Self::Full(bytes) => {
118 if bytes.is_empty() {
119 task::Poll::Ready(None)
120 } else {
121 let mut result = bytes::Bytes::new();
122 mem::swap(&mut result, bytes);
123 task::Poll::Ready(Some(Ok(hyper::body::Frame::data(result))))
124 }
125 },
126 Self::Chunked(chunked) => match future::Future::poll(Pin::new(chunked), ctx) {
127 task::Poll::Pending => task::Poll::Pending,
128 task::Poll::Ready(Ok(Some(result))) => {
129 task::Poll::Ready(Some(Ok(hyper::body::Frame::data(result))))
130 }
131 task::Poll::Ready(Ok(None)) => task::Poll::Ready(None),
132 task::Poll::Ready(Err(error)) => task::Poll::Ready(Some(Err(error))),
133 }
134 }
135 }
136
137 #[inline(always)]
138 fn is_end_stream(&self) -> bool {
139 self.is_finished()
140 }
141
142 #[inline(always)]
143 fn size_hint(&self) -> hyper::body::SizeHint {
144 hyper::body::SizeHint::with_exact(self.len())
145 }
146}
147
148impl<W: FsTaskSpawner, C: StaticFileConfig> Service<Request<Incoming>> for StaticFiles<W, C> {
149 type Response = Response<Body<W, C::FileService>>;
150 type Error = Infallible;
151 type Future = future::Ready<Result<Self::Response, Self::Error>>;
152
153 #[inline(always)]
154 fn call(&self, req: Request<Incoming>) -> Self::Future {
155 future::ready(Ok(self.serve_http(req.method(), req.uri(), req.headers())))
156 }
157}