Merge pull request #122 from joseluisq/feature/Rewrites_with_pattern_matching
feat: url rewrites with pattern matching support
Diff
docs/content/features/url-rewrites.md | 41 ++++++++++++++++++++++++++++++++++++-
docs/mkdocs.yml | 1 +-
src/handler.rs | 13 ++++++++---
src/lib.rs | 1 +-
src/rewrites.rs | 19 +++++++++++++++++-
src/settings/file.rs | 11 +++++++++-
src/settings/mod.rs | 40 +++++++++++++++++++++++++++++++++--
tests/toml/config.toml | 16 ++++++++++----
8 files changed, 132 insertions(+), 10 deletions(-)
@@ -0,0 +1,41 @@
# URL Rewrites
**SWS** provides the ability to rewrite request URLs with pattern matching support.
URI rewrites are particularly useful with pattern matching ([globs](https://en.wikipedia.org/wiki/Glob_(programming))), as the server can accept any URL that matches the pattern and let the client-side code decide what to display.
## Structure
The URL rewrite rules should be defined mainly as an [Array of Tables](https://toml.io/en/v1.0.0#array-of-tables).
Each table entry should have two key/value pairs:
- One `source` key containing a string _glob pattern_.
- One `destination` string containing the local file path.
!!! info "Note"
The incoming request(s) will reach the `destination` only if the request(s) URI matches the `source` pattern.
### Source
The source is a [Glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) that should match against the URI that is requesting a resource file.
### Destination
A local file path which must exist. It has to look something like `/some/directory/file.html`. It is worth noting that the `/` at the beginning indicates the server's root directory.
## Examples
```toml
[advanced]
### URL Rewrites
[[advanced.rewrites]]
source = "**/*.{png,ico,gif}"
destination = "/assets/generic1.png"
[[advanced.rewrites]]
source = "**/*.{jpg,jpeg}"
destination = "/images/generic2.png"
```
@@ -139,6 +139,7 @@ nav:
- 'Worker Threads Customization': 'features/worker-threads.md'
- 'Error Pages': 'features/error-pages.md'
- 'Custom HTTP Headers': 'features/custom-http-headers.md'
- 'URL Rewrites': 'features/url-rewrites.md'
- 'Windows Service': 'features/windows-service.md'
- 'Platforms & Architectures': 'platforms-architectures.md'
- 'Migration from v1 to v2': 'migration.md'
@@ -3,7 +3,7 @@ use std::{future::Future, net::SocketAddr, path::PathBuf, sync::Arc};
use crate::{
basic_auth, compression, control_headers, cors, custom_headers, error_page, fallback_page,
security_headers, settings::Advanced, static_files, Error, Result,
rewrites, security_headers, settings::Advanced, static_files, Error, Result,
};
@@ -43,7 +43,7 @@ impl RequestHandler {
let uri = req.uri();
let root_dir = &self.opts.root_dir;
let uri_path = uri.path();
let mut uri_path = uri.path();
let uri_query = uri.query();
let dir_listing = self.opts.dir_listing;
let dir_listing_order = self.opts.dir_listing_order;
@@ -65,7 +65,7 @@ impl RequestHandler {
);
async move {
if !(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS) {
return error_page::error_response(
uri,
@@ -128,6 +128,13 @@ impl RequestHandler {
}
}
if let Some(advanced) = &self.opts.advanced_opts {
if let Some(uri) = rewrites::rewrite_uri_path(uri_path, &advanced.rewrites) {
uri_path = uri
}
}
match static_files::handle(
method,
@@ -19,6 +19,7 @@ pub mod fallback_page;
pub mod handler;
pub mod helpers;
pub mod logger;
pub mod rewrites;
pub mod security_headers;
pub mod server;
pub mod service;
@@ -0,0 +1,19 @@
use crate::settings::Rewrites;
pub fn rewrite_uri_path<'a>(
uri_path: &'a str,
rewrites_opts_vec: &'a Option<Vec<Rewrites>>,
) -> Option<&'a str> {
if let Some(rewrites_vec) = rewrites_opts_vec {
for rewrites_entry in rewrites_vec.iter() {
if rewrites_entry.source.is_match(uri_path) {
return Some(rewrites_entry.destination.as_str());
}
}
}
None
}
@@ -7,7 +7,7 @@ use std::{collections::BTreeSet, path::PathBuf};
use crate::{helpers, Context, Result};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
Error,
@@ -37,12 +37,21 @@ pub struct Headers {
pub headers: HeaderMap,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Rewrites {
pub source: String,
pub destination: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Advanced {
pub headers: Option<Vec<Headers>>,
pub rewrites: Option<Vec<Rewrites>>,
}
@@ -20,9 +20,18 @@ pub struct Headers {
pub headers: HeaderMap,
}
pub struct Rewrites {
pub source: GlobMatcher,
pub destination: String,
}
pub struct Advanced {
pub headers: Option<Vec<Headers>>,
pub rewrites: Option<Vec<Rewrites>>,
}
@@ -81,7 +90,7 @@ impl Settings {
config_file = Some(path_resolved);
if let Some(general) = settings.general {
if let Some(v) = general.host {
host = v
@@ -151,7 +160,7 @@ impl Settings {
}
}
if let Some(advanced) = settings.advanced {
let headers_entries = match advanced.headers {
@@ -179,8 +188,35 @@ impl Settings {
_ => None,
};
let rewrites_entries = match advanced.rewrites {
Some(rewrites_entries) => {
let mut rewrites_vec: Vec<Rewrites> = Vec::new();
for rewrites_entry in rewrites_entries.iter() {
let source = Glob::new(&rewrites_entry.source)
.with_context(|| {
format!(
"can not compile glob pattern for rewrite source: {}",
&rewrites_entry.source
)
})?
.compile_matcher();
rewrites_vec.push(Rewrites {
source,
destination: rewrites_entry.destination.to_owned(),
});
}
Some(rewrites_vec)
}
_ => None,
};
settings_advanced = Some(Advanced {
headers: headers_entries,
rewrites: rewrites_entries,
});
}
}
@@ -2,7 +2,7 @@
host = "::"
port = 8087
port = 8787
root = "docker/public"
@@ -45,9 +45,6 @@ grace-period = 0
page-fallback = ""
page-fallback = ""
log-remote-address = false
@@ -79,3 +76,14 @@ Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
[[advanced.headers]]
source = "**/*.{jpg,jpeg,png,ico,gif}"
headers.Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
[[advanced.rewrites]]
source = "**/*.{png,ico,gif}"
destination = "/assets/favicon.ico"
[[advanced.rewrites]]
source = "**/*.{jpg,jpeg}"
destination = "/images/nomad.png"