xitca_web/service/
file.rs1use core::convert::Infallible;
4
5use std::path::PathBuf;
6
7use http_file::{ServeDir as _ServeDir, runtime::AsyncFs};
8use xitca_http::util::service::router::{PathGen, RouteGen};
9
10use crate::service::Service;
11
12pub struct ServeDir<F: AsyncFs = dumb::Dumb> {
14 inner: _ServeDir<F>,
15}
16
17#[cfg(feature = "file")]
18impl ServeDir {
19 pub fn new(path: impl Into<PathBuf>) -> ServeDir<impl AsyncFs + Clone> {
36 ServeDir {
37 inner: _ServeDir::new(path),
38 }
39 }
40
41 #[cfg(feature = "io-uring")]
42 pub fn new_tokio_uring(path: impl Into<PathBuf>) -> ServeDir<impl AsyncFs + Clone> {
43 ServeDir {
44 inner: _ServeDir::new_tokio_uring(path),
45 }
46 }
47}
48
49impl<F> ServeDir<F>
50where
51 F: AsyncFs,
52{
53 pub fn with_fs(path: impl Into<PathBuf>, fs: F) -> Self {
56 ServeDir {
57 inner: _ServeDir::with_fs(path, fs),
58 }
59 }
60}
61
62impl<F> PathGen for ServeDir<F>
63where
64 F: AsyncFs,
65{
66 fn path_gen(&mut self, prefix: &str) -> String {
67 let mut prefix = String::from(prefix);
68 if prefix.ends_with('/') {
69 prefix.pop();
70 }
71
72 prefix.push_str("/*p");
73
74 prefix
75 }
76}
77
78impl<F> RouteGen for ServeDir<F>
79where
80 F: AsyncFs,
81{
82 type Route<R> = R;
83
84 fn route_gen<R>(route: R) -> Self::Route<R> {
85 route
86 }
87}
88
89impl<F> Service for ServeDir<F>
90where
91 F: AsyncFs + Clone,
92{
93 type Response = service::ServeDirService<F>;
94 type Error = Infallible;
95
96 async fn call(&self, _: ()) -> Result<Self::Response, Self::Error> {
97 Ok(service::ServeDirService(self.inner.clone()))
98 }
99}
100
101mod service {
102 use http_file::{ServeDir, ServeError, runtime::AsyncFs};
103
104 use crate::{
105 body::ResponseBody,
106 context::WebContext,
107 error::{Error, ErrorStatus, MatchError, MethodNotAllowed, RouterError},
108 http::{Method, StatusCode, WebResponse},
109 service::Service,
110 };
111
112 pub struct ServeDirService<F: AsyncFs>(pub(super) ServeDir<F>);
113
114 impl<'r, C, B, F> Service<WebContext<'r, C, B>> for ServeDirService<F>
115 where
116 F: AsyncFs,
117 F::File: 'static,
118 {
119 type Response = WebResponse;
120 type Error = RouterError<Error>;
121
122 async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
123 match self.0.serve(ctx.req()).await {
124 Ok(res) => Ok(res.map(ResponseBody::box_stream)),
125 Err(ServeError::NotModified) => {
126 let mut res = ctx.into_response(ResponseBody::none());
127 *res.status_mut() = StatusCode::NOT_MODIFIED;
128 Ok(res)
129 }
130 Err(e) => Err(match e {
131 ServeError::NotFound => RouterError::Match(MatchError),
132 ServeError::MethodNotAllowed => {
133 RouterError::NotAllowed(MethodNotAllowed(Box::new(vec![Method::GET, Method::HEAD])))
134 }
135 ServeError::Io(io) => RouterError::Service(Error::from(io)),
136 _ => RouterError::Service(Error::from(ErrorStatus::bad_request())),
137 }),
138 }
139 }
140 }
141}
142
143mod dumb {
144 use core::future::Ready;
145
146 use std::{io, path::PathBuf};
147
148 use http_file::runtime::{AsyncFs, ChunkRead, Meta};
149
150 use crate::bytes::BytesMut;
151
152 #[derive(Clone, Copy)]
154 pub struct Dumb;
155
156 impl AsyncFs for Dumb {
157 type File = DumbFile;
158
159 type OpenFuture = Ready<io::Result<Self::File>>;
160
161 fn open(&self, _: PathBuf) -> Self::OpenFuture {
162 unimplemented!()
163 }
164 }
165
166 pub struct DumbFile;
168
169 impl Meta for DumbFile {
170 fn modified(&mut self) -> Option<std::time::SystemTime> {
171 unimplemented!()
172 }
173
174 fn len(&self) -> u64 {
175 unimplemented!()
176 }
177 }
178
179 impl ChunkRead for DumbFile {
180 type SeekFuture<'f>
181 = Ready<io::Result<()>>
182 where
183 Self: 'f;
184 type Future = Ready<io::Result<Option<(Self, BytesMut, usize)>>>;
185
186 fn seek(&mut self, _: io::SeekFrom) -> Self::SeekFuture<'_> {
187 unimplemented!()
188 }
189
190 fn next(self, _: xitca_http::bytes::BytesMut) -> Self::Future {
191 unimplemented!()
192 }
193 }
194}