Skip to content

leafac/caddy-express-sse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Caddy Doesn’t Seem to Flush Response Buffer, Breaking Reverse Proxy of Server-Sent Events (caddyserver/caddy#4247)

For your convenience, you may also clone the code for this issue from https://github.com/leafac/caddy-express-sse

Caddyfile

localhost

reverse_proxy 127.0.0.1:4000

A simple Node.js web server running at 127.0.0.1:4000:

const express = require("express");
const app = express();

app.get("/", (req ,res) => {
  res.type("text/event-stream").flushHeaders();
});

app.listen(4000);

Start the server and the reverse proxy:

$ npm install
$ node index.js # Leave running, continue on another terminal
$ caddy run # Leave running, continue on another terminal

When I make a request to the server directly (without Caddy), I get the response I expect:

$ curl -Nv http://localhost:4000
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 4000 (#0)
> GET / HTTP/1.1
> Host: localhost:4000
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/event-stream; charset=utf-8
< Date: Mon, 19 Jul 2021 14:25:59 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked

Note how the response headers came through.

But when I request via Caddy, the following happens:

$ curl -Nv https://localhost    
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Jul 19 14:23:15 2021 GMT
*  expire date: Jul 20 02:23:15 2021 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x13e80f800)
> GET / HTTP/2
> Host: localhost
> User-Agent: curl/7.64.1
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!

Now how the response headers didn’t arrive.

In both cases the connection is kept open, which is the intended behavior.

Why Is This an Issue?

As far as I can tell, browsers doesn’t fire an open event on the EventSource until the headers are received.

Other Things I Tried without Success (They Don’t Seem to Affect the Behavior at All):

  • Change my Caddyfile to use flush_interval -1 (see https://caddy.community/t/v2-server-sent-events-from-flask-to-caddy-via-gunicorn/7806/2).

  • Rewrite my Node.js server to read like the following (see caddyserver/caddy#3765):

    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'X-Accel-Buffering': 'no',
      'Transfer-Encoding': 'chunked',
    });

    (Weirdly enough, writing it this way makes it not work even when I make the request to the Node.js server directly, without Caddy in the middle. Using a .flushHeaders() right after the call to .writeHead() seems to fix it.)

My Suspicion: The flush_interval configuration seems to be ignored, regardless of whether it was set explicitly in the Caddyfile or it was set by Caddy itself because the response Content-Type is text/event-stream (see https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#streaming and https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/streaming.go#L104-L108).

A Workaround: Send a dummy server-sent event to force Caddy to flush the headers:

res.type("text/event-stream").write(":\n\n");
Versions
$ node --version
v16.5.0
$ caddy version
v2.4.3 h1:Y1FaV2N4WO3rBqxSYA8UZsZTQdN+PwcoOcAiZTM8C0I=

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published