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 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, Error, Result,
45};
46
47#[cfg(feature = "directory-listing")]
48use crate::directory_listing::DirListFmt;
49
50pub struct RequestHandlerOpts {
52 pub root_dir: PathBuf,
55 #[cfg(feature = "experimental")]
56 pub memory_cache: Option<MemCacheOpts>,
58 pub compression: bool,
60 #[cfg(any(
61 feature = "compression",
62 feature = "compression-gzip",
63 feature = "compression-brotli",
64 feature = "compression-zstd",
65 feature = "compression-deflate"
66 ))]
67 pub compression_level: crate::settings::CompressionLevel,
69 pub compression_static: bool,
71 #[cfg(feature = "directory-listing")]
73 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
74 pub dir_listing: bool,
75 #[cfg(feature = "directory-listing")]
77 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
78 pub dir_listing_order: u8,
79 #[cfg(feature = "directory-listing")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
81 pub dir_listing_format: DirListFmt,
83 pub cors: Option<cors::Configured>,
85 pub security_headers: bool,
87 pub cache_control_headers: bool,
89 pub page404: PathBuf,
91 pub page50x: PathBuf,
93 #[cfg(feature = "fallback-page")]
95 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
96 pub page_fallback: Vec<u8>,
97 #[cfg(feature = "basic-auth")]
99 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
100 pub basic_auth: String,
101 pub index_files: Vec<String>,
103 pub log_remote_address: bool,
105 pub log_x_real_ip: bool,
107 pub log_forwarded_for: bool,
109 pub trusted_proxies: Vec<IpAddr>,
111 pub redirect_trailing_slash: bool,
113 pub ignore_hidden_files: bool,
115 pub disable_symlinks: bool,
117 pub health: bool,
119 #[cfg(all(unix, feature = "experimental"))]
121 pub experimental_metrics: bool,
122 pub maintenance_mode: bool,
124 pub maintenance_mode_status: StatusCode,
126 pub maintenance_mode_file: PathBuf,
128
129 pub advanced_opts: Option<Advanced>,
131}
132
133impl Default for RequestHandlerOpts {
134 fn default() -> Self {
135 Self {
136 root_dir: PathBuf::from("./public"),
137 compression: true,
138 compression_static: false,
139 #[cfg(any(
140 feature = "compression",
141 feature = "compression-gzip",
142 feature = "compression-brotli",
143 feature = "compression-zstd",
144 feature = "compression-deflate"
145 ))]
146 compression_level: crate::settings::CompressionLevel::Default,
147 #[cfg(feature = "directory-listing")]
148 dir_listing: false,
149 #[cfg(feature = "directory-listing")]
150 dir_listing_order: 6, #[cfg(feature = "directory-listing")]
152 dir_listing_format: DirListFmt::Html,
153 cors: None,
154 #[cfg(feature = "experimental")]
155 memory_cache: None,
156 security_headers: false,
157 cache_control_headers: true,
158 page404: PathBuf::from("./404.html"),
159 page50x: PathBuf::from("./50x.html"),
160 #[cfg(feature = "fallback-page")]
161 page_fallback: Vec::new(),
162 #[cfg(feature = "basic-auth")]
163 basic_auth: String::new(),
164 index_files: vec!["index.html".into()],
165 log_remote_address: false,
166 log_x_real_ip: false,
167 log_forwarded_for: false,
168 trusted_proxies: Vec::new(),
169 redirect_trailing_slash: true,
170 ignore_hidden_files: false,
171 disable_symlinks: false,
172 health: false,
173 #[cfg(all(unix, feature = "experimental"))]
174 experimental_metrics: false,
175 maintenance_mode: false,
176 maintenance_mode_status: StatusCode::SERVICE_UNAVAILABLE,
177 maintenance_mode_file: PathBuf::new(),
178 advanced_opts: None,
179 }
180 }
181}
182
183pub struct RequestHandler {
185 pub opts: Arc<RequestHandlerOpts>,
187}
188
189impl RequestHandler {
190 pub fn handle<'a>(
192 &'a self,
193 req: &'a mut Request<Body>,
194 remote_addr: Option<SocketAddr>,
195 ) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a {
196 let mut base_path = &self.opts.root_dir;
197 #[cfg(feature = "directory-listing")]
198 let dir_listing = self.opts.dir_listing;
199 #[cfg(feature = "directory-listing")]
200 let dir_listing_order = self.opts.dir_listing_order;
201 #[cfg(feature = "directory-listing")]
202 let dir_listing_format = &self.opts.dir_listing_format;
203 let redirect_trailing_slash = self.opts.redirect_trailing_slash;
204 let compression_static = self.opts.compression_static;
205 let ignore_hidden_files = self.opts.ignore_hidden_files;
206 let disable_symlinks = self.opts.disable_symlinks;
207 let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
208 #[cfg(feature = "experimental")]
209 let memory_cache = self.opts.memory_cache.as_ref();
210
211 log_addr::pre_process(&self.opts, req, remote_addr);
212
213 async move {
214 if !req.method().is_allowed() {
216 return error_page::error_response(
217 req.uri(),
218 req.method(),
219 &StatusCode::METHOD_NOT_ALLOWED,
220 &self.opts.page404,
221 &self.opts.page50x,
222 );
223 }
224
225 if let Some(result) = health::pre_process(&self.opts, req) {
227 return result;
228 }
229
230 #[cfg(all(unix, feature = "experimental"))]
232 if let Some(result) = metrics::pre_process(&self.opts, req) {
233 return result;
234 }
235
236 if let Some(result) = cors::pre_process(&self.opts, req) {
238 return result;
239 }
240
241 #[cfg(feature = "basic-auth")]
243 if let Some(response) = basic_auth::pre_process(&self.opts, req) {
244 return response;
245 }
246
247 if let Some(response) = maintenance_mode::pre_process(&self.opts, req) {
249 return response;
250 }
251
252 if let Some(result) = redirects::pre_process(&self.opts, req) {
254 return result;
255 }
256
257 if let Some(result) = rewrites::pre_process(&self.opts, req) {
259 return result;
260 }
261
262 if let Some(advanced) = &self.opts.advanced_opts {
264 if let Some(root) =
266 virtual_hosts::get_real_root(req, advanced.virtual_hosts.as_deref())
267 {
268 base_path = root;
269 }
270 }
271
272 let index_files = index_files.as_ref();
273
274 let (resp, file_path) = match static_files::handle(&HandleOpts {
276 method: req.method(),
277 headers: req.headers(),
278 #[cfg(feature = "experimental")]
279 memory_cache,
280 base_path,
281 uri_path: req.uri().path(),
282 uri_query: req.uri().query(),
283 #[cfg(feature = "directory-listing")]
284 dir_listing,
285 #[cfg(feature = "directory-listing")]
286 dir_listing_order,
287 #[cfg(feature = "directory-listing")]
288 dir_listing_format,
289 redirect_trailing_slash,
290 compression_static,
291 ignore_hidden_files,
292 index_files,
293 disable_symlinks,
294 })
295 .await
296 {
297 Ok(result) => (result.resp, Some(result.file_path)),
298 Err(status) => (
299 error_page::error_response(
300 req.uri(),
301 req.method(),
302 &status,
303 &self.opts.page404,
304 &self.opts.page50x,
305 )?,
306 None,
307 ),
308 };
309
310 #[cfg(feature = "fallback-page")]
312 let resp = fallback_page::post_process(&self.opts, req, resp)?;
313
314 let resp = cors::post_process(&self.opts, req, resp)?;
316
317 #[cfg(any(
319 feature = "compression",
320 feature = "compression-gzip",
321 feature = "compression-brotli",
322 feature = "compression-zstd",
323 feature = "compression-deflate"
324 ))]
325 let resp = compression_static::post_process(&self.opts, req, resp)?;
326
327 #[cfg(any(
329 feature = "compression",
330 feature = "compression-gzip",
331 feature = "compression-brotli",
332 feature = "compression-zstd",
333 feature = "compression-deflate"
334 ))]
335 let resp = compression::post_process(&self.opts, req, resp)?;
336
337 let resp = control_headers::post_process(&self.opts, req, resp)?;
339
340 let resp = security_headers::post_process(&self.opts, req, resp)?;
342
343 let resp = custom_headers::post_process(&self.opts, req, resp, file_path.as_ref())?;
345
346 Ok(resp)
347 }
348 }
349}