# RustProxy 0.29.1 implementation note RustProxy 0.29.1 adds explicit Nginx-style internal error routing without parsing Nginx configuration, plus multipart byte ranges and optional PHP `PATH_INFO` support. SQLite remains schema 9 and Cold Cache remains format 3. ## Internal error fallback A vhost main source and each Custom location can define: ```json "error_fallback": { "target": "redirect-check", "statuses": [404] } ``` `target` is the stable `id` of an enabled Custom location in the same vhost with `"internal": true`. Internal locations are retained in the compiled route table but never participate in normal public URI matching. The supported intercepted status set is deliberately bounded to `403`, `404`, `500`, `502`, `503` and `504`; omitting `statuses` defaults to `404`. The dispatch is internal, not a `3xx` client redirect. RustProxy rebuilds the fallback request with the original HTTP method, URI, query string, HTTP version, request headers and the existing transport/request context. The target location then executes through the normal vhost/location path, including ACL/authentication, header rules, cache policy, compression and response processing. Vhost rate limiting is not charged a second time for the same client request. For requests with a body, RustProxy can replay a body only when its exact size is known and no larger than 8 MiB. Such a request is buffered once so the same bytes can be sent to the fallback target. Unknown-size or larger streaming bodies continue to the original target, but an error fallback cannot replay them; the original error response is retained. Configuration validation rejects missing/public fallback targets and recursive fallback cycles. A runtime depth limit of 8 internal dispatches remains as a second guard against loops after hot-reload/state inconsistencies. Configured upstream response statuses are intercepted before normal response finalization and cache publication. RustProxy-generated reverse-proxy failures also participate where applicable: missing backend (`500`), unavailable/capacity (`503`), connect failure (`503`), invalid upstream response (`502`) and response timeout (`504`). A configured stale cached response still wins before transport-error fallback. ## Example: static files -> application -> redirector The following vhost fragment expresses the same request flow as an Nginx `try_files`/`error_page` chain, without an HTTP redirect between stages: ```json { "source": { "type": "web_server", "web_service_id": "www-linuxiarz" }, "error_fallback": { "target": "to8000", "statuses": [404] }, "backends": [ { "id": "app", "url": "http://127.0.0.1:8000", "enabled": true, "allow_private": true, "address_allowlist": ["127.0.0.1/32"] }, { "id": "redirector", "url": "http://127.0.0.1:9000", "enabled": true, "allow_private": true, "address_allowlist": ["127.0.0.1/32"] } ], "routes": [ { "id": "to8000", "internal": true, "backend_id": "app", "source": {"type": "reverse_proxy"}, "error_fallback": { "target": "redirect-check", "statuses": [404] } }, { "id": "redirect-check", "internal": true, "backend_id": "redirector", "source": {"type": "reverse_proxy"}, "cache": false } ] } ``` The effective flow is: ```text public request -> Web Server 2xx/3xx -> return normally 404 -> @to8000 (internal) -> backend app non-404 -> return normally 404 -> @redirect-check (internal) -> redirector backend ``` Normal public Custom locations can still route `/stats`, `/user-info`, `/health`, RBL files or other paths directly before the main source is used. An internal location's `path_prefix` is not used for public matching; its `id` is the fallback name. ## Multipart byte ranges The Web Server now accepts comma-separated byte ranges such as: ```http Range: bytes=0-99,200-299 ``` One satisfiable range retains the existing single-part `206` response. Multiple satisfiable ranges produce a streaming `multipart/byteranges` response with per-part `Content-Type` and `Content-Range`. At most 16 ranges are accepted. Malformed or entirely unsatisfiable range sets return `416`. Multipart responses use the identity file representation rather than `.br`/`.gz` sidecars so the multipart MIME framing and `Content-Encoding` semantics cannot conflict. ## PHP PATH_INFO Web Service PHP configuration gains the default-off field: ```json "php": { "enabled": true, "path_info": true } ``` When enabled, RustProxy may resolve a request such as `/index.php/foo/bar` by finding the longest existing configured PHP script prefix under the Web Service document root. FastCGI then receives, conceptually: ```text SCRIPT_FILENAME=/document/root/index.php SCRIPT_NAME=/index.php PATH_INFO=/foo/bar REQUEST_URI=/index.php/foo/bar ``` The split uses the same document-root confinement and PHP file checks as normal script resolution. It is disabled by default because it changes URL-to-script resolution semantics. ## Compatibility No SQLite migration is required. Existing vhosts, locations and Web Services deserialize unchanged because `internal`, `error_fallback` and PHP `path_info` use backward-compatible defaults. Existing single-range responses are retained.