ferron/modules/
static_file_serving.rs

1use std::error::Error;
2use std::fmt::Write;
3use std::io::SeekFrom;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::sync::Arc;
7use std::time::Duration;
8
9use crate::ferron_common::{
10  ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11  ServerModuleHandlers, SocketData,
12};
13use crate::ferron_common::{HyperUpgraded, WithRuntime};
14use async_compression::brotli::EncoderParams;
15use async_compression::tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder};
16use async_compression::zstd::CParameter;
17use async_compression::Level;
18use async_trait::async_trait;
19use chrono::offset::Local;
20use chrono::DateTime;
21use futures_util::TryStreamExt;
22use hashlink::LruCache;
23use http::HeaderValue;
24use http_body_util::{BodyExt, Empty, Full, StreamBody};
25use hyper::body::Bytes;
26use hyper::{body::Frame, Response, StatusCode};
27use hyper::{header, HeaderMap, Method};
28use hyper_tungstenite::HyperWebsocket;
29use sha2::{Digest, Sha256};
30use tokio::fs;
31use tokio::io::{AsyncReadExt, AsyncSeekExt, BufReader};
32use tokio::runtime::Handle;
33use tokio::sync::RwLock;
34use tokio_util::io::ReaderStream;
35
36use crate::ferron_util::generate_directory_listing::generate_directory_listing;
37use crate::ferron_util::ttl_cache::TtlCache;
38
39pub fn server_module_init(
40) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
41  let pathbuf_cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
42  let etag_cache = Arc::new(RwLock::new(LruCache::new(1000)));
43  Ok(Box::new(StaticFileServingModule::new(
44    pathbuf_cache,
45    etag_cache,
46  )))
47}
48
49struct StaticFileServingModule {
50  pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
51  etag_cache: Arc<RwLock<LruCache<String, String>>>,
52}
53
54impl StaticFileServingModule {
55  fn new(
56    pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
57    etag_cache: Arc<RwLock<LruCache<String, String>>>,
58  ) -> Self {
59    Self {
60      pathbuf_cache,
61      etag_cache,
62    }
63  }
64}
65
66impl ServerModule for StaticFileServingModule {
67  fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
68    Box::new(StaticFileServingModuleHandlers {
69      pathbuf_cache: self.pathbuf_cache.clone(),
70      etag_cache: self.etag_cache.clone(),
71      handle,
72    })
73  }
74}
75struct StaticFileServingModuleHandlers {
76  pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
77  etag_cache: Arc<RwLock<LruCache<String, String>>>,
78  handle: Handle,
79}
80
81fn parse_range_header(range_str: &str, default_end: u64) -> Option<(u64, u64)> {
82  if let Some(range_part) = range_str.strip_prefix("bytes=") {
83    let parts: Vec<&str> = range_part.split('-').collect();
84    if parts.len() == 2 {
85      if parts[0].is_empty() {
86        if let Ok(end) = u64::from_str(parts[1]) {
87          return Some((default_end - end + 1, default_end));
88        }
89      } else if parts[1].is_empty() {
90        if let Ok(start) = u64::from_str(parts[0]) {
91          return Some((start, default_end));
92        }
93      } else if !parts[0].is_empty() && !parts[1].is_empty() {
94        if let (Ok(start), Ok(end)) = (u64::from_str(parts[0]), u64::from_str(parts[1])) {
95          return Some((start, end));
96        }
97      }
98    }
99  }
100  None
101}
102
103fn extract_etag_inner(input: &str) -> Option<String> {
104  // Remove the surrounding double quotes
105  let trimmed = input.trim_matches('"');
106
107  // Split the string at the hyphen and take the first part
108  let parts: Vec<&str> = trimmed.split('-').collect();
109  if parts.is_empty() {
110    None
111  } else {
112    Some(parts[0].to_string())
113  }
114}
115
116#[async_trait]
117impl ServerModuleHandlers for StaticFileServingModuleHandlers {
118  async fn request_handler(
119    &mut self,
120    request: RequestData,
121    config: &ServerConfig,
122    _socket_data: &SocketData,
123    _error_logger: &ErrorLogger,
124  ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
125    WithRuntime::new(self.handle.clone(), async move {
126      if let Some(wwwroot) = config["wwwroot"].as_str() {
127        let hyper_request = request.get_hyper_request();
128        let request_path = hyper_request.uri().path();
129        let mut request_path_bytes = request_path.bytes();
130        if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
131          return Ok(
132            ResponseData::builder(request)
133              .status(StatusCode::BAD_REQUEST)
134              .build(),
135          );
136        }
137
138        let original_request_path = request
139          .get_original_url()
140          .map_or(request_path, |u| u.path());
141
142        let cache_key = format!(
143          "{}{}{}",
144          match config["ip"].as_str() {
145            Some(ip) => format!("{ip}-"),
146            None => String::from(""),
147          },
148          match config["domain"].as_str() {
149            Some(domain) => format!("{domain}-"),
150            None => String::from(""),
151          },
152          request_path
153        );
154
155        let rwlock_read = self.pathbuf_cache.read().await;
156        let joined_pathbuf_option = rwlock_read.get(&cache_key);
157        drop(rwlock_read);
158
159        let joined_pathbuf_cached = joined_pathbuf_option.is_some();
160        let mut joined_pathbuf = match joined_pathbuf_option {
161          Some(joined_pathbuf) => joined_pathbuf,
162          None => {
163            let path = Path::new(wwwroot);
164            let mut relative_path = &request_path[1..];
165            while relative_path.as_bytes().first().copied() == Some(b'/') {
166              relative_path = &relative_path[1..];
167            }
168
169            let decoded_relative_path = match urlencoding::decode(relative_path) {
170              Ok(path) => path.to_string(),
171              Err(_) => {
172                return Ok(
173                  ResponseData::builder(request)
174                    .status(StatusCode::BAD_REQUEST)
175                    .build(),
176                );
177              }
178            };
179
180            path.join(decoded_relative_path)
181          }
182        };
183
184        match fs::metadata(&joined_pathbuf).await {
185          Ok(mut metadata) => {
186            if !joined_pathbuf_cached {
187              if metadata.is_dir() {
188                let indexes = vec!["index.html", "index.htm", "index.xhtml"];
189                for index in indexes {
190                  let temp_joined_pathbuf = joined_pathbuf.join(index);
191                  match fs::metadata(&temp_joined_pathbuf).await {
192                    Ok(temp_metadata) => {
193                      if temp_metadata.is_file() {
194                        metadata = temp_metadata;
195                        joined_pathbuf = temp_joined_pathbuf;
196                        break;
197                      }
198                    }
199                    Err(err) => match err.kind() {
200                      tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
201                        continue;
202                      }
203                      tokio::io::ErrorKind::PermissionDenied => {
204                        return Ok(
205                          ResponseData::builder(request)
206                            .status(StatusCode::FORBIDDEN)
207                            .build(),
208                        );
209                      }
210                      _ => Err(err)?,
211                    },
212                  };
213                }
214              }
215              let mut rwlock_write = self.pathbuf_cache.write().await;
216              rwlock_write.cleanup();
217              rwlock_write.insert(cache_key, joined_pathbuf.clone());
218              drop(rwlock_write);
219            }
220
221            if metadata.is_file() {
222              // Check if compression is possible at all
223              let mut compression_possible = false;
224
225              if config["enableCompression"].as_bool() != Some(false) {
226                // A hard-coded list of non-compressible file extension
227                let non_compressible_file_extensions = vec![
228                  "7z",
229                  "air",
230                  "amlx",
231                  "apk",
232                  "apng",
233                  "appinstaller",
234                  "appx",
235                  "appxbundle",
236                  "arj",
237                  "au",
238                  "avif",
239                  "bdoc",
240                  "boz",
241                  "br",
242                  "bz",
243                  "bz2",
244                  "caf",
245                  "class",
246                  "doc",
247                  "docx",
248                  "dot",
249                  "dvi",
250                  "ear",
251                  "epub",
252                  "flv",
253                  "gdoc",
254                  "gif",
255                  "gsheet",
256                  "gslides",
257                  "gz",
258                  "iges",
259                  "igs",
260                  "jar",
261                  "jnlp",
262                  "jp2",
263                  "jpe",
264                  "jpeg",
265                  "jpf",
266                  "jpg",
267                  "jpg2",
268                  "jpgm",
269                  "jpm",
270                  "jpx",
271                  "kmz",
272                  "latex",
273                  "m1v",
274                  "m2a",
275                  "m2v",
276                  "m3a",
277                  "m4a",
278                  "mesh",
279                  "mk3d",
280                  "mks",
281                  "mkv",
282                  "mov",
283                  "mp2",
284                  "mp2a",
285                  "mp3",
286                  "mp4",
287                  "mp4a",
288                  "mp4v",
289                  "mpe",
290                  "mpeg",
291                  "mpg",
292                  "mpg4",
293                  "mpga",
294                  "msg",
295                  "msh",
296                  "msix",
297                  "msixbundle",
298                  "odg",
299                  "odp",
300                  "ods",
301                  "odt",
302                  "oga",
303                  "ogg",
304                  "ogv",
305                  "ogx",
306                  "opus",
307                  "p12",
308                  "pdf",
309                  "pfx",
310                  "pgp",
311                  "pkpass",
312                  "png",
313                  "pot",
314                  "pps",
315                  "ppt",
316                  "pptx",
317                  "qt",
318                  "ser",
319                  "silo",
320                  "sit",
321                  "snd",
322                  "spx",
323                  "stpxz",
324                  "stpz",
325                  "swf",
326                  "tif",
327                  "tiff",
328                  "ubj",
329                  "usdz",
330                  "vbox-extpack",
331                  "vrml",
332                  "war",
333                  "wav",
334                  "weba",
335                  "webm",
336                  "wmv",
337                  "wrl",
338                  "x3dbz",
339                  "x3dvz",
340                  "xla",
341                  "xlc",
342                  "xlm",
343                  "xls",
344                  "xlsx",
345                  "xlt",
346                  "xlw",
347                  "xpi",
348                  "xps",
349                  "zip",
350                  "zst",
351                ];
352                let file_extension = joined_pathbuf
353                  .extension()
354                  .map_or_else(|| "".to_string(), |ext| ext.to_string_lossy().to_string());
355                let file_extension_compressible =
356                  !non_compressible_file_extensions.contains(&(&file_extension as &str));
357
358                if metadata.len() > 256 && file_extension_compressible {
359                  compression_possible = true;
360                }
361              }
362
363              let vary;
364
365              // Handle ETags
366              let mut etag_option = None;
367              if config["enableETag"].as_bool() != Some(false) {
368                let etag_cache_key = format!(
369                  "{}-{}-{}",
370                  joined_pathbuf.to_string_lossy(),
371                  metadata.len(),
372                  match metadata.modified() {
373                    Ok(mtime) => {
374                      let datetime: DateTime<Local> = mtime.into();
375                      datetime.format("%Y-%m-%d %H:%M:%S").to_string()
376                    }
377                    Err(_) => String::from(""),
378                  }
379                );
380                let rwlock_read = self.etag_cache.read().await;
381                // Had to use "peek", since "get" would mutate the LRU cache
382                let etag_locked_option = rwlock_read.peek(&etag_cache_key).cloned();
383                drop(rwlock_read);
384                let etag = match etag_locked_option {
385                  Some(etag) => etag,
386                  None => {
387                    let etag_cache_key_clone = etag_cache_key.clone();
388                    let etag = tokio::task::spawn_blocking(move || {
389                      let mut hasher = Sha256::new();
390                      hasher.update(etag_cache_key_clone);
391                      hasher
392                        .finalize()
393                        .iter()
394                        .fold(String::new(), |mut output, b| {
395                          let _ = write!(output, "{b:02x}");
396                          output
397                        })
398                    })
399                    .await?;
400
401                    let mut rwlock_write = self.etag_cache.write().await;
402                    rwlock_write.insert(etag_cache_key, etag.clone());
403                    drop(rwlock_write);
404
405                    etag
406                  }
407                };
408
409                vary = if compression_possible {
410                  "Accept-Encoding, If-Match, If-None-Match, Range"
411                } else {
412                  "If-Match, If-None-Match, Range"
413                };
414
415                if let Some(if_none_match_value) =
416                  hyper_request.headers().get(header::IF_NONE_MATCH)
417                {
418                  match if_none_match_value.to_str() {
419                    Ok(if_none_match) => {
420                      if let Some(etag_extracted) = extract_etag_inner(if_none_match) {
421                        if etag_extracted == etag {
422                          let etag_original = if_none_match.to_string();
423                          return Ok(
424                            ResponseData::builder(request)
425                              .response(
426                                Response::builder()
427                                  .status(StatusCode::NOT_MODIFIED)
428                                  .header(header::ETAG, etag_original)
429                                  .header(header::VARY, vary)
430                                  .body(Empty::new().map_err(|e| match e {}).boxed())?,
431                              )
432                              .build(),
433                          );
434                        }
435                      }
436                    }
437                    Err(_) => {
438                      let mut header_map = HeaderMap::new();
439                      if let Ok(vary) = HeaderValue::from_str(vary) {
440                        header_map.insert(header::VARY, vary);
441                      }
442                      return Ok(
443                        ResponseData::builder(request)
444                          .status(StatusCode::BAD_REQUEST)
445                          .headers(header_map)
446                          .build(),
447                      );
448                    }
449                  }
450                }
451
452                if let Some(if_match_value) = hyper_request.headers().get(header::IF_MATCH) {
453                  match if_match_value.to_str() {
454                    Ok(if_match) => {
455                      if if_match != "*" {
456                        if let Some(etag_extracted) = extract_etag_inner(if_match) {
457                          if etag_extracted != etag {
458                            let mut header_map = HeaderMap::new();
459                            header_map.insert(header::ETAG, if_match_value.clone());
460                            if let Ok(vary) = HeaderValue::from_str(vary) {
461                              header_map.insert(header::VARY, vary);
462                            }
463                            return Ok(
464                              ResponseData::builder(request)
465                                .status(StatusCode::PRECONDITION_FAILED)
466                                .headers(header_map)
467                                .build(),
468                            );
469                          }
470                        }
471                      }
472                    }
473                    Err(_) => {
474                      let mut header_map = HeaderMap::new();
475                      if let Ok(vary) = HeaderValue::from_str(vary) {
476                        header_map.insert(header::VARY, vary);
477                      }
478                      return Ok(
479                        ResponseData::builder(request)
480                          .status(StatusCode::BAD_REQUEST)
481                          .headers(header_map)
482                          .build(),
483                      );
484                    }
485                  }
486                }
487                etag_option = Some(etag);
488              } else {
489                vary = if compression_possible {
490                  "Accept-Encoding, Range"
491                } else {
492                  "Range"
493                };
494              }
495
496              let content_type_option = new_mime_guess::from_path(&joined_pathbuf)
497                .first()
498                .map(|mime_type| mime_type.to_string());
499
500              let range_header = match hyper_request.headers().get(header::RANGE) {
501                Some(value) => match value.to_str() {
502                  Ok(value) => Some(value),
503                  Err(_) => {
504                    let mut header_map = HeaderMap::new();
505                    if let Ok(vary) = HeaderValue::from_str(vary) {
506                      header_map.insert(header::VARY, vary);
507                    }
508                    return Ok(
509                      ResponseData::builder(request)
510                        .status(StatusCode::BAD_REQUEST)
511                        .headers(header_map)
512                        .build(),
513                    );
514                  }
515                },
516                None => None,
517              };
518
519              if let Some(range_header) = range_header {
520                let file_length = metadata.len();
521                if file_length == 0 {
522                  let mut header_map = HeaderMap::new();
523                  if let Ok(vary) = HeaderValue::from_str(vary) {
524                    header_map.insert(header::VARY, vary);
525                  }
526                  return Ok(
527                    ResponseData::builder(request)
528                      .status(StatusCode::RANGE_NOT_SATISFIABLE)
529                      .headers(header_map)
530                      .build(),
531                  );
532                }
533                if let Some((range_begin, range_end)) =
534                  parse_range_header(range_header, file_length - 1)
535                {
536                  if range_end > file_length - 1
537                    || range_begin > file_length - 1
538                    || range_begin > range_end
539                  {
540                    let mut header_map = HeaderMap::new();
541                    if let Ok(vary) = HeaderValue::from_str(vary) {
542                      header_map.insert(header::VARY, vary);
543                    }
544                    return Ok(
545                      ResponseData::builder(request)
546                        .status(StatusCode::RANGE_NOT_SATISFIABLE)
547                        .headers(header_map)
548                        .build(),
549                    );
550                  }
551
552                  let request_method = hyper_request.method();
553                  let content_length = range_end - range_begin + 1;
554
555                  // Build response
556                  let mut response_builder = Response::builder()
557                    .status(StatusCode::PARTIAL_CONTENT)
558                    .header(header::CONTENT_LENGTH, content_length)
559                    .header(
560                      header::CONTENT_RANGE,
561                      format!("bytes {range_begin}-{range_end}/{file_length}"),
562                    );
563
564                  if let Some(etag) = etag_option {
565                    response_builder = response_builder.header(header::ETAG, format!("\"{etag}\""));
566                  }
567
568                  if let Some(content_type) = content_type_option {
569                    response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
570                  }
571
572                  response_builder = response_builder.header(header::VARY, vary);
573
574                  let response = match request_method {
575                    &Method::HEAD => {
576                      response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
577                    }
578                    _ => {
579                      // Open file for reading
580                      let mut file = match fs::File::open(joined_pathbuf).await {
581                        Ok(file) => file,
582                        Err(err) => match err.kind() {
583                          tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
584                            return Ok(
585                              ResponseData::builder(request)
586                                .status(StatusCode::NOT_FOUND)
587                                .build(),
588                            );
589                          }
590                          tokio::io::ErrorKind::PermissionDenied => {
591                            return Ok(
592                              ResponseData::builder(request)
593                                .status(StatusCode::FORBIDDEN)
594                                .build(),
595                            );
596                          }
597                          _ => Err(err)?,
598                        },
599                      };
600
601                      // Seek and limit the file reader
602                      file.seek(SeekFrom::Start(range_begin)).await?;
603                      let file_limited = file.take(content_length);
604
605                      // Use BufReader for better performance.
606                      let file_bufreader = BufReader::with_capacity(12800, file_limited);
607
608                      // Construct a boxed body
609                      let reader_stream = ReaderStream::new(file_bufreader);
610                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
611                      let boxed_body = stream_body.boxed();
612
613                      response_builder.body(boxed_body)?
614                    }
615                  };
616
617                  return Ok(ResponseData::builder(request).response(response).build());
618                } else {
619                  let mut header_map = HeaderMap::new();
620                  if let Ok(vary) = HeaderValue::from_str(vary) {
621                    header_map.insert(header::VARY, vary);
622                  }
623
624                  return Ok(
625                    ResponseData::builder(request)
626                      .status(StatusCode::RANGE_NOT_SATISFIABLE)
627                      .headers(header_map)
628                      .build(),
629                  );
630                }
631              } else {
632                let mut use_gzip = false;
633                let mut use_deflate = false;
634                let mut use_brotli = false;
635                let mut use_zstd = false;
636
637                if compression_possible {
638                  let user_agent = match hyper_request.headers().get(header::USER_AGENT) {
639                    Some(user_agent_value) => user_agent_value.to_str().unwrap_or_default(),
640                    None => "",
641                  };
642
643                  // Some web browsers have broken HTTP compression handling
644                  let is_netscape_4_broken_html_compression = user_agent.starts_with("Mozilla/4.");
645                  let is_netscape_4_broken_compression = match user_agent.strip_prefix("Mozilla/4.")
646                  {
647                    Some(stripped_user_agent) => matches!(
648                      stripped_user_agent.chars().nth(0),
649                      Some('6') | Some('7') | Some('8')
650                    ),
651                    None => false,
652                  };
653                  let is_w3m_broken_html_compression = user_agent.starts_with("w3m/");
654                  if !(content_type_option == Some("text/html".to_string())
655                    && (is_netscape_4_broken_html_compression || is_w3m_broken_html_compression))
656                    && !is_netscape_4_broken_compression
657                  {
658                    let accept_encoding = match hyper_request.headers().get(header::ACCEPT_ENCODING)
659                    {
660                      Some(header_value) => header_value.to_str().unwrap_or_default(),
661                      None => "",
662                    };
663
664                    // Checking the Accept-Encoding header naively...
665                    if accept_encoding.contains("br") {
666                      use_brotli = true;
667                    } else if accept_encoding.contains("zstd") {
668                      use_zstd = true;
669                    } else if accept_encoding.contains("deflate") {
670                      use_deflate = true;
671                    } else if accept_encoding.contains("gzip") {
672                      use_gzip = true;
673                    }
674                  }
675                }
676
677                let request_method = hyper_request.method();
678                let content_length = metadata.len();
679
680                // Build response
681                let mut response_builder = Response::builder()
682                  .status(StatusCode::OK)
683                  .header(header::ACCEPT_RANGES, "bytes");
684
685                if let Some(etag) = etag_option {
686                  if use_brotli {
687                    response_builder =
688                      response_builder.header(header::ETAG, format!("\"{etag}-br\""));
689                  } else if use_zstd {
690                    response_builder =
691                      response_builder.header(header::ETAG, format!("\"{etag}-zstd\""));
692                  } else if use_deflate {
693                    response_builder =
694                      response_builder.header(header::ETAG, format!("\"{etag}-deflate\""));
695                  } else if use_gzip {
696                    response_builder =
697                      response_builder.header(header::ETAG, format!("\"{etag}-gzip\""));
698                  } else {
699                    response_builder = response_builder.header(header::ETAG, format!("\"{etag}\""));
700                  }
701                }
702
703                response_builder = response_builder.header(header::VARY, vary);
704
705                if let Some(content_type) = content_type_option {
706                  response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
707                }
708
709                if use_brotli {
710                  response_builder = response_builder.header(header::CONTENT_ENCODING, "br");
711                } else if use_zstd {
712                  response_builder = response_builder.header(header::CONTENT_ENCODING, "zstd");
713                } else if use_deflate {
714                  response_builder = response_builder.header(header::CONTENT_ENCODING, "deflate");
715                } else if use_gzip {
716                  response_builder = response_builder.header(header::CONTENT_ENCODING, "gzip");
717                } else {
718                  // Content-Length header + HTTP compression = broken HTTP responses!
719                  response_builder =
720                    response_builder.header(header::CONTENT_LENGTH, content_length);
721                }
722
723                let response = match request_method {
724                  &Method::HEAD => {
725                    response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
726                  }
727                  _ => {
728                    // Open file for reading
729                    let file = match fs::File::open(joined_pathbuf).await {
730                      Ok(file) => file,
731                      Err(err) => match err.kind() {
732                        tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
733                          return Ok(
734                            ResponseData::builder(request)
735                              .status(StatusCode::NOT_FOUND)
736                              .build(),
737                          );
738                        }
739                        tokio::io::ErrorKind::PermissionDenied => {
740                          return Ok(
741                            ResponseData::builder(request)
742                              .status(StatusCode::FORBIDDEN)
743                              .build(),
744                          );
745                        }
746                        _ => Err(err)?,
747                      },
748                    };
749
750                    // Use BufReader for better performance.
751                    let file_bufreader = BufReader::with_capacity(12800, file);
752
753                    // Construct a boxed body
754                    let boxed_body = if use_brotli {
755                      // Use Brotli compression with moderate quality (4) for good compression/speed balance
756                      // Also, set the window size and block size to optimize compression, and reduce memory usage
757                      let reader_stream =
758                        ReaderStream::new(BrotliEncoder::with_quality_and_params(
759                          file_bufreader,
760                          Level::Precise(4),
761                          EncoderParams::default().window_size(17).block_size(18),
762                        ));
763                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
764                      stream_body.boxed()
765                    } else if use_zstd {
766                      // Limit the Zstandard window size to 128K (2^17 bytes) to support many HTTP clients
767                      // Also, set the size of the initial probe table to reduce memory usage
768                      let reader_stream = ReaderStream::new(ZstdEncoder::with_quality_and_params(
769                        file_bufreader,
770                        Level::Default,
771                        &[CParameter::window_log(17), CParameter::hash_log(10)],
772                      ));
773                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
774                      stream_body.boxed()
775                    } else if use_deflate {
776                      let reader_stream = ReaderStream::new(DeflateEncoder::new(file_bufreader));
777                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
778                      stream_body.boxed()
779                    } else if use_gzip {
780                      let reader_stream = ReaderStream::new(GzipEncoder::new(file_bufreader));
781                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
782                      stream_body.boxed()
783                    } else {
784                      let reader_stream = ReaderStream::new(file_bufreader);
785                      let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
786                      stream_body.boxed()
787                    };
788
789                    response_builder.body(boxed_body)?
790                  }
791                };
792
793                return Ok(ResponseData::builder(request).response(response).build());
794              }
795            } else if metadata.is_dir() {
796              if config["enableDirectoryListing"].as_bool() == Some(true) {
797                let joined_maindesc_pathbuf = joined_pathbuf.join(".maindesc");
798                let directory = match fs::read_dir(joined_pathbuf).await {
799                  Ok(directory) => directory,
800                  Err(err) => match err.kind() {
801                    tokio::io::ErrorKind::NotFound => {
802                      return Ok(
803                        ResponseData::builder(request)
804                          .status(StatusCode::NOT_FOUND)
805                          .build(),
806                      );
807                    }
808                    tokio::io::ErrorKind::PermissionDenied => {
809                      return Ok(
810                        ResponseData::builder(request)
811                          .status(StatusCode::FORBIDDEN)
812                          .build(),
813                      );
814                    }
815                    _ => Err(err)?,
816                  },
817                };
818
819                let description = (fs::read_to_string(joined_maindesc_pathbuf).await).ok();
820
821                let directory_listing_html =
822                  generate_directory_listing(directory, original_request_path, description).await?;
823                let content_length: Option<u64> = directory_listing_html.len().try_into().ok();
824
825                let mut response_builder = Response::builder().status(StatusCode::OK);
826
827                if let Some(content_length) = content_length {
828                  response_builder = response_builder.header(header::CONTENT_LENGTH, content_length)
829                }
830                response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
831
832                let response = response_builder.body(
833                  Full::new(Bytes::from(directory_listing_html))
834                    .map_err(|e| match e {})
835                    .boxed(),
836                )?;
837
838                return Ok(ResponseData::builder(request).response(response).build());
839              } else {
840                return Ok(
841                  ResponseData::builder(request)
842                    .status(StatusCode::FORBIDDEN)
843                    .build(),
844                );
845              }
846            } else {
847              return Ok(
848                ResponseData::builder(request)
849                  .status(StatusCode::NOT_IMPLEMENTED)
850                  .build(),
851              );
852            }
853          }
854          Err(err) => match err.kind() {
855            tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
856              return Ok(
857                ResponseData::builder(request)
858                  .status(StatusCode::NOT_FOUND)
859                  .build(),
860              );
861            }
862            tokio::io::ErrorKind::PermissionDenied => {
863              return Ok(
864                ResponseData::builder(request)
865                  .status(StatusCode::FORBIDDEN)
866                  .build(),
867              );
868            }
869            _ => Err(err)?,
870          },
871        }
872      }
873
874      Ok(ResponseData::builder(request).build())
875    })
876    .await
877  }
878
879  async fn proxy_request_handler(
880    &mut self,
881    request: RequestData,
882    _config: &ServerConfig,
883    _socket_data: &SocketData,
884    _error_logger: &ErrorLogger,
885  ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
886    Ok(ResponseData::builder(request).build())
887  }
888
889  async fn response_modifying_handler(
890    &mut self,
891    response: HyperResponse,
892  ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
893    Ok(response)
894  }
895
896  async fn proxy_response_modifying_handler(
897    &mut self,
898    response: HyperResponse,
899  ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
900    Ok(response)
901  }
902
903  async fn connect_proxy_request_handler(
904    &mut self,
905    _upgraded_request: HyperUpgraded,
906    _connect_address: &str,
907    _config: &ServerConfig,
908    _socket_data: &SocketData,
909    _error_logger: &ErrorLogger,
910  ) -> Result<(), Box<dyn Error + Send + Sync>> {
911    Ok(())
912  }
913
914  fn does_connect_proxy_requests(&mut self) -> bool {
915    false
916  }
917
918  async fn websocket_request_handler(
919    &mut self,
920    _websocket: HyperWebsocket,
921    _uri: &hyper::Uri,
922    _headers: &hyper::HeaderMap,
923    _config: &ServerConfig,
924    _socket_data: &SocketData,
925    _error_logger: &ErrorLogger,
926  ) -> Result<(), Box<dyn Error + Send + Sync>> {
927    Ok(())
928  }
929
930  fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
931    false
932  }
933}