always serve embedded frontend
This commit is contained in:
Generated
+1
-1
@@ -709,7 +709,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ushort"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"include_dir",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ushort"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.86"
|
||||
description = "A compact, self-contained URL shortener"
|
||||
|
||||
@@ -16,7 +16,3 @@ max_url_length = 2048
|
||||
max_retention_days = 3650
|
||||
rate_limit_requests = 60
|
||||
rate_limit_window = 60
|
||||
|
||||
# Keep false when the embedded frontend should be served by ushort.
|
||||
# Set true only when a separate web server serves the frontend files.
|
||||
production = false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
ushort:
|
||||
image: sodium/ushort:0.1.1
|
||||
image: sodium/ushort:0.1.2
|
||||
container_name: ushort
|
||||
command: ["/app/config.toml"]
|
||||
ports:
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ when a query string follows the slash.
|
||||
|
||||
## Health and frontend
|
||||
|
||||
- `GET /` serves the embedded frontend when `production=false`.
|
||||
- `GET /` serves the embedded frontend.
|
||||
- `GET /api/health` returns HTTP 200:
|
||||
|
||||
```json
|
||||
|
||||
@@ -24,9 +24,10 @@ ushort legacy-config.json
|
||||
| `max_retention_days` | no | `3650` | Maximum per-URL retention value. |
|
||||
| `rate_limit_requests` | no | `60` | Requests allowed per client/window. |
|
||||
| `rate_limit_window` | no | `60` | Sliding-window length in seconds. |
|
||||
| `production` | no | `false` | Disable embedded frontend routes when a separate server provides them. |
|
||||
|
||||
The legacy `short_length` option remains an alias for `min_short_length`.
|
||||
The removed legacy `production` key is ignored when present; ushort always
|
||||
serves its embedded frontend and static assets.
|
||||
|
||||
## Public URL layouts
|
||||
|
||||
|
||||
+4
-5
@@ -19,13 +19,12 @@ the server as `docker-compose.yml`. For repeatable releases, replace its image
|
||||
tag with the verified multi-architecture digest:
|
||||
|
||||
```yaml
|
||||
image: docker.io/sodium/ushort:0.1.1@sha256:<verified-index-digest>
|
||||
image: docker.io/sodium/ushort:0.1.2@sha256:<verified-index-digest>
|
||||
```
|
||||
|
||||
Set `db_path = "data/urlshort.db"` and normally keep `production = false` so
|
||||
the executable serves its embedded frontend. Keep `config.toml` owned by the
|
||||
container identity (`1001:1001`) with mode `0400`; the `data` directory must be
|
||||
writable by the same identity.
|
||||
Set `db_path = "data/urlshort.db"`. The executable always serves its embedded
|
||||
frontend. Keep `config.toml` owned by the container identity (`1001:1001`) with
|
||||
mode `0400`; the `data` directory must be writable by the same identity.
|
||||
|
||||
## Start and validate
|
||||
|
||||
|
||||
+4
-3
@@ -8,9 +8,10 @@ actions, API-key administration, sorting, pagination, theme selection, and
|
||||
locally hosted fonts. Relative asset URLs and the browser-derived API prefix
|
||||
allow the same files to work at either a subdomain root or a nested path.
|
||||
|
||||
Set `production = false` for the normal self-contained deployment. Setting it
|
||||
to `true` disables the embedded frontend routes for installations that serve
|
||||
those files separately.
|
||||
The frontend and static assets are always available. The former `production`
|
||||
toggle was removed because disabling embedded assets conflicts with ushort's
|
||||
self-contained deployment model. Old configuration files containing that key
|
||||
remain loadable; its value is ignored.
|
||||
|
||||
The nginx examples apply `no-store` to the HTML entry point and `no-cache` to
|
||||
static assets. Embedded static requests do not consume the API rate-limit
|
||||
|
||||
+28
-40
@@ -30,7 +30,6 @@ struct RawConfig {
|
||||
max_retention_days: i64,
|
||||
rate_limit_requests: usize,
|
||||
rate_limit_window: u64,
|
||||
production: bool,
|
||||
}
|
||||
|
||||
impl Default for RawConfig {
|
||||
@@ -49,7 +48,6 @@ impl Default for RawConfig {
|
||||
max_retention_days: 3650,
|
||||
rate_limit_requests: 60,
|
||||
rate_limit_window: 60,
|
||||
production: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +66,6 @@ pub struct Config {
|
||||
pub max_retention_days: i64,
|
||||
pub rate_limit_requests: usize,
|
||||
pub rate_limit_window: u64,
|
||||
pub production: bool,
|
||||
pub base_path: String,
|
||||
}
|
||||
|
||||
@@ -152,7 +149,6 @@ impl Config {
|
||||
max_retention_days: raw.max_retention_days,
|
||||
rate_limit_requests: raw.rate_limit_requests,
|
||||
rate_limit_window: raw.rate_limit_window,
|
||||
production: raw.production,
|
||||
base_path,
|
||||
})
|
||||
}
|
||||
@@ -372,10 +368,7 @@ impl App {
|
||||
|
||||
let raw_path = target_path(&request.target);
|
||||
let bare_base = !self.config.base_path.is_empty() && raw_path == self.config.base_path;
|
||||
if matches!(request.method.as_str(), "GET" | "HEAD")
|
||||
&& !self.config.production
|
||||
&& !bare_base
|
||||
{
|
||||
if matches!(request.method.as_str(), "GET" | "HEAD") && !bare_base {
|
||||
if let Some(path) = local_path(raw_path, &self.config.base_path) {
|
||||
match path.as_str() {
|
||||
"/" | "/static" => return self.serve_static("index.html"),
|
||||
@@ -432,32 +425,14 @@ impl App {
|
||||
|
||||
fn handle_get(&self, path: &str, request: &RequestData) -> ResponseData {
|
||||
match path {
|
||||
"/" => {
|
||||
if self.config.production {
|
||||
health_response()
|
||||
} else {
|
||||
self.serve_static("index.html")
|
||||
}
|
||||
}
|
||||
"/" => self.serve_static("index.html"),
|
||||
"/api/health" => health_response(),
|
||||
"/api/shorten" => self.handle_shorten(request),
|
||||
"/api/urls" => self.handle_list_urls(request),
|
||||
"/api/lookup" => self.handle_lookup(request),
|
||||
"/static" => {
|
||||
if self.config.production {
|
||||
ResponseData::empty(404)
|
||||
} else {
|
||||
self.serve_static("index.html")
|
||||
}
|
||||
}
|
||||
"/static" => self.serve_static("index.html"),
|
||||
_ if path.starts_with("/api/urls/") => self.handle_get_url(&path["/api/urls/".len()..]),
|
||||
_ if path.starts_with("/static/") => {
|
||||
if self.config.production {
|
||||
ResponseData::empty(404)
|
||||
} else {
|
||||
self.serve_static(&path["/static/".len()..])
|
||||
}
|
||||
}
|
||||
_ if path.starts_with("/static/") => self.serve_static(&path["/static/".len()..]),
|
||||
_ => self.handle_redirect(path.trim_start_matches('/')),
|
||||
}
|
||||
}
|
||||
@@ -1477,19 +1452,32 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_flag_keeps_legacy_backend_static_behavior() {
|
||||
let path = temp_path("production");
|
||||
let mut cfg = config(path.clone());
|
||||
cfg.production = true;
|
||||
let app = App::new(cfg).unwrap();
|
||||
fn deprecated_production_key_is_ignored_and_frontend_remains_enabled() {
|
||||
let path = temp_path("deprecated-production");
|
||||
let config_path = std::env::temp_dir().join(format!(
|
||||
"ushort-deprecated-production-{}-{}.toml",
|
||||
std::process::id(),
|
||||
rand::random::<u64>()
|
||||
));
|
||||
fs::write(
|
||||
&config_path,
|
||||
format!(
|
||||
"base_url = \"https://example.test/s\"\n\
|
||||
api_key = \"secret\"\n\
|
||||
db_path = \"{}\"\n\
|
||||
production = true\n",
|
||||
path.display()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let app = App::new(Config::load(&config_path).unwrap()).unwrap();
|
||||
let root = app.handle(RequestData::new("GET", "/s/"));
|
||||
assert_eq!(
|
||||
root.body,
|
||||
br#"{"status": "ok", "service": "url-shortener"}"#
|
||||
);
|
||||
assert_eq!(root.status, 200);
|
||||
assert_eq!(root.body, include_bytes!("../static/index.html"));
|
||||
let asset = app.handle(RequestData::new("GET", "/s/static/app.js"));
|
||||
assert_eq!(asset.status, 404);
|
||||
assert!(asset.body.is_empty());
|
||||
assert_eq!(asset.status, 200);
|
||||
assert_eq!(asset.body, include_bytes!("../static/app.js"));
|
||||
let _ = fs::remove_file(config_path);
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,6 @@ fn main() {
|
||||
"Rate limit : {} req/{}s per IP",
|
||||
app.config.rate_limit_requests, app.config.rate_limit_window
|
||||
);
|
||||
eprintln!("Production : {}", app.config.production);
|
||||
|
||||
for mut request in server.incoming_requests() {
|
||||
let maximum_body = app.max_request_body_bytes();
|
||||
let declared_too_large = request
|
||||
|
||||
Reference in New Issue
Block a user