Skip to content

Commit 73973af

Browse files
Pangjipingclaude
andauthored
fix(egress): unblock SSE/chunked streaming through mitmproxy (#898)
* fix(egress): unblock SSE/chunked streaming through mitmproxy The mitmdump --set stream_large_bodies=1m option (added to prevent OOM on large downloads) buffers any response under 1 MiB before forwarding, which stalls LLM SSE / chunked streams of small chunks until the stream ends or the threshold is hit, producing perceptible stutter downstream. Introduce a bundled system addon that is always loaded by the egress launcher and forces flow.response.stream = True for responses with content-type: text/event-stream or transfer-encoding: chunked. Large- body OOM protection from stream_large_bodies stays intact for everything else. The previous example script add_header.py is renamed to system.py and repurposed as the always-on system addon (wire-transparent: no headers added or altered). User-supplied addons via OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT are still loaded after the system addon and may observe or override its hooks. Refs: https://project.aone.alibaba-inc.com/v2/project/2135082/req/82131871 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(egress): case-insensitive header match and correct system addon docs Normalize content-type and transfer-encoding to lowercase before substring matching in the system addon, so legal mixed-case values like Transfer-Encoding: Chunked or Content-Type: Text/Event-Stream still trigger streaming instead of getting buffered by stream_large_bodies=1m. Also fix the transparent-mode doc, which referenced a non-existent OPENSANDBOX_EGRESS_MITMPROXY_SYSTEM_SCRIPT env var as a way to disable or swap the system addon. The launcher always appends the bundled system addon unconditionally; document that users override behavior via OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT (loaded after the system addon). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e0a7346 commit 73973af

4 files changed

Lines changed: 61 additions & 22 deletions

File tree

components/egress/docs/mitmproxy-transparent.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ By default, mitmproxy listens on `18081` and transparent redirect rules are set
3030
# Optional: change listening port (default: 18081)
3131
export OPENSANDBOX_EGRESS_MITMPROXY_PORT=18081
3232

33-
# Optional: enable mitm addon script (e.g., inject request headers)
34-
export OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT=/opt/opensandbox/mitmscripts/add_header.py
33+
# Optional: load an additional user-defined mitm addon (loaded after the system addon)
34+
export OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT=/path/to/your/addon.py
3535

3636
# Optional: bypass decryption for selected domains (semicolon-separated regex list)
3737
export OPENSANDBOX_EGRESS_MITMPROXY_IGNORE_HOSTS='.*\.log\.aliyuncs\.com;.*\.example\.internal'
@@ -43,7 +43,7 @@ export OPENSANDBOX_EGRESS_MITMPROXY_IGNORE_HOSTS='.*\.log\.aliyuncs\.com;.*\.exa
4343
|------|----------|------|--------|
4444
| `OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT` | Yes | Enable transparent mitmproxy (`1/true/on`, etc.) | Disabled |
4545
| `OPENSANDBOX_EGRESS_MITMPROXY_PORT` | No | mitmdump listen port; `iptables` redirects `80/443` here | `18081` |
46-
| `OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT` | No | mitm addon script path (`-s`) | Empty |
46+
| `OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT` | No | Additional user mitm addon script path (`-s`); loaded after the system addon | Empty |
4747
| `OPENSANDBOX_EGRESS_MITMPROXY_IGNORE_HOSTS` | No | Host/IP regex list for TLS pass-through (`;` separated) | Empty |
4848
| `OPENSANDBOX_EGRESS_MITMPROXY_CONFDIR` | No | mitm config and CA directory (passed as `--set confdir=`, also used as `HOME`) | Default directory under `/var/lib/mitmproxy` |
4949
| `OPENSANDBOX_EGRESS_MITMPROXY_UPSTREAM_TRUST_DIR` | No | Trust directory for upstream TLS verification (OpenSSL style) | `/etc/ssl/certs` |
@@ -62,23 +62,31 @@ Notes:
6262
export OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT=true
6363
```
6464

65-
### 2) Enable with Header Injection
65+
### 2) System Addon (Always On)
66+
67+
The bundled system addon at `/var/egress/mitmscripts/system.py` is shipped in the egress image and loaded automatically whenever transparent mode is enabled. It stays wire-transparent (no headers added or altered) and currently provides:
68+
69+
- Forces streaming (`flow.response.stream = True`) for SSE (`text/event-stream`) and chunked responses, so each chunk is forwarded immediately instead of being buffered up to the `stream_large_bodies=1m` threshold (critical for LLM streaming UX).
70+
71+
The system addon is always loaded and cannot be disabled via configuration. To override its behavior, supply a user addon via `OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT`; user addons are loaded after the system addon and may observe or override its hooks.
72+
73+
### 3) Add a User Addon Alongside the System Addon
6674

6775
```bash
6876
export OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT=true
69-
export OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT=/opt/opensandbox/mitmscripts/add_header.py
77+
export OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT=/path/to/your/addon.py
7078
```
7179

72-
Built-in example script: `/opt/opensandbox/mitmscripts/add_header.py` (adds `X-OpenSandbox-Egress: 1`).
80+
The user addon is loaded after the system addon (`-s system.py -s user.py`), so user hooks observe and may override system behavior.
7381

74-
### 3) Bypass Decryption for Specific Domains (e.g. log upload)
82+
### 4) Bypass Decryption for Specific Domains (e.g. log upload)
7583

7684
```bash
7785
export OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT=true
7886
export OPENSANDBOX_EGRESS_MITMPROXY_IGNORE_HOSTS='.*\.log\.aliyuncs\.com'
7987
```
8088

81-
### 4) Use a Fixed CA (consistent fingerprint across replicas)
89+
### 5) Use a Fixed CA (consistent fingerprint across replicas)
8290

8391
If CA files already exist in `confdir`, mitmproxy reuses them instead of regenerating on each startup. Typical paths:
8492

components/egress/mitmscripts/add_header.py

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2026 Alibaba Group Holding Ltd.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# OpenSandbox egress system addon.
16+
#
17+
# Always loaded by the egress mitmproxy launcher. Stays transparent on the
18+
# wire (does not add or alter headers that would reveal the proxy to peers).
19+
#
20+
# Behavior:
21+
# Forces streaming for SSE / chunked responses so each chunk is forwarded
22+
# immediately, bypassing the stream_large_bodies=1m buffer set in launch.go
23+
# (which otherwise stalls LLM-style small-chunk streams).
24+
#
25+
# User-defined addons can be loaded alongside this script via
26+
# OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT.
27+
from mitmproxy import http
28+
29+
30+
def responseheaders(flow: http.HTTPFlow) -> None:
31+
if flow.response is None:
32+
return
33+
content_type = flow.response.headers.get("content-type", "").lower()
34+
transfer_encoding = flow.response.headers.get("transfer-encoding", "").lower()
35+
if "text/event-stream" in content_type or "chunked" in transfer_encoding:
36+
flow.response.stream = True

components/egress/pkg/mitmproxy/launch.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,16 @@ const RunAsUser = "mitmproxy"
3434
// Loopback: transparent mode receives via REDIRECT; do not listen on 0.0.0.0 in the netns.
3535
const listenHostLoopback = "127.0.0.1"
3636

37+
// systemScriptPath: bundled system addon shipped via the egress Dockerfile
38+
// (COPY components/egress/mitmscripts /var/egress/mitmscripts). Always loaded.
39+
const systemScriptPath = "/var/egress/mitmscripts/system.py"
40+
3741
// Config: mitmdump --mode transparent; UserName must match iptables ! --uid-owner, ConfDir is mitm state/CA.
3842
type Config struct {
3943
ListenPort int
4044
UserName string
4145
ConfDir string
46+
// ScriptPath is an optional user-supplied addon, loaded after the system addon.
4247
ScriptPath string
4348
// OnExit is called (if non-nil) when mitmdump exits. Called from a background goroutine.
4449
OnExit func(error)
@@ -120,8 +125,10 @@ func Launch(cfg Config) (*Running, error) {
120125
args = append(args, "--set", "confdir="+cd)
121126
homeEnv = cd
122127
}
123-
if strings.TrimSpace(cfg.ScriptPath) != "" {
124-
args = append(args, "-s", strings.TrimSpace(cfg.ScriptPath))
128+
// Load the system addon first so user addons can observe / override its hooks.
129+
args = append(args, "-s", systemScriptPath)
130+
if user := strings.TrimSpace(cfg.ScriptPath); user != "" {
131+
args = append(args, "-s", user)
125132
}
126133

127134
// Upstream passthrough: each pattern becomes --set ignore_hosts= (regex; IP ranges are practical in transparent mode).

0 commit comments

Comments
 (0)