1use hyper::{Request, Response, StatusCode};
10use std::{
11 future::Future,
12 net::{IpAddr, SocketAddr},
13 path::PathBuf,
14 sync::Arc,
15};
16
17use crate::body::Body;
18
19#[cfg(any(
20 feature = "compression",
21 feature = "compression-gzip",
22 feature = "compression-brotli",
23 feature = "compression-zstd",
24 feature = "compression-deflate"
25))]
26use crate::compression;
27
28use crate::compression_static;
29
30#[cfg(feature = "basic-auth")]
31use crate::basic_auth;
32
33#[cfg(feature = "fallback-page")]
34use crate::fallback_page;
35
36#[cfg(feature = "metrics")]
37use crate::metrics;
38
39#[cfg(feature = "mem-cache")]
40use crate::mem_cache::cache::MemCacheOpts;
41
42use crate::{
43 Error, Result, control_headers, cors, custom_headers, error_page,
44 exts::http::MethodExt,
45 health, log_addr, maintenance_mode, redirects, rewrites, security_headers,
46 settings::Advanced,
47 static_files::{self, HandleOpts},
48 text_charset, virtual_hosts,
49};
50
51#[cfg(feature = "directory-listing")]
52use crate::directory_listing::DirListFmt;
53
54#[cfg(feature = "directory-listing-download")]
55use crate::directory_listing::download::DirDownloadFmt;
56
57pub struct RequestHandlerOpts {
59 pub root_dir: PathBuf,
62 #[cfg(feature = "mem-cache")]
63 pub memory_cache: Option<MemCacheOpts>,
65 pub compression: bool,
67 #[cfg(any(
68 feature = "compression",
69 feature = "compression-gzip",
70 feature = "compression-brotli",
71 feature = "compression-zstd",
72 feature = "compression-deflate"
73 ))]
74 pub compression_level: crate::settings::CompressionLevel,
76 pub compression_static: bool,
78 #[cfg(feature = "directory-listing")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
81 pub dir_listing: bool,
82 #[cfg(feature = "directory-listing")]
84 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
85 pub dir_listing_order: u8,
86 #[cfg(feature = "directory-listing")]
87 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
88 pub dir_listing_format: DirListFmt,
90 #[cfg(feature = "directory-listing-download")]
92 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
93 pub dir_listing_download: Vec<DirDownloadFmt>,
94 pub cors: Option<cors::Configured>,
96 pub security_headers: bool,
98 pub cache_control_headers: bool,
100 pub etag: bool,
102 pub page404: PathBuf,
104 pub page50x: PathBuf,
106 #[cfg(feature = "fallback-page")]
108 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
109 pub page_fallback: Vec<u8>,
110 #[cfg(feature = "basic-auth")]
112 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
113 pub basic_auth: String,
114 pub index_files: Vec<String>,
116 pub log_remote_address: bool,
118 pub log_x_real_ip: bool,
120 pub log_forwarded_for: bool,
122 pub trusted_proxies: Vec<IpAddr>,
124 pub redirect_trailing_slash: bool,
126 pub include_hidden: bool,
128 pub follow_symlinks: bool,
130 pub use_relative_root: bool,
132 pub accept_markdown: bool,
134 pub text_charset: bool,
136 pub health: bool,
138 #[cfg(feature = "metrics")]
140 pub metrics_enabled: bool,
141 pub maintenance_mode: bool,
143 pub maintenance_mode_status: StatusCode,
145 pub maintenance_mode_file: PathBuf,
147
148 pub advanced_opts: Option<Advanced>,
150}
151
152impl Default for RequestHandlerOpts {
153 fn default() -> Self {
154 Self {
155 root_dir: PathBuf::from("./public"),
156 compression: true,
157 compression_static: false,
158 #[cfg(any(
159 feature = "compression",
160 feature = "compression-gzip",
161 feature = "compression-brotli",
162 feature = "compression-zstd",
163 feature = "compression-deflate"
164 ))]
165 compression_level: crate::settings::CompressionLevel::Default,
166 #[cfg(feature = "directory-listing")]
167 dir_listing: false,
168 #[cfg(feature = "directory-listing")]
169 dir_listing_order: 6, #[cfg(feature = "directory-listing")]
171 dir_listing_format: DirListFmt::Html,
172 #[cfg(feature = "directory-listing-download")]
173 dir_listing_download: Vec::new(),
174 cors: None,
175 #[cfg(feature = "mem-cache")]
176 memory_cache: None,
177 security_headers: false,
178 cache_control_headers: true,
179 etag: true,
180 page404: PathBuf::from("./404.html"),
181 page50x: PathBuf::from("./50x.html"),
182 #[cfg(feature = "fallback-page")]
183 page_fallback: Vec::new(),
184 #[cfg(feature = "basic-auth")]
185 basic_auth: String::new(),
186 index_files: vec!["index.html".into()],
187 log_remote_address: false,
188 log_x_real_ip: false,
189 log_forwarded_for: false,
190 trusted_proxies: Vec::new(),
191 redirect_trailing_slash: true,
192 include_hidden: true,
193 follow_symlinks: true,
194 use_relative_root: false,
195 accept_markdown: false,
196 text_charset: true,
197 health: false,
198 #[cfg(feature = "metrics")]
199 metrics_enabled: false,
200 maintenance_mode: false,
201 maintenance_mode_status: StatusCode::SERVICE_UNAVAILABLE,
202 maintenance_mode_file: PathBuf::new(),
203 advanced_opts: None,
204 }
205 }
206}
207
208pub struct RequestHandler {
210 pub opts: Arc<RequestHandlerOpts>,
212}
213
214impl RequestHandler {
215 pub fn handle<'a, B>(
217 &'a self,
218 req: &'a mut Request<B>,
219 remote_addr: Option<SocketAddr>,
220 ) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a
221 where
222 B: Send + 'a,
223 {
224 let mut base_path = &self.opts.root_dir;
225 #[cfg(feature = "directory-listing")]
226 let dir_listing = self.opts.dir_listing;
227 #[cfg(feature = "directory-listing")]
228 let dir_listing_order = self.opts.dir_listing_order;
229 #[cfg(feature = "directory-listing")]
230 let dir_listing_format = &self.opts.dir_listing_format;
231 #[cfg(feature = "directory-listing-download")]
232 let dir_listing_download = &self.opts.dir_listing_download;
233 let redirect_trailing_slash = self.opts.redirect_trailing_slash;
234 let compression_static = self.opts.compression_static;
235 let etag = self.opts.etag;
236 let include_hidden = self.opts.include_hidden;
237 let follow_symlinks = self.opts.follow_symlinks;
238 let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
239 #[cfg(feature = "mem-cache")]
240 let memory_cache = self.opts.memory_cache.as_ref();
241
242 log_addr::pre_process(&self.opts, req, remote_addr);
243
244 async move {
245 #[cfg(feature = "metrics")]
246 let req_start = std::time::Instant::now();
247 #[cfg(feature = "metrics")]
248 let metrics_enabled = self.opts.metrics_enabled;
249
250 #[cfg(feature = "metrics")]
251 if metrics_enabled {
252 metrics::inc_requests_inflight();
253 }
254
255 let result: Result<Response<Body>, Error> = async {
256 if !req.method().is_allowed() {
258 return error_page::error_response(
259 req.uri(),
260 req.method(),
261 &StatusCode::METHOD_NOT_ALLOWED,
262 &self.opts.page404,
263 &self.opts.page50x,
264 );
265 }
266
267 if let Some(result) = health::pre_process(&self.opts, req) {
269 return result;
270 }
271
272 if let Some(result) = cors::pre_process(&self.opts, req) {
274 return result;
275 }
276
277 #[cfg(feature = "basic-auth")]
279 if let Some(response) = basic_auth::pre_process(&self.opts, req) {
280 return response;
281 }
282
283 #[cfg(feature = "metrics")]
285 if let Some(result) = metrics::pre_process(&self.opts, req) {
286 return result;
287 }
288
289 if let Some(response) = maintenance_mode::pre_process(&self.opts, req) {
291 return response;
292 }
293
294 if let Some(result) = redirects::pre_process(&self.opts, req) {
296 return result;
297 }
298
299 if let Some(result) = rewrites::pre_process(&self.opts, req) {
301 return result;
302 }
303
304 if let Some(advanced) = &self.opts.advanced_opts {
306 if let Some(root) =
308 virtual_hosts::get_real_root(req, advanced.virtual_hosts.as_deref())
309 {
310 base_path = root;
311 }
312 }
313
314 let index_files = index_files.as_ref();
315
316 let uri_path_md = if self.opts.accept_markdown {
318 crate::markdown::pre_process(
319 req,
320 base_path,
321 req.uri().path(),
322 self.opts.include_hidden,
323 )
324 } else {
325 None
326 };
327 let uri_path = uri_path_md.as_deref().unwrap_or(req.uri().path());
328
329 let (resp, file_path) = match static_files::handle(&HandleOpts {
331 method: req.method(),
332 headers: req.headers(),
333 #[cfg(feature = "mem-cache")]
334 memory_cache,
335 base_path,
336 uri_path,
337 uri_query: req.uri().query(),
338 #[cfg(feature = "directory-listing")]
339 dir_listing,
340 #[cfg(feature = "directory-listing")]
341 dir_listing_order,
342 #[cfg(feature = "directory-listing")]
343 dir_listing_format,
344 #[cfg(feature = "directory-listing-download")]
345 dir_listing_download,
346 redirect_trailing_slash,
347 compression_static,
348 etag,
349 include_hidden,
350 index_files,
351 follow_symlinks,
352 })
353 .await
354 {
355 Ok(result) => (result.resp, Some(result.file_path)),
356 Err(status) => (
357 error_page::error_response(
358 req.uri(),
359 req.method(),
360 &status,
361 &self.opts.page404,
362 &self.opts.page50x,
363 )?,
364 None,
365 ),
366 };
367
368 #[cfg(feature = "fallback-page")]
370 let resp = fallback_page::post_process(&self.opts, req, resp)?;
371
372 let resp = cors::post_process(&self.opts, req, resp)?;
374
375 let resp = crate::markdown::post_process(uri_path_md.is_some(), &self.opts, resp)?;
377
378 let resp = text_charset::post_process(&self.opts, resp)?;
380
381 let resp = compression_static::post_process(&self.opts, req, resp)?;
383
384 #[cfg(any(
386 feature = "compression",
387 feature = "compression-gzip",
388 feature = "compression-brotli",
389 feature = "compression-zstd",
390 feature = "compression-deflate"
391 ))]
392 let resp = compression::post_process(&self.opts, req, resp)?;
393
394 let resp = control_headers::post_process(&self.opts, req, resp)?;
396
397 let resp = security_headers::post_process(&self.opts, req, resp)?;
399
400 let resp = custom_headers::post_process(&self.opts, req, resp, file_path.as_ref())?;
402
403 Ok(resp)
404 }
405 .await;
406
407 #[cfg(feature = "metrics")]
408 if metrics_enabled {
409 metrics::dec_requests_inflight();
410 if let Ok(ref resp) = result {
411 let bytes = resp
412 .headers()
413 .get(http::header::CONTENT_LENGTH)
414 .and_then(|v| v.to_str().ok())
415 .and_then(|v| v.parse::<u64>().ok())
416 .unwrap_or(0);
417 metrics::record_request(
418 req,
419 resp.status(),
420 bytes,
421 req_start.elapsed().as_secs_f64(),
422 );
423 }
424 }
425
426 result
427 }
428 }
429}