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