Repository: static-web-server/static-web-server (language: rust)

Issue:
Add in memory cache to SWS
**Is your feature request related to a problem? Please describe.**

SWS does not have in memory caching for static assets.  

**Describe the solution you'd like**
On first request files will be taken from disk and cached for a given period in memory, all other subsequent will get files  from memory cache.  

This can be especially effective for scenarios where one have remote volumes bindings, like [CSI](https://github.com/container-storage-interface/spec/blob/master/spec.md) 

I think this feature shall be enabled with help of Environment variable, which will specify Time to live in seconds like `IN_MEMORY_CACHE_TTL=3600`

A possible solution could be to implement in memory caching with help of [freqfs](https://docs.rs/freqfs/0.4.3/freqfs/)?



Discussion / hints:
FYI there is already PoC in progress on https://github.com/static-web-server/static-web-server/pull/328 using the [SIEVE](https://cachemon.github.io/SIEVE-website/) eviction algorithm.

Fix patch:
diff --git a/.github/workflows/devel.yml b/.github/workflows/devel.yml
index 51f8e8c5..874c14be 100644
--- a/.github/workflows/devel.yml
+++ b/.github/workflows/devel.yml
@@ -72,7 +72,7 @@ jobs:
           # We test against the latest and minimum Rust stable version.
           - build: pinned
             os: ubuntu-22.04
-            rust: 1.74.0
+            rust: 1.76.0
           # Some of our release builds are generated by a nightly compiler to take
           # advantage of the latest optimizations/compile time improvements.
           - build: linux-musl
diff --git a/Cargo.lock b/Cargo.lock
index c0017a01..fbc011cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -235,6 +235,12 @@ version = "3.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
+[[package]]
+name = "bytecount"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
@@ -247,6 +253,46 @@ version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
 
+[[package]]
+name = "camino"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "castaway"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
+dependencies = [
+ "rustversion",
+]
+
 [[package]]
 name = "cc"
 version = "1.1.10"
@@ -331,6 +377,20 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
 
+[[package]]
+name = "compact_str"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
 [[package]]
 name = "const_fn"
 version = "0.4.10"
@@ -381,6 +441,21 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
 [[package]]
 name = "crypto-common"
 version = "0.1.6"
@@ -391,6 +466,19 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
 [[package]]
 name = "deranged"
 version = "0.3.11"
@@ -416,6 +504,31 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "error-chain"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
 [[package]]
 name = "flate2"
 version = "1.0.31"
@@ -533,6 +646,12 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
 [[package]]
 name = "globset"
 version = "0.4.14"
@@ -800,6 +919,12 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
 [[package]]
 name = "listenfd"
 version = "1.0.1"
@@ -871,6 +996,21 @@ dependencies = [
  "unicase",
 ]
 
+[[package]]
+name = "mini-moka"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-utils",
+ "dashmap",
+ "skeptic",
+ "smallvec",
+ "tagptr",
+ "triomphe",
+]
+
 [[package]]
 name = "miniz_oxide"
 version = "0.7.4"
@@ -1073,6 +1213,17 @@ version = "2.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.36"
@@ -1141,6 +1292,19 @@ version = "0.1.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
 [[package]]
 name = "rustls"
 version = "0.23.12"
@@ -1183,18 +1347,42 @@ dependencies = [
  "untrusted",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+
 [[package]]
 name = "ryu"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.207"
@@ -1321,6 +1509,21 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "skeptic"
+version = "0.13.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
+dependencies = [
+ "bytecount",
+ "cargo_metadata",
+ "error-chain",
+ "glob",
+ "pulldown-cmark",
+ "tempfile",
+ "walkdir",
+]
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -1363,6 +1566,7 @@ dependencies = [
  "bytes",
  "chrono",
  "clap",
+ "compact_str",
  "form_urlencoded",
  "futures-util",
  "globset",
@@ -1374,6 +1578,7 @@ dependencies = [
  "listenfd",
  "maud",
  "mime_guess",
+ "mini-moka",
  "percent-encoding",
  "pin-project",
  "prometheus",
@@ -1397,6 +1602,12 @@ dependencies = [
  "windows-service",
 ]
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "strsim"
 version = "0.11.1"
@@ -1420,6 +1631,25 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "tagptr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
+[[package]]
+name = "tempfile"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.63"
@@ -1692,6 +1922,12 @@ dependencies = [
  "tracing-log",
 ]
 
+[[package]]
+name = "triomphe"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
+
 [[package]]
 name = "try-lock"
 version = "0.2.5"
@@ -1816,6 +2052,16 @@ version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
 [[package]]
 name = "want"
 version = "0.3.1"
@@ -1908,6 +2154,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 0529b594..5bc4225b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "static-web-server"
 version = "2.32.2"
 edition = "2021"
-rust-version = "1.74.0"
+rust-version = "1.76.0"
 authors = ["Jose Quintana <https://joseluisq.net>"]
 license = "MIT OR Apache-2.0"
 description = "A cross-platform, high-performance and asynchronous web server for static files-serving."
@@ -67,6 +67,7 @@ bcrypt = { version = "0.15", optional = true }
 bytes = "1.6"
 chrono = { version = "0.4", default-features = false, features = ["std", "clock"], optional = true }
 clap = { version = "4.5", features = ["derive", "env"] }
+compact_str = "0.8"
 form_urlencoded = "1.2"
 futures-util = { version = "0.3", default-features = false }
 globset = { version = "0.4", features = ["serde1"] }
@@ -78,6 +79,7 @@ lazy_static = "1.5"
 listenfd = "1.0"
 maud = { version = "0.26", optional = true }
 mime_guess = "2.0"
+mini-moka = "0.10.3"
 percent-encoding = "2.3"
 pin-project = "1.1"
 regex = "1.10"
@@ -86,13 +88,13 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
 serde_ignored = "0.1"
 serde_json = "1.0"
 serde_repr = "0.1"
+shadow-rs = "0.29"
 tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"] }
 tokio-rustls = { version = "0.26", optional = true, default-features = false, features = ["logging", "tls12", "ring"] }
 tokio-util = { version = "0.7", default-features = false, features = ["io"] }
 toml = "0.8"
 tracing = { version = "0.1", default-features = false, features = ["std"] }
 tracing-subscriber = { version = "0.3", default-features = false, features = ["smallvec", "registry", "parking_lot", "fmt", "ansi", "tracing-log"] }
-shadow-rs = "0.29"
 
 [target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.tikv-jemallocator]
 version = "0.6"
diff --git a/docs/content/building-from-source.md b/docs/content/building-from-source.md
index 25fd058f..c2b8ef3b 100644
--- a/docs/content/building-from-source.md
+++ b/docs/content/building-from-source.md
@@ -6,7 +6,7 @@ Follow these instructions to either build **`SWS`** project from the source or t
 
 If you want to build **SWS** from the source, all you need is a [Rust 2021 Edition](https://blog.rust-lang.org/2021/05/11/edition-2021.html) installed.
 
-So make sure to install Rust [1.74.0](https://blog.rust-lang.org/2023/11/16/Rust-1.74.0.html) or newer (or nightly) along with [the toolchain(s)](https://rust-lang.github.io/rustup/concepts/toolchains.html) of your preference.
+So make sure to install Rust [1.76.0](https://blog.rust-lang.org/2024/02/08/Rust-1.76.0.html) or newer (or nightly) along with [the toolchain(s)](https://rust-lang.github.io/rustup/concepts/toolchains.html) of your preference.
 
 Then clone the repository and use [Cargo](https://doc.rust-lang.org/cargo/) to build the project from the source.
 
@@ -16,7 +16,7 @@ cd static-web-server
 cargo build --release
 ```
 
-Finally, the release binary should be available at `target/release/static-web-server` or under your toolchain directory chosen.
+Finally, the release binary should be available at `target/https://blog.rust-lang.org/2024/02/08/Rust-1.76.0.htmlrelease/static-web-server` or under your toolchain directory chosen.
 
 !!! info "Don't use the project's `Makefile`"
     Please don't use the project's `Makefile` since it's only intended for development and some on-demand tasks.
diff --git a/src/cors.rs b/src/cors.rs
index 23bc8e7c..50aff321 100644
--- a/src/cors.rs
+++ b/src/cors.rs
@@ -214,7 +214,7 @@ impl Default for Cors {
 }
 
 #[derive(Clone, Debug)]
-/// CORS is configurated.
+/// CORS configured.
 pub struct Configured {
     cors: Cors,
     allowed_headers: AccessControlAllowHeaders,
diff --git a/src/handler.rs b/src/handler.rs
index fa1581c4..5096f242 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -30,7 +30,9 @@ use crate::metrics;
 use crate::{
     control_headers, cors, custom_headers, error_page, health,
     http_ext::MethodExt,
-    log_addr, maintenance_mode, redirects, rewrites, security_headers,
+    log_addr, maintenance_mode,
+    mem_cache::cache::MemCacheOpts,
+    redirects, rewrites, security_headers,
     settings::Advanced,
     static_files::{self, HandleOpts},
     virtual_hosts, Error, Result,
@@ -44,6 +46,8 @@ pub struct RequestHandlerOpts {
     // General options
     /// Root directory of static files.
     pub root_dir: PathBuf,
+    /// In-memory cache feature (experimental).
+    pub memory_cache: Option<MemCacheOpts>,
     /// Compression feature.
     pub compression: bool,
     #[cfg(any(
@@ -134,6 +138,7 @@ impl Default for RequestHandlerOpts {
             #[cfg(feature = "directory-listing")]
             dir_listing_format: DirListFmt::Html,
             cors: None,
+            memory_cache: None,
             security_headers: false,
             cache_control_headers: true,
             page404: PathBuf::from("./404.html"),
@@ -183,6 +188,7 @@ impl RequestHandler {
         let ignore_hidden_files = self.opts.ignore_hidden_files;
         let disable_symlinks = self.opts.disable_symlinks;
         let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
+        let memory_cache = self.opts.memory_cache.as_ref();
 
         log_addr::pre_process(&self.opts, req, remote_addr);
 
@@ -251,6 +257,7 @@ impl RequestHandler {
             let (resp, file_path) = match static_files::handle(&HandleOpts {
                 method: req.method(),
                 headers: req.headers(),
+                memory_cache,
                 base_path,
                 uri_path: req.uri().path(),
                 uri_query: req.uri().query(),
diff --git a/src/lib.rs b/src/lib.rs
index 900e0051..5c9a356c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -177,6 +177,7 @@ pub(crate) mod http_ext;
 pub mod https_redirect;
 pub(crate) mod log_addr;
 pub mod maintenance_mode;
+pub(crate) mod mem_cache;
 #[cfg(all(unix, feature = "experimental"))]
 pub(crate) mod metrics;
 pub mod redirects;
diff --git a/src/mem_cache/cache.rs b/src/mem_cache/cache.rs
new file mode 100644
index 00000000..a2e55e9e
--- /dev/null
+++ b/src/mem_cache/cache.rs
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+// This file is part of Static Web Server.
+// See https://static-web-server.net/ for more information
+// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
+
+//! It provides in-memory files cache functionality with expiration policy support
+//! such as Time to live (TTL) and Time to idle (TTI).
+//!
+//! Admission to a cache is controlled by the Least Frequently Used (LFU) policy
+//! and the eviction from a cache is controlled by the Least Recently Used (LRU) policy.
+//!
+
+use bytes::Bytes;
+use compact_str::CompactString;
+use headers::{
+    AcceptRanges, ContentLength, ContentRange, ContentType, HeaderMap, HeaderMapExt, LastModified,
+};
+use hyper::{Body, Response, StatusCode};
+use mini_moka::sync::Cache;
+use std::io::{Read, Seek, SeekFrom};
+use std::path::Path;
+use std::sync::{Arc, OnceLock};
+use std::time::Duration;
+use tokio::sync::Semaphore;
+
+use crate::conditional_headers::{ConditionalBody, ConditionalHeaders};
+use crate::fs::stream::FileStream;
+use crate::handler::RequestHandlerOpts;
+use crate::response::{bytes_range, BadRangeError};
+use crate::Result;
+
+/// Global cache that stores all files in memory.
+/// It provides expiration policies like Time to live (TTL) and Time to idle (TTI) support.
+pub(crate) static CACHE_STORE: OnceLock<Cache<CompactString, Arc<MemFile>>> = OnceLock::new();
+
+/// A single cache permit to allow reading a file once.
+static CACHE_PERMIT: Semaphore = Semaphore::const_new(1);
+
+/// It defines the in-memory files cache options.
+pub struct MemCacheOpts {
+    /// The maximum size per file in bytes.
+    pub max_file_size: u64,
+}
+
+impl MemCacheOpts {
+    /// Creates a new instance of `MemCacheOpts`.
+    #[inline]
+    pub fn new(max_file_size: u64) -> Self {
+        Self {
+            max_file_size: 1024 * 1024 * max_file_size,
+        }
+    }
+}
+
+/// Make sure to initialize the in-memory cache store.
+pub(crate) fn init(handler_opts: &mut RequestHandlerOpts) -> Result {
+    if let Some(advanced_opts) = handler_opts.advanced_opts.as_ref() {
+        if let Some(opts) = advanced_opts.memory_cache.as_ref() {
+            // TODO: define maximum values
+
+            // Default 256 entries
+            let capacity = opts.capacity.unwrap_or(256);
+            // Default 1h
+            let ttl = opts.ttl.unwrap_or(3600);
+            // Default 5min
+            let tti = opts.tti.unwrap_or(300);
+            // Default 8mb
+            let max_file_size = opts.max_file_size.unwrap_or(8192);
+
+            server_info!(
+                "in-memory cache (experimental): enabled=true, capacity={capacity}, ttl={ttl}, tti={tti}, max_file_size={max_file_size}"
+            );
+
+            let mem_opts = MemCacheOpts::new(max_file_size);
+
+            let cache = Cache::builder()
+                .max_capacity(capacity)
+                // Time to live (TTL): 30 minutes
+                .time_to_live(Duration::from_secs(ttl))
+                // Time to idle (TTI):  5 minutes
+                .time_to_idle(Duration::from_secs(tti))
+                .build();
+
+            if CACHE_STORE.set(cache).is_err() {
+                bail!("unable to initialize the in-memory cache store")
+            }
+
+            handler_opts.memory_cache = Some(mem_opts);
+
+            return Ok(());
+        }
+    }
+
+    server_info!("in-memory cache (experimental): enabled=false");
+
+    Ok(())
+}
+
+/// Try to get the file in a form of a response from the cache store by a path or
+/// acquires a permit to ensure to hold until the file is read first (once).
+///
+/// If the file is not found in the cache store then
+/// a cache permit is acquired internally (one at a time)
+/// to allow the caller to read the file first.
+/// Once the file is read on caller's side then the permit is dropped.
+pub(crate) async fn get_or_acquire(
+    file_path: &Path,
+    headers_opt: &HeaderMap,
+) -> Option<Result<Response<Body>, StatusCode>> {
+    let file_path_str = file_path.to_str().or(None)?;
+
+    let store = CACHE_STORE.get().unwrap();
+    match store.get::<CompactString>(&file_path_str.into()) {
+        Some(mem_file) => {
+            tracing::debug!(
+                "file `{}` found in the in-memory cache store, returning it directly",
+                file_path_str
+            );
+            Some(mem_file.response_body(headers_opt))
+        }
+        _ => {
+            tracing::debug!(
+                "file `{}` was not found in the in-memory cache store, continuing",
+                file_path_str
+            );
+            // If a file is not found in the store then continue
+            // with the normal flow and wait on first file read
+            if let Err(err) = CACHE_PERMIT.acquire().await {
+                tracing::error!("error trying to acquire permit on first read: {:?}", err);
+                return Some(Err(StatusCode::INTERNAL_SERVER_ERROR));
+            }
+            None
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct MemFileTempOpts {
+    pub(crate) file_path: String,
+    pub(crate) content_type: ContentType,
+    pub(crate) last_modified: Option<LastModified>,
+}
+
+impl MemFileTempOpts {
+    pub(crate) fn new(
+        file_path: String,
+        content_type: ContentType,
+        last_modified: Option<LastModified>,
+    ) -> Self {
+        Self {
+            file_path,
+            content_type,
+            last_modified,
+        }
+    }
+}
+
+/// In-memory file representation to be store in the cache.
+#[derive(Debug)]
+pub(crate) struct MemFile {
+    /// Bytes of the the current file.
+    data: Bytes,
+    /// Buffer size for the current file.
+    buf_size: usize,
+    /// `Content-Type` header for the current file.
+    content_type: ContentType,
+    /// `Last Modified` header for the current file.
+    last_modified: Option<LastModified>,
+}
+
+impl MemFile {
+    #[inline]
+    pub(crate) fn new(
+        data: Bytes,
+        buf_size: usize,
+        content_type: ContentType,
+        last_modified: Option<LastModified>,
+    ) -> Self {
+        Self {
+            data,
+            buf_size,
+            content_type,
+            last_modified,
+        }
+    }
+
+    pub(crate) fn response_body(&self, headers: &HeaderMap) -> Result<Response<Body>, StatusCode> {
+        let conditionals = ConditionalHeaders::new(headers);
+        let modified = self.last_modified;
+
+        match conditionals.check(modified) {
+            ConditionalBody::NoBody(resp) => Ok(resp),
+            ConditionalBody::WithBody(range) => {
+                let mem_buf = self.data.clone();
+                let mut len = mem_buf.len() as u64;
+                let mut reader = std::io::Cursor::new(mem_buf);
+                let buf_size = self.buf_size;
+
+                bytes_range(range, len)
+                    .map(|(start, end)| {
+                        match reader.seek(SeekFrom::Start(start)) {
+                            Ok(_) => (),
+                            Err(err) => {
+                                tracing::error!("seek file from start error: {:?}", err);
+                                return Err(StatusCode::INTERNAL_SERVER_ERROR);
+                            }
+                        };
+
+                        let sub_len = end - start;
+                        let reader = reader.take(sub_len);
+
+                        let body = Body::wrap_stream(FileStream { reader, buf_size });
+                        let mut resp = Response::new(body);
+
+                        if sub_len != len {
+                            *resp.status_mut() = StatusCode::PARTIAL_CONTENT;
+                            resp.headers_mut().typed_insert(
+                                match ContentRange::bytes(start..end, len) {
+                                    Ok(range) => range,
+                                    Err(err) => {
+                                        tracing::error!("invalid content range error: {:?}", err);
+                                        let mut resp = Response::new(Body::empty());
+                                        *resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
+                                        resp.headers_mut()
+                                            .typed_insert(ContentRange::unsatisfied_bytes(len));
+                                        return Ok(resp);
+                                    }
+                                },
+                            );
+
+                            len = sub_len;
+                        }
+
+                        resp.headers_mut().typed_insert(ContentLength(len));
+                        resp.headers_mut().typed_insert(self.content_type.clone());
+                        resp.headers_mut().typed_insert(AcceptRanges::bytes());
+
+                        if let Some(last_modified) = modified {
+                            resp.headers_mut().typed_insert(last_modified);
+                        }
+
+                        Ok(resp)
+                    })
+                    .unwrap_or_else(|BadRangeError| {
+                        // bad byte range
+                        let mut resp = Response::new(Body::empty());
+                        *resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
+                        resp.headers_mut()
+                            .typed_insert(ContentRange::unsatisfied_bytes(len));
+                        Ok(resp)
+                    })
+            }
+        }
+    }
+}
diff --git a/src/mem_cache/mod.rs b/src/mem_cache/mod.rs
new file mode 100644
index 00000000..9fcffe9a
--- /dev/null
+++ b/src/mem_cache/mod.rs
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+// This file is part of Static Web Server.
+// See https://static-web-server.net/ for more information
+// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
+
+//! A module that provides several In-Memory cache facilities.
+//!
+
+pub(crate) mod cache;
+pub(crate) mod stream;
diff --git a/src/mem_cache/stream.rs b/src/mem_cache/stream.rs
new file mode 100644
index 00000000..e7b426ed
--- /dev/null
+++ b/src/mem_cache/stream.rs
@@ -0,0 +1,71 @@
+use bytes::{BufMut, Bytes, BytesMut};
+use futures_util::Stream;
+use std::io::Read;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+
+use crate::mem_cache::cache::{MemFile, MemFileTempOpts, CACHE_STORE};
+use crate::Result;
+
+#[derive(Debug)]
+pub(crate) struct MemCacheFileStream<T> {
+    pub(crate) reader: T,
+    pub(crate) buf_size: usize,
+    pub(crate) mem_opts: Option<MemFileTempOpts>,
+    pub(crate) mem_buf: Option<BytesMut>,
+}
+
+impl<T: Read + Unpin> Stream for MemCacheFileStream<T> {
+    type Item = Result<Bytes>;
+
+    fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+        let buf_size = self.buf_size;
+        let mut buf = BytesMut::zeroed(buf_size);
+        let pinned = Pin::into_inner(self);
+
+        match pinned.reader.read(&mut buf[..]) {
+            Ok(n) => {
+                if n == 0 {
+                    Poll::Ready(None)
+                } else {
+                    buf.truncate(n);
+                    let buf = buf.freeze();
+
+                    // Handle in-memory cache if enabled
+                    if pinned.mem_opts.is_some() && pinned.mem_buf.is_some() {
+                        let buf_data_mut = pinned.mem_buf.as_mut().unwrap();
+                        buf_data_mut.put(buf.clone());
+
+                        // If file size is reached then proceed cache it
+                        if buf_data_mut.len() == buf_data_mut.capacity() {
+                            let buf_data = pinned.mem_buf.take().unwrap().freeze();
+                            let mem_file_opts = pinned.mem_opts.as_ref().unwrap();
+
+                            let mem_file = Arc::new(MemFile::new(
+                                buf_data,
+                                buf_size,
+                                mem_file_opts.content_type.to_owned(),
+                                mem_file_opts.last_modified,
+                            ));
+
+                            let file_path = mem_file_opts.file_path.as_str();
+                            tracing::debug!(
+                                "file `{}` is inserted to the in-memory cache store",
+                                file_path
+                            );
+
+                            CACHE_STORE
+                                .get()
+                                .unwrap()
+                                .insert(file_path.into(), mem_file);
+                        }
+                    }
+
+                    Poll::Ready(Some(Ok(buf)))
+                }
+            }
+            Err(err) => Poll::Ready(Some(Err(anyhow::Error::from(err)))),
+        }
+    }
+}
diff --git a/src/response.rs b/src/response.rs
index fda95e4c..3abc694b 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -6,6 +6,7 @@
 //! Module to transition files into HTTP responses.
 //!
 
+use bytes::BytesMut;
 use headers::{
     AcceptRanges, ContentLength, ContentRange, ContentType, HeaderMapExt, LastModified, Range,
 };
@@ -17,6 +18,10 @@ use std::path::PathBuf;
 
 use crate::conditional_headers::{ConditionalBody, ConditionalHeaders};
 use crate::fs::stream::{optimal_buf_size, FileStream};
+use crate::mem_cache::{
+    cache::{MemCacheOpts, MemFileTempOpts},
+    stream::MemCacheFileStream,
+};
 
 /// It converts a file object into a corresponding HTTP response or
 /// returns an error holding an HTTP status code otherwise.
@@ -25,6 +30,7 @@ pub(crate) fn response_body(
     path: &PathBuf,
     meta: &Metadata,
     conditionals: ConditionalHeaders,
+    memory_cache: Option<&MemCacheOpts>,
 ) -> Result<Response<Body>, StatusCode> {
     let mut len = meta.len();
     let modified = meta.modified().ok().map(LastModified::from);
@@ -33,6 +39,7 @@ pub(crate) fn response_body(
         ConditionalBody::NoBody(resp) => Ok(resp),
         ConditionalBody::WithBody(range) => {
             let buf_size = optimal_buf_size(meta);
+
             bytes_range(range, len)
                 .map(|(start, end)| {
                     match file.seek(SeekFrom::Start(start)) {
@@ -45,9 +52,46 @@ pub(crate) fn response_body(
 
                     let sub_len = end - start;
                     let reader = BufReader::new(file).take(sub_len);
-                    let stream = FileStream { reader, buf_size };
 
-                    let body = Body::wrap_stream(stream);
+                    let mime = mime_guess::from_path(path).first_or_octet_stream();
+                    let content_type = ContentType::from(mime);
+
+                    // Add the file to the in-memory cache only under these conditions:
+                    // - if the feature is enabled and
+                    // - if the file size does not exceed the maximum permitted and
+                    // - if the file is not found in the cache store
+                    // TODO: make this a feature
+                    let body = match memory_cache {
+                        // Cache the file only if does not exceed the max size
+                        Some(mem_cache_opts) if len <= mem_cache_opts.max_file_size => {
+                            match path.to_str() {
+                                Some(path_str) => {
+                                    let content_type = content_type.clone();
+                                    let file_path = path_str.to_owned();
+
+                                    let mem_buf = Some(BytesMut::with_capacity(len as usize));
+                                    let mem_opts = Some(MemFileTempOpts::new(
+                                        file_path,
+                                        content_type,
+                                        modified,
+                                    ));
+                                    tracing::debug!(
+                                        "preparing `{}` to be inserted in-memory cache store",
+                                        path_str,
+                                    );
+                                    Body::wrap_stream(MemCacheFileStream {
+                                        reader,
+                                        buf_size,
+                                        mem_opts,
+                                        mem_buf,
+                                    })
+                                }
+                                _ => Body::wrap_stream(FileStream { reader, buf_size }),
+                            }
+                        }
+                        _ => Body::wrap_stream(FileStream { reader, buf_size }),
+                    };
+
                     let mut resp = Response::new(body);
 
                     if sub_len != len {
@@ -69,10 +113,8 @@ pub(crate) fn response_body(
                         len = sub_len;
                     }
 
-                    let mime = mime_guess::from_path(path).first_or_octet_stream();
-
                     resp.headers_mut().typed_insert(ContentLength(len));
-                    resp.headers_mut().typed_insert(ContentType::from(mime));
+                    resp.headers_mut().typed_insert(content_type);
                     resp.headers_mut().typed_insert(AcceptRanges::bytes());
 
                     if let Some(last_modified) = modified {
@@ -81,7 +123,7 @@ pub(crate) fn response_body(
 
                     Ok(resp)
                 })
-                .unwrap_or_else(|BadRange| {
+                .unwrap_or_else(|BadRangeError| {
                     // bad byte range
                     let mut resp = Response::new(Body::empty());
                     *resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
@@ -93,11 +135,11 @@ pub(crate) fn response_body(
     }
 }
 
-struct BadRange;
+pub(crate) struct BadRangeError;
 
 /// It handles the `Range` header returning the corresponding start/end-range bytes
 /// or returns an error for bad ranges otherwise.
-fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRange> {
+pub(crate) fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRangeError> {
     let range = if let Some(range) = range {
         range
     } else {
@@ -114,7 +156,7 @@ fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRang
                 (Bound::Included(a), Bound::Included(b)) => {
                     // `start` can not be greater than `end`
                     if a > b {
-                        return Err(BadRange);
+                        return Err(BadRangeError);
                     }
                     // For the special case where b == the file size
                     (a, if b == max_len { b } else { b + 1 })
@@ -151,11 +193,11 @@ fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRang
                 return Ok((start, max_len));
             }
 
-            Err(BadRange)
+            Err(BadRangeError)
         })
         .next()
-        // NOTE: default to `BadRange` in case of wrong `Range` bytes format
-        .unwrap_or(Err(BadRange));
+        // NOTE: default to `BadRangeError` in case of wrong `Range` bytes format
+        .unwrap_or(Err(BadRangeError));
 
     resp
 }
diff --git a/src/server.rs b/src/server.rs
index 7ea53f29..bf7e7f1a 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -13,6 +13,7 @@ use std::sync::Arc;
 use tokio::sync::{watch::Receiver, Mutex};
 
 use crate::handler::{RequestHandler, RequestHandlerOpts};
+
 #[cfg(all(unix, feature = "experimental"))]
 use crate::metrics;
 #[cfg(any(unix, windows))]
@@ -44,7 +45,8 @@ use crate::{compression, compression_static};
 use crate::basic_auth;
 
 use crate::{
-    control_headers, cors, health, helpers, log_addr, maintenance_mode, security_headers, Settings,
+    control_headers, cors, health, helpers, log_addr, maintenance_mode, mem_cache,
+    security_headers, Settings,
 };
 use crate::{service::RouterService, Context, Result};
 
@@ -338,6 +340,9 @@ impl Server {
         // Security Headers option
         security_headers::init(general.security_headers, &mut handler_opts);
 
+        // In-Memory cache option
+        mem_cache::cache::init(&mut handler_opts)?;
+
         // Create a service router for Hyper
         let router_service = RouterService::new(RequestHandler {
             opts: Arc::from(handler_opts),
diff --git a/src/settings/file.rs b/src/settings/file.rs
index 1f656b4b..3cc417aa 100644
--- a/src/settings/file.rs
+++ b/src/settings/file.rs
@@ -160,6 +160,20 @@ pub struct VirtualHosts {
     pub root: Option<PathBuf>,
 }
 
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "kebab-case")]
+/// Represents the in-memory file cache feature.
+pub struct MemoryCache {
+    /// Maximum capacity entries of the memory cache store.
+    pub capacity: Option<u64>,
+    /// Time to live in seconds of a cached file entry.
+    pub ttl: Option<u64>,
+    /// Time to idle in seconds of a cached file entry.
+    pub tti: Option<u64>,
+    /// Maximum size in bytes for a file entry to be cached.
+    pub max_file_size: Option<u64>,
+}
+
 /// Advanced server options only available in configuration file mode.
 #[derive(Debug, Serialize, Deserialize, Clone)]
 #[serde(rename_all = "kebab-case")]
@@ -172,6 +186,8 @@ pub struct Advanced {
     pub redirects: Option<Vec<Redirects>>,
     /// Name-based virtual hosting
     pub virtual_hosts: Option<Vec<VirtualHosts>>,
+    /// In-memory cache feature (experimental).
+    pub memory_cache: Option<MemoryCache>,
 }
 
 /// General server options available in configuration file mode.
@@ -363,6 +379,9 @@ pub struct General {
     /// Custom maintenance mode HTML file.
     pub maintenance_mode_file: Option<PathBuf>,
 
+    /// In-memory files cache feature.
+    pub memory_cache: Option<bool>,
+
     #[cfg(windows)]
     /// windows service feature.
     pub windows_service: Option<bool>,
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 7666b295..9b598de0 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -25,7 +25,7 @@ pub use cli::Commands;
 
 use cli::General;
 
-use self::file::{RedirectsKind, Settings as FileSettings};
+use self::file::{MemoryCache, RedirectsKind, Settings as FileSettings};
 
 #[cfg(any(
     feature = "compression",
@@ -85,6 +85,8 @@ pub struct Advanced {
     pub redirects: Option<Vec<Redirects>>,
     /// Name-based virtual hosting
     pub virtual_hosts: Option<Vec<VirtualHosts>>,
+    /// In-memory cache feature (experimental).
+    pub memory_cache: Option<MemoryCache>,
 }
 
 /// The full server CLI and File options.
@@ -569,6 +571,7 @@ impl Settings {
                     rewrites: rewrites_entries,
                     redirects: redirects_entries,
                     virtual_hosts: vhosts_entries,
+                    memory_cache: advanced.memory_cache,
                 });
             }
         } else if log_init {
diff --git a/src/static_files.rs b/src/static_files.rs
index a72fc7ac..2a26fffb 100644
--- a/src/static_files.rs
+++ b/src/static_files.rs
@@ -19,6 +19,7 @@ use crate::conditional_headers::ConditionalHeaders;
 use crate::fs::meta::{try_metadata, try_metadata_with_html_suffix, FileMetadata};
 use crate::fs::path::{sanitize_path, PathExt};
 use crate::http_ext::{MethodExt, HTTP_SUPPORTED_METHODS};
+use crate::mem_cache::cache::{self, MemCacheOpts};
 use crate::response::response_body;
 use crate::Result;
 
@@ -44,6 +45,8 @@ const DEFAULT_INDEX_FILES: &[&str; 1] = &["index.html"];
 pub struct HandleOpts<'a> {
     /// Request method.
     pub method: &'a Method,
+    /// In-memory files cache feature (experimental).
+    pub memory_cache: Option<&'a MemCacheOpts>,
     /// Request headers.
     pub headers: &'a HeaderMap<HeaderValue>,
     /// Request base path.
@@ -98,6 +101,24 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<StaticFileResponse, Sta
     let headers_opt = opts.headers;
     let mut file_path = sanitize_path(opts.base_path, uri_path)?;
 
+    // In-memory file cache feature with eviction policy
+    let memory_cache = opts.memory_cache;
+    if memory_cache.is_some() {
+        // NOTE: we only support a default auto index for directory requests
+        // when working on a memory-cache context.
+        if opts.redirect_trailing_slash && uri_path.ends_with('/') {
+            file_path.push("index.html");
+        }
+
+        if let Some(result) = cache::get_or_acquire(file_path.as_path(), headers_opt).await {
+            return Ok(StaticFileResponse {
+                resp: result?,
+                // file_path: resp_file_path,
+                file_path,
+            });
+        }
+    }
+
     let FileMetadata {
         file_path,
         metadata,
@@ -181,7 +202,13 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<StaticFileResponse, Sta
     // Check for a pre-compressed file variant if present under the `opts.compression_static` context
     if let Some(precompressed_meta) = precompressed_variant {
         let (precomp_path, precomp_encoding) = precompressed_meta;
-        let mut resp = file_reply(headers_opt, file_path, &metadata, Some(precomp_path))?;
+        let mut resp = file_reply(
+            headers_opt,
+            file_path,
+            &metadata,
+            Some(precomp_path),
+            memory_cache,
+        )?;
 
         // Prepare corresponding headers to let know how to decode the payload
         resp.headers_mut().remove(CONTENT_LENGTH);
@@ -203,7 +230,7 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<StaticFileResponse, Sta
         });
     }
 
-    let resp = file_reply(headers_opt, file_path, &metadata, None)?;
+    let resp = file_reply(headers_opt, file_path, &metadata, None, memory_cache)?;
 
     Ok(StaticFileResponse {
         resp,
@@ -409,12 +436,13 @@ fn file_reply<'a>(
     path: &'a PathBuf,
     meta: &'a Metadata,
     path_precompressed: Option<PathBuf>,
+    memory_cache: Option<&'a MemCacheOpts>,
 ) -> Result<Response<Body>, StatusCode> {
     let conditionals = ConditionalHeaders::new(headers);
     let file_path = path_precompressed.as_ref().unwrap_or(path);
 
     match File::open(file_path) {
-        Ok(file) => response_body(file, path, meta, conditionals),
+        Ok(file) => response_body(file, path, meta, conditionals, memory_cache),
         Err(err) => {
             let status = match err.kind() {
                 io::ErrorKind::NotFound => {


Test patch:
diff --git a/src/testing.rs b/src/testing.rs
index b5e4276f..4712d5c6 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -104,6 +104,7 @@ pub mod fixtures {
             maintenance_mode: general.maintenance_mode,
             maintenance_mode_status: general.maintenance_mode_status,
             maintenance_mode_file: general.maintenance_mode_file,
+            memory_cache: None,
             advanced_opts: advanced,
         };
 
diff --git a/tests/compression_static.rs b/tests/compression_static.rs
index f1c9cbc0..6b4f6c17 100644
--- a/tests/compression_static.rs
+++ b/tests/compression_static.rs
@@ -45,6 +45,7 @@ mod tests {
             base_path: &public_dir(),
             uri_path: "index.html",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -116,6 +117,7 @@ mod tests {
             base_path: &public_dir(),
             uri_path: "404.html",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -184,6 +186,7 @@ mod tests {
             base_path: &public_dir().join("assets/"),
             uri_path: "index.html",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -250,6 +253,7 @@ mod tests {
             base_path: &base_path,
             uri_path: "/",
             uri_query: None,
+            memory_cache: None,
             dir_listing: true,
             dir_listing_order: 6,
             dir_listing_format: &DirListFmt::Html,
@@ -282,6 +286,7 @@ mod tests {
             base_path: &public_dir(),
             uri_path: "main.js",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
diff --git a/tests/dir_listing.rs b/tests/dir_listing.rs
index 835bba82..919c70f5 100644
--- a/tests/dir_listing.rs
+++ b/tests/dir_listing.rs
@@ -43,6 +43,7 @@ mod tests {
                 base_path: &root_dir("docker/public/"),
                 uri_path: "/assets",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 6,
                 dir_listing_format: &DirListFmt::Html,
@@ -76,6 +77,7 @@ mod tests {
                 base_path: &root_dir("docs/"),
                 uri_path: "/content/",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 6,
                 dir_listing_format: &DirListFmt::Html,
@@ -119,6 +121,7 @@ mod tests {
                 base_path: &root_dir("docs/"),
                 uri_path: "/content",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 6,
                 dir_listing_format: &DirListFmt::Html,
@@ -162,6 +165,7 @@ mod tests {
                 base_path: &root_dir("docs/"),
                 uri_path: "/README.md",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 6,
                 dir_listing_format: &DirListFmt::Html,
@@ -195,6 +199,7 @@ mod tests {
                 base_path: &root_dir("tests/fixtures/public/"),
                 uri_path: "/",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 6,
                 dir_listing_format: &DirListFmt::Html,
@@ -249,6 +254,7 @@ mod tests {
                 base_path: &root_dir("tests/fixtures/public/"),
                 uri_path: "/",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 1,
                 dir_listing_format: &DirListFmt::Json,
@@ -321,6 +327,7 @@ mod tests {
                 base_path: &root_dir(&empty_dir),
                 uri_path: "/",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 1,
                 dir_listing_format: &DirListFmt::Json,
@@ -366,6 +373,7 @@ mod tests {
                 base_path: &root_dir("tests/fixtures/public"),
                 uri_path: "/",
                 uri_query: None,
+                memory_cache: None,
                 dir_listing: true,
                 dir_listing_order: 1,
                 dir_listing_format: &DirListFmt::Html,
diff --git a/tests/static_files.rs b/tests/static_files.rs
index 62f4d275..dee51ef2 100644
--- a/tests/static_files.rs
+++ b/tests/static_files.rs
@@ -48,6 +48,7 @@ mod tests {
             base_path: &root_dir(),
             uri_path: "index.html",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -92,6 +93,7 @@ mod tests {
             base_path: &root_dir(),
             uri_path: "index.html",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -137,6 +139,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "xyz.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -169,6 +172,7 @@ mod tests {
             base_path: &root_dir(),
             uri_path: "assets",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -203,6 +207,7 @@ mod tests {
             base_path: &root_dir(),
             uri_path: "assets",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -236,6 +241,7 @@ mod tests {
             base_path: &root_dir(),
             uri_path: "assets",
             uri_query: None,
+            memory_cache: None,
             #[cfg(feature = "directory-listing")]
             dir_listing: false,
             #[cfg(feature = "directory-listing")]
@@ -274,6 +280,7 @@ mod tests {
                     base_path: &root_dir(),
                     uri_path: uri,
                     uri_query: None,
+                    memory_cache: None,
                     #[cfg(feature = "directory-listing")]
                     dir_listing: false,
                     #[cfg(feature = "directory-listing")]
@@ -327,6 +334,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "/index%2ehtml",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -362,6 +370,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "/%2E%2e.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -399,6 +408,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -437,6 +447,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -478,6 +489,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -517,6 +529,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -554,6 +567,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -590,6 +604,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -630,6 +645,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -714,6 +730,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -785,6 +802,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -835,6 +853,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -885,6 +904,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -936,6 +956,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -979,6 +1000,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1032,6 +1054,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1082,6 +1105,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1132,6 +1156,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1185,6 +1210,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1228,6 +1254,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1270,6 +1297,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1327,6 +1355,7 @@ mod tests {
                 base_path: &root_dir(),
                 uri_path: "index.html",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1376,6 +1405,7 @@ mod tests {
                 base_path: &root_dir,
                 uri_path: ".dotfile",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1416,6 +1446,7 @@ mod tests {
                 base_path: &root_dir,
                 uri_path: "/",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1458,6 +1489,7 @@ mod tests {
                 base_path: &root_dir,
                 uri_path: "/symlink",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
@@ -1490,6 +1522,7 @@ mod tests {
                 base_path: &root_dir,
                 uri_path: "/symlink/spécial file.txt~",
                 uri_query: None,
+                memory_cache: None,
                 #[cfg(feature = "directory-listing")]
                 dir_listing: false,
                 #[cfg(feature = "directory-listing")]
diff --git a/tests/toml/config.toml b/tests/toml/config.toml
index 1db5b7ee..be939ec5 100644
--- a/tests/toml/config.toml
+++ b/tests/toml/config.toml
@@ -70,7 +70,7 @@ index-files = "index.html, index.htm"
 
 #### Maintenance Mode
 maintenance-mode = false
-# maintenance-mode-status = 503 
+# maintenance-mode-status = 503
 # maintenance-mode-file = "maintenance.html"
 
 ### Windows Only
@@ -153,3 +153,12 @@ root = "docker"
 [[advanced.virtual-hosts]]
 host = "localhost"
 root = "docker/abc"
+
+[advanced.memory-cache]
+capacity = 100
+# 30min
+ttl = 1800
+# 5min
+tti = 300
+# 8mb
+max-file-size = 8192