Skip to content

treaty2: text/plain streams aren't detected behind HTTP/2 reverse proxies #258

Description

@NeilTheFisher

Streaming a text/plain async generator route works on a direct HTTP/1.1 connection but breaks behind any HTTP/2 reverse proxy (Traefik, nginx, Caddy, Cloudflare, etc.). The detection added in #213 requires Transfer-Encoding: chunked, which HTTP/2 doesn't allow as a response header (RFC 7540 §8.1.2.2), so proxies strip it on h1→h2. Eden then falls into await response.text() and the request hangs forever on long-lived streams (while (true) generators stay in pending).

sse() works because the SSE branch matches on Content-Type only.

Repro

new Elysia()
  .get("/live", async function* () {
    let i = 0
    while (true) {
      yield \`\${i}\`
      i++
      await new Promise(r => setTimeout(r, 1000))
    }
  })
  .listen(3000)
  • Direct: `curl -i http://localhost:3000/live\` → `Transfer-Encoding: chunked` present, Eden streams.
  • Through Traefik on HTTPS/h2: response has no `Transfer-Encoding` header, Eden hangs.

Fix

Drop the `Transfer-Encoding: chunked` clause from the streaming detection in the default branch of the Content-Type switch:

```ts
response.headers.get('content-type')?.startsWith('text/') &&
!response.headers.has('content-length')
```

This does not regress #213 (the issue that originally added the chunked check). Per RFC 7230 §3.3.1, `Transfer-Encoding: chunked` and `Content-Length` are mutually exclusive, so any HTTP/1.1 chunked response already satisfies the remaining `!has('content-length')` check. A non-streaming `text/plain` response continues to be buffered because it carries a `Content-Length`.

Happy to PR with regression tests.


Possibly related: #255 / PR #256 fix non-SSE chunk yield semantics (chunks weren't emitting until end). Both fixes are needed for HTTP/2 plain-text streams to work end-to-end.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions