133 lines
9.2 KiB
Markdown
133 lines
9.2 KiB
Markdown
# Custom locations
|
||
|
||
RustProxy 0.22.3 supports the common location-level controls that are typically used for application reverse proxies. The UI intentionally exposes a safe subset instead of accepting arbitrary Nginx configuration text.
|
||
|
||
## Routing model in 0.16.0
|
||
|
||
The vhost's main backend pool is an internal fallback and is **not** one of the Custom locations. Custom locations are optional overrides only. Request routing is therefore:
|
||
|
||
```text
|
||
exact custom location
|
||
-> priority/regex/prefix custom location according to the documented precedence
|
||
-> main vhost backend pool
|
||
```
|
||
|
||
An empty Custom locations list is valid. Adding `/test`, a regex, a local response or a dedicated-backend location cannot disable `/`, `/abc` or other unmatched paths. Older saved documents that contain the former untouched synthetic `root` prefix `/` entry are normalized automatically. If `/` was deliberately customized with a backend, cache/ACL/header rule, rewrite or local response, it remains a real Custom location and intentionally overrides the main fallback.
|
||
|
||
## Dashboard editor
|
||
|
||
The visual editor is the primary workflow. Every location card now shows a live **Effective behavior preview** at the bottom with the match mode/pattern, proxy or local-response action, selected backend, rewrite behavior, cache policy, ACL composition and request/response header-rule counts. The preview is calculated from the unsaved form state so an administrator can verify intent before saving.
|
||
|
||
Request and response header rules use numbered visual cards with localized **Operation / Header name / Header value** controls. `Set`, `Append` and `Remove` are supported. The Advanced JSON view remains available for automation/copy-paste, but is no longer required for normal editing. The documentation button in the location tab explains precedence, rewrites, local responses, policy composition and common examples.
|
||
|
||
## Match modes
|
||
|
||
Each location has an ID, a pattern and one match mode:
|
||
|
||
| RustProxy mode | Nginx-style equivalent | Behaviour |
|
||
| --- | --- | --- |
|
||
| `prefix` | `location /path/` | Longest matching path prefix. A matching regex may still win. |
|
||
| `prefix_priority` | `location ^~ /path/` | Longest prefix and, when selected, skips regex locations. |
|
||
| `exact` | `location = /path` | Exact normalized path only. Exact matches have highest priority. |
|
||
| `regex` | `location ~ pattern` | Case-sensitive Rust regular expression. Regex locations are tested in configuration order. |
|
||
| `regex_insensitive` | `location ~* pattern` | Case-insensitive Rust regular expression. Regex locations are tested in configuration order. |
|
||
|
||
Routing and ACL checks collapse repeated literal slashes, matching Nginx's usual `merge_slashes on` behaviour. For example `/static/js//events.js` is routed as `/static/js/events.js`. Unless a rewrite is configured, the original request target is still sent to the backend.
|
||
|
||
Encoded separators (`%2f`, `%5c`), encoded `%`, dot segments and other ambiguous path forms are still rejected instead of being silently normalized.
|
||
|
||
## Actions
|
||
|
||
A location can proxy to a backend, serve an existing Web Service, return a local response, or redirect the client to another absolute HTTP(S) URL.
|
||
|
||
### Proxy
|
||
|
||
The location can use the vhost pool or pin traffic to one backend. A dedicated backend can be created from the location editor. Cache policy can inherit from the vhost or be explicitly enabled/disabled for the location.
|
||
|
||
Supported path transformations:
|
||
|
||
- **Preserve** — send the original client path unchanged.
|
||
- **Strip prefix** — remove the matched prefix before proxying.
|
||
- **Replace prefix** — replace the matched prefix with another normalized path prefix.
|
||
- **Regex rewrite** — available for regex locations; Rust regex captures can be referenced as `$1` or `${1}` in the replacement.
|
||
|
||
The query string is preserved. Rewrites change only the selected source path; reverse-proxy cache identity remains based on the public client URL.
|
||
|
||
### Web Server
|
||
|
||
`Web Server` selects one reusable service previously created in the **Web Server** workspace. The location stores only the Web Service ID; document root, index list and directory-listing policy remain in that shared service. Existing location matching and path rewriting are reused, but backend pinning and reverse-proxy object-cache controls do not apply to local files. See [WEB_SERVER.md](WEB_SERVER.md).
|
||
|
||
### Local response
|
||
|
||
`Return custom response` is the equivalent of a simple Nginx `return`: choose an HTTP status from 200 through 599, a Content-Type and a response body. RustProxy does not open a backend connection for this action. `HEAD`, `204` and `304` responses do not send a body.
|
||
|
||
### Redirect
|
||
|
||
`Redirect` is a backend-less action. Choose `301`, `302`, `303`, `307` or `308`, set an absolute `http://` or `https://` target, and optionally preserve the incoming query string. RustProxy validates the target and does not allow capture expansion inside the target scheme/authority.
|
||
|
||
For capture-based path rewriting, configure a separate **Redirect rewrite regex**. Captures can be referenced as `$1` or `${name}` in the redirect target path/query. This separation mirrors an Nginx `location` selector followed by a separate `rewrite` expression. For example, the Nginx pattern:
|
||
|
||
```nginx
|
||
location ~* \.(?:tar\.gz|zip?)$ {
|
||
rewrite ^/(.*)$ https://objectstorage.example.invalid/b/repo/o/$1 permanent;
|
||
}
|
||
```
|
||
|
||
corresponds to a case-insensitive regex Custom location `\.(?:tar\.gz|zip?)$`, action **Redirect**, status `301`, target `https://objectstorage.example.invalid/b/repo/o/$1`, and redirect rewrite regex `^/(.*)$`. Query preservation is controlled independently.
|
||
|
||
|
||
## Request and response headers
|
||
|
||
Every location has a visual header configurator and an Advanced JSON view. Both edit the same data.
|
||
|
||
Supported operations are:
|
||
|
||
- `set` — replace the header with the configured value;
|
||
- `append` — append another header value;
|
||
- `remove` — remove the header.
|
||
|
||
Example JSON:
|
||
|
||
```json
|
||
{
|
||
"request": [
|
||
{"op": "set", "name": "X-App-Route", "value": "api"}
|
||
],
|
||
"response": [
|
||
{"op": "set", "name": "Cache-Control", "value": "no-store"},
|
||
{"op": "remove", "name": "X-Legacy-Header"}
|
||
]
|
||
}
|
||
```
|
||
|
||
Hop-by-hop/framing headers, forwarding identity headers, RustProxy request IDs and WebSocket handshake headers are protected and cannot be overwritten by custom rules. This prevents a location rule from changing transport framing or spoofing the verified client identity.
|
||
|
||
## Access control
|
||
|
||
A location can define its own IPv4/IPv6 CIDR ACL, select a reusable ACL profile, add a location-specific country block/allow-only rule, and configure an HTTP method policy (`off`, `allow` or `block`). Method names are normalized to uppercase and validated as HTTP method tokens.
|
||
|
||
All access layers are additive. The request must pass the vhost method policy, location method policy, host/profile/vhost IP ACLs, location profile ACL, inline location ACL and every applicable country policy. A location therefore cannot override a denial from its vhost. Method denial returns `405 Method Not Allowed` (with `Allow` for allow-list mode); IP/country denials retain the normal `403` pipeline. These checks occur before backend selection/connection.
|
||
|
||
Authentication, vhost rate limits, security headers, compression, custom error pages and WebSocket policy still apply through the normal vhost pipeline.
|
||
|
||
## Internal locations and error fallbacks
|
||
|
||
RustProxy 0.29.1 can mark a Custom location as `internal`. An internal location is addressed by its stable location `id` and is never selected by a public URI match. The vhost main source or another Custom location can use `error_fallback` to internally dispatch configured `403/404/500/502/503/504` responses to that target. The default intercepted status is `404`.
|
||
|
||
This is an internal request handoff, not an HTTP redirect. The original method, URI/query and request context are retained. The fallback target re-enters normal access/header/cache/compression/response processing. Fallback targets must be enabled internal locations in the same vhost; validation rejects cycles and runtime dispatch is depth-limited. See [0.29.1 implementation note](V0.29.1_IMPLEMENTATION.md).
|
||
|
||
### Replace origin error bodies without an internal location (0.29.7)
|
||
|
||
The independent, optional **Vhost → Pages → Intercept application error responses**
|
||
setting (`pages.intercept_statuses`, for example `[404, 503]`) replaces a
|
||
backend or Web Server/PHP origin's error *body* with the configured vhost
|
||
error page, then global error page, or a built-in page. It retains the HTTP
|
||
status and sets `Cache-Control: no-store`. Unlike `error_fallback`, it does
|
||
**not** forward the request to a different backend. The existing internal
|
||
`error_fallback` action has priority and its final response remains intact.
|
||
See [Web Server – application HTTP error interception](WEB_SERVER.md#intercept-application-http-errors-0297).
|
||
|
||
## What is intentionally not an Nginx parser
|
||
|
||
Custom locations do **not** execute arbitrary Nginx directives or configuration snippets. RustProxy does not currently implement location-level `try_files`, per-location filesystem aliases/roots, embedded scripting, arbitrary `if` expressions or arbitrary module directives. Static roots are defined only by reusable Web Services. Use the explicit UI controls and the documented JSON schema instead; unknown fields are rejected on save.
|