fix: harden SSE compression exclusion and add Caddy reverse proxy docs (#939)

The compression middleware filter runs before route handlers, so the
res.getHeader('Content-Type') check in shouldSkipCompression is always
undefined at decision time. SSE exclusion relied entirely on the Accept
header, which non-standard clients (curl, fetch) may omit.

Add deterministic path-based exclusion for all known SSE routes so
compression is skipped regardless of client behavior. Also add a Caddy
reverse proxy example and a CDN double-compression warning to docs.
This commit is contained in:
jwcrystal
2026-04-17 18:12:20 +03:00
committed by GitHub
parent c494d9f8b9
commit 6d5afe55db
3 changed files with 116 additions and 2 deletions
+44 -2
View File
@@ -290,6 +290,48 @@ Also enable `Websockets Support` in Nginx Proxy Manager for this host.
- the proxy is compressing and buffering live traffic - the proxy is compressing and buffering live traffic
- the proxy is missing WebSocket support - the proxy is missing WebSocket support
## Website docs ## Example: Caddy
- Website version: `packages/docs/content/docs/reverse-proxy.mdx` <details>
<summary>Show example config</summary>
```caddy
reverse_proxy 127.0.0.1:3000 {
# WebSocket support is automatic in Caddy
# Flush SSE responses immediately
flush_interval -1
# Pass through Host and proxy headers
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Increase timeouts for long-lived streams
transport http {
read_timeout 3600s
write_timeout 3600s
}
}
```
</details>
Caddy handles WebSocket upgrades automatically — no extra configuration needed. The `flush_interval -1` directive ensures SSE chunks are forwarded immediately without buffering.
## CDN and double-compression warning
If you place a CDN (such as Cloudflare) in front of your reverse proxy, be aware of double compression:
- OpenChamber compresses HTTP responses with gzip (threshold 1 KB).
- Cloudflare and other CDNs also compress responses by default.
- This can cause double-compressed responses or incorrect `Content-Encoding` headers.
To avoid this, disable compression at **one** layer:
- **Cloudflare:** Rules → Compression → disable (or use "Passthrough" mode).
- **Nginx:** `gzip off` (already shown in the examples above).
- **Caddy:** Caddy does not re-compress by default if the upstream already sends compressed content.
SSE streaming routes are excluded from compression by OpenChamber, but the CDN may still buffer them. Check your CDN documentation for how to disable buffering on SSE paths.
@@ -295,6 +295,52 @@ Also enable `Websockets Support` in Nginx Proxy Manager for this host.
- the proxy is compressing and buffering live traffic - the proxy is compressing and buffering live traffic
- the proxy is missing WebSocket support - the proxy is missing WebSocket support
## Example: Caddy
<details>
<summary>Show example config</summary>
```caddy
reverse_proxy 127.0.0.1:3000 {
# WebSocket support is automatic in Caddy
# Flush SSE responses immediately
flush_interval -1
# Pass through Host and proxy headers
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Increase timeouts for long-lived streams
transport http {
read_timeout 3600s
write_timeout 3600s
}
}
```
</details>
Caddy handles WebSocket upgrades automatically — no extra configuration needed. The `flush_interval -1` directive ensures SSE chunks are forwarded immediately without buffering.
## CDN and double-compression warning
If you place a CDN (such as Cloudflare) in front of your reverse proxy, be aware of double compression:
- OpenChamber compresses HTTP responses with gzip (threshold 1 KB).
- Cloudflare and other CDNs also compress responses by default.
- This can cause double-compressed responses or incorrect `Content-Encoding` headers.
To avoid this, disable compression at **one** layer:
- **Cloudflare:** Rules → Compression → disable (or use "Passthrough" mode).
- **Nginx:** `gzip off` (already shown in the examples above).
- **Caddy:** Caddy does not re-compress by default if the upstream already sends compressed content.
SSE streaming routes are excluded from compression by OpenChamber, but the CDN may still buffer them. Check your CDN documentation for how to disable buffering on SSE paths.
## Related ## Related
- [Tunnels](/tunnels/) - [Tunnels](/tunnels/)
+26
View File
@@ -109,11 +109,37 @@ function headerIncludesEventStream(value) {
return false; return false;
} }
/**
* SSE endpoint paths that must never be compressed by the compression middleware.
*
* The compression middleware filter runs before route handlers, so
* `res.getHeader('Content-Type')` is still undefined at that point.
* This means the Accept-header check alone is not sufficient for
* non-standard clients (e.g. curl, fetch) that omit Accept.
* Path-based exclusion acts as a deterministic fallback.
*/
const SSE_PATH_PREFIXES = [
'/api/event',
'/api/global/event',
'/api/notifications/stream',
'/api/openchamber/events',
];
function shouldSkipCompression(req, res) { function shouldSkipCompression(req, res) {
if (headerIncludesEventStream(req.headers.accept)) { if (headerIncludesEventStream(req.headers.accept)) {
return true; return true;
} }
const pathname = req.path || req.url || '';
if (pathname.startsWith('/api/terminal/') && pathname.endsWith('/stream')) {
return true;
}
for (const prefix of SSE_PATH_PREFIXES) {
if (pathname === prefix) {
return true;
}
}
return headerIncludesEventStream(res.getHeader('Content-Type')); return headerIncludesEventStream(res.getHeader('Content-Type'));
} }