1use hyper::{Body, Request, Response, StatusCode};
10use std::{
11 future::Future,
12 net::{IpAddr, SocketAddr},
13 path::PathBuf,
14 sync::Arc,
15};
16
17#[cfg(any(
18 feature = "compression",
19 feature = "compression-gzip",
20 feature = "compression-brotli",
21 feature = "compression-zstd",
22 feature = "compression-deflate"
23))]
24use crate::{compression, compression_static};
25
26#[cfg(feature = "basic-auth")]
27use crate::basic_auth;
28
29#[cfg(feature = "fallback-page")]
30use crate::fallback_page;
31
32#[cfg(all(unix, feature = "experimental"))]
33use crate::metrics;
34
35#[cfg(feature = "experimental")]
36use crate::mem_cache::cache::MemCacheOpts;
37
38use crate::{
39 Error, Result, control_headers, cors, custom_headers, error_page, health,
40 http_ext::MethodExt,
41 log_addr, maintenance_mode, redirects, rewrites, security_headers,
42 settings::Advanced,
43 static_files::{self, HandleOpts},
44 virtual_hosts,
45};
46
47#[cfg(feature = "directory-listing")]
48use crate::directory_listing::DirListFmt;
49
50#[cfg(feature = "directory-listing-download")]
51use crate::directory_listing_download::DirDownloadFmt;
52
53pub struct RequestHandlerOpts {
55 pub root_dir: PathBuf,
58 #[cfg(feature = "experimental")]
59 pub memory_cache: Option<MemCacheOpts>,
61 pub compression: bool,
63 #[cfg(any(
64 feature = "compression",
65 feature = "compression-gzip",
66 feature = "compression-brotli",
67 feature = "compression-zstd",
68 feature = "compression-deflate"
69 ))]
70 pub compression_level: crate::settings::CompressionLevel,
72 pub compression_static: bool,
74 #[cfg(feature = "directory-listing")]
76 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
77 pub dir_listing: bool,
78 #[cfg(feature = "directory-listing")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
81 pub dir_listing_order: u8,
82 #[cfg(feature = "directory-listing")]
83 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
84 pub dir_listing_format: DirListFmt,
86 #[cfg(feature = "directory-listing-download")]
88 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
89 pub dir_listing_download: Vec<DirDownloadFmt>,
90 pub cors: Option<cors::Configured>,
92 pub security_headers: bool,
94 pub cache_control_headers: bool,
96 pub page404: PathBuf,
98 pub page50x: PathBuf,
100 #[cfg(feature = "fallback-page")]
102 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
103 pub page_fallback: Vec<u8>,
104 #[cfg(feature = "basic-auth")]
106 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
107 pub basic_auth: String,
108 pub index_files: Vec<String>,
110 pub log_remote_address: bool,
112 pub log_x_real_ip: bool,
114 pub log_forwarded_for: bool,
116 pub trusted_proxies: Vec<IpAddr>,
118 pub redirect_trailing_slash: bool,
120 pub ignore_hidden_files: bool,
122 pub disable_symlinks: bool,
124 pub accept_markdown: bool,
126 pub health: bool,
128 #[cfg(all(unix, feature = "experimental"))]
130 pub experimental_metrics: bool,
131 pub maintenance_mode: bool,
133 pub maintenance_mode_status: StatusCode,
135 pub maintenance_mode_file: PathBuf,
137
138 pub advanced_opts: Option<Advanced>,
140}
141
142impl Default for RequestHandlerOpts {
143 fn default() -> Self {
144 Self {
145 root_dir: PathBuf::from("./public"),
146 compression: true,
147 compression_static: false,
148 #[cfg(any(
149 feature = "compression",
150 feature = "compression-gzip",
151 feature = "compression-brotli",
152 feature = "compression-zstd",
153 feature = "compression-deflate"
154 ))]
155 compression_level: crate::settings::CompressionLevel::Default,
156 #[cfg(feature = "directory-listing")]
157 dir_listing: false,
158 #[cfg(feature = "directory-listing")]
159 dir_listing_order: 6, #[cfg(feature = "directory-listing")]
161 dir_listing_format: DirListFmt::Html,
162 #[cfg(feature = "directory-listing-download")]
163 dir_listing_download: Vec::new(),
164 cors: None,
165 #[cfg(feature = "experimental")]
166 memory_cache: None,
167 security_headers: false,
168 cache_control_headers: true,
169 page404: PathBuf::from("./404.html"),
170 page50x: PathBuf::from("./50x.html"),
171 #[cfg(feature = "fallback-page")]
172 page_fallback: Vec::new(),
173 #[cfg(feature = "basic-auth")]
174 basic_auth: String::new(),
175 index_files: vec!["index.html".into()],
176 log_remote_address: false,
177 log_x_real_ip: false,
178 log_forwarded_for: false,
179 trusted_proxies: Vec::new(),
180 redirect_trailing_slash: true,
181 ignore_hidden_files: false,
182 disable_symlinks: false,
183 accept_markdown: false,
184 health: false,
185 #[cfg(all(unix, feature = "experimental"))]
186 experimental_metrics: false,
187 maintenance_mode: false,
188 maintenance_mode_status: StatusCode::SERVICE_UNAVAILABLE,
189 maintenance_mode_file: PathBuf::new(),
190 advanced_opts: None,
191 }
192 }
193}
194
195pub struct RequestHandler {
197 pub opts: Arc<RequestHandlerOpts>,
199}
200
201impl RequestHandler {
202 pub fn handle<'a>(
204 &'a self,
205 req: &'a mut Request<Body>,
206 remote_addr: Option<SocketAddr>,
207 ) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a {
208 let mut base_path = &self.opts.root_dir;
209 #[cfg(feature = "directory-listing")]
210 let dir_listing = self.opts.dir_listing;
211 #[cfg(feature = "directory-listing")]
212 let dir_listing_order = self.opts.dir_listing_order;
213 #[cfg(feature = "directory-listing")]
214 let dir_listing_format = &self.opts.dir_listing_format;
215 #[cfg(feature = "directory-listing-download")]
216 let dir_listing_download = &self.opts.dir_listing_download;
217 let redirect_trailing_slash = self.opts.redirect_trailing_slash;
218 let compression_static = self.opts.compression_static;
219 let ignore_hidden_files = self.opts.ignore_hidden_files;
220 let disable_symlinks = self.opts.disable_symlinks;
221 let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
222 #[cfg(feature = "experimental")]
223 let memory_cache = self.opts.memory_cache.as_ref();
224
225 log_addr::pre_process(&self.opts, req, remote_addr);
226
227 async move {
228 if !req.method().is_allowed() {
230 return error_page::error_response(
231 req.uri(),
232 req.method(),
233 &StatusCode::METHOD_NOT_ALLOWED,
234 &self.opts.page404,
235 &self.opts.page50x,
236 );
237 }
238
239 if let Some(result) = health::pre_process(&self.opts, req) {
241 return result;
242 }
243
244 #[cfg(all(unix, feature = "experimental"))]
246 if let Some(result) = metrics::pre_process(&self.opts, req) {
247 return result;
248 }
249
250 if let Some(result) = cors::pre_process(&self.opts, req) {
252 return result;
253 }
254
255 #[cfg(feature = "basic-auth")]
257 if let Some(response) = basic_auth::pre_process(&self.opts, req) {
258 return response;
259 }
260
261 if let Some(response) = maintenance_mode::pre_process(&self.opts, req) {
263 return response;
264 }
265
266 if let Some(result) = redirects::pre_process(&self.opts, req) {
268 return result;
269 }
270
271 if let Some(result) = rewrites::pre_process(&self.opts, req) {
273 return result;
274 }
275
276 if let Some(advanced) = &self.opts.advanced_opts {
278 if let Some(root) =
280 virtual_hosts::get_real_root(req, advanced.virtual_hosts.as_deref())
281 {
282 base_path = root;
283 }
284 }
285
286 let index_files = index_files.as_ref();
287
288 let uri_path_md = if self.opts.accept_markdown {
290 crate::markdown::pre_process(req, base_path, req.uri().path())
291 } else {
292 None
293 };
294 let uri_path = uri_path_md.as_deref().unwrap_or(req.uri().path());
295
296 let (resp, file_path) = match static_files::handle(&HandleOpts {
298 method: req.method(),
299 headers: req.headers(),
300 #[cfg(feature = "experimental")]
301 memory_cache,
302 base_path,
303 uri_path,
304 uri_query: req.uri().query(),
305 #[cfg(feature = "directory-listing")]
306 dir_listing,
307 #[cfg(feature = "directory-listing")]
308 dir_listing_order,
309 #[cfg(feature = "directory-listing")]
310 dir_listing_format,
311 #[cfg(feature = "directory-listing-download")]
312 dir_listing_download,
313 redirect_trailing_slash,
314 compression_static,
315 ignore_hidden_files,
316 index_files,
317 disable_symlinks,
318 })
319 .await
320 {
321 Ok(result) => (result.resp, Some(result.file_path)),
322 Err(status) => (
323 error_page::error_response(
324 req.uri(),
325 req.method(),
326 &status,
327 &self.opts.page404,
328 &self.opts.page50x,
329 )?,
330 None,
331 ),
332 };
333
334 #[cfg(feature = "fallback-page")]
336 let resp = fallback_page::post_process(&self.opts, req, resp)?;
337
338 let resp = cors::post_process(&self.opts, req, resp)?;
340
341 let resp = crate::markdown::post_process(uri_path_md.is_some(), &self.opts, resp)?;
343
344 #[cfg(any(
346 feature = "compression",
347 feature = "compression-gzip",
348 feature = "compression-brotli",
349 feature = "compression-zstd",
350 feature = "compression-deflate"
351 ))]
352 let resp = compression_static::post_process(&self.opts, req, resp)?;
353
354 #[cfg(any(
356 feature = "compression",
357 feature = "compression-gzip",
358 feature = "compression-brotli",
359 feature = "compression-zstd",
360 feature = "compression-deflate"
361 ))]
362 let resp = compression::post_process(&self.opts, req, resp)?;
363
364 let resp = control_headers::post_process(&self.opts, req, resp)?;
366
367 let resp = security_headers::post_process(&self.opts, req, resp)?;
369
370 let resp = custom_headers::post_process(&self.opts, req, resp, file_path.as_ref())?;
372
373 Ok(resp)
374 }
375 }
376}