{"id":18774110,"url":"https://github.com/leafac/caddy-express-sse","last_synced_at":"2025-12-14T05:30:14.050Z","repository":{"id":117510252,"uuid":"387496046","full_name":"leafac/caddy-express-sse","owner":"leafac","description":null,"archived":false,"fork":false,"pushed_at":"2021-07-19T15:05:21.000Z","size":9,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-29T08:42:17.958Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leafac.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-19T14:38:55.000Z","updated_at":"2021-07-19T15:05:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"53ae19f6-f6c1-4bf9-9741-7a72f73ab259","html_url":"https://github.com/leafac/caddy-express-sse","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafac%2Fcaddy-express-sse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafac%2Fcaddy-express-sse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafac%2Fcaddy-express-sse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafac%2Fcaddy-express-sse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leafac","download_url":"https://codeload.github.com/leafac/caddy-express-sse/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239680987,"owners_count":19679509,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T19:37:23.202Z","updated_at":"2025-12-14T05:30:14.008Z","avatar_url":"https://github.com/leafac.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Caddy Doesn’t Seem to Flush Response Buffer, Breaking Reverse Proxy of Server-Sent Events (https://github.com/caddyserver/caddy/issues/4247)\n\n\u003e For your convenience, you may also clone the code for this issue from \u003chttps://github.com/leafac/caddy-express-sse\u003e\n\n`Caddyfile`\n\n```\nlocalhost\n\nreverse_proxy 127.0.0.1:4000\n```\n\nA simple Node.js web server running at `127.0.0.1:4000`:\n\n```javascript\nconst express = require(\"express\");\nconst app = express();\n\napp.get(\"/\", (req ,res) =\u003e {\n  res.type(\"text/event-stream\").flushHeaders();\n});\n\napp.listen(4000);\n```\n\nStart the server and the reverse proxy:\n\n```console\n$ npm install\n$ node index.js # Leave running, continue on another terminal\n$ caddy run # Leave running, continue on another terminal\n```\n\nWhen I make a request to the server directly (without Caddy), I get the response I expect:\n\n```console\n$ curl -Nv http://localhost:4000\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 4000 (#0)\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:4000\n\u003e User-Agent: curl/7.64.1\n\u003e Accept: */*\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c X-Powered-By: Express\n\u003c Content-Type: text/event-stream; charset=utf-8\n\u003c Date: Mon, 19 Jul 2021 14:25:59 GMT\n\u003c Connection: keep-alive\n\u003c Keep-Alive: timeout=5\n\u003c Transfer-Encoding: chunked\n```\n\nNote how the response headers came through.\n\nBut when I request via Caddy, the following happens:\n\n```console\n$ curl -Nv https://localhost    \n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n* successfully set certificate verify locations:\n*   CAfile: /etc/ssl/cert.pem\n  CApath: none\n* TLSv1.2 (OUT), TLS handshake, Client hello (1):\n* TLSv1.2 (IN), TLS handshake, Server hello (2):\n* TLSv1.2 (IN), TLS handshake, Certificate (11):\n* TLSv1.2 (IN), TLS handshake, Server key exchange (12):\n* TLSv1.2 (IN), TLS handshake, Server finished (14):\n* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):\n* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.2 (OUT), TLS handshake, Finished (20):\n* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):\n* TLSv1.2 (IN), TLS handshake, Finished (20):\n* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305\n* ALPN, server accepted to use h2\n* Server certificate:\n*  subject: [NONE]\n*  start date: Jul 19 14:23:15 2021 GMT\n*  expire date: Jul 20 02:23:15 2021 GMT\n*  subjectAltName: host \"localhost\" matched cert's \"localhost\"\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify ok.\n* Using HTTP2, server supports multi-use\n* Connection state changed (HTTP/2 confirmed)\n* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0\n* Using Stream ID: 1 (easy handle 0x13e80f800)\n\u003e GET / HTTP/2\n\u003e Host: localhost\n\u003e User-Agent: curl/7.64.1\n\u003e Accept: */*\n\u003e \n* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!\n```\n\nNow how the response headers didn’t arrive.\n\nIn both cases the connection is kept open, which is the intended behavior.\n\n**Why Is This an Issue?**\n\nAs far as I can tell, browsers doesn’t fire an `open` event on the `EventSource` until the headers are received.\n\n**Other Things I Tried without Success (They Don’t Seem to Affect the Behavior at All):**\n\n- Change my Caddyfile to use `flush_interval -1` (see \u003chttps://caddy.community/t/v2-server-sent-events-from-flask-to-caddy-via-gunicorn/7806/2\u003e).\n\n- Rewrite my Node.js server to read like the following (see \u003chttps://github.com/caddyserver/caddy/issues/3765\u003e):\n\n  ```javascript\n  res.writeHead(200, {\n    'Content-Type': 'text/event-stream',\n    'Cache-Control': 'no-cache',\n    'X-Accel-Buffering': 'no',\n    'Transfer-Encoding': 'chunked',\n  });\n  ```\n\n  (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.)\n\n**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 \u003chttps://caddyserver.com/docs/caddyfile/directives/reverse_proxy#streaming\u003e and \u003chttps://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/streaming.go#L104-L108\u003e).\n\n**A Workaround:** Send a dummy server-sent event to force Caddy to flush the headers:\n\n```javascript\nres.type(\"text/event-stream\").write(\":\\n\\n\");\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eVersions\u003c/strong\u003e\u003c/summary\u003e\n\n```console\n$ node --version\nv16.5.0\n$ caddy version\nv2.4.3 h1:Y1FaV2N4WO3rBqxSYA8UZsZTQdN+PwcoOcAiZTM8C0I=\n```\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafac%2Fcaddy-express-sse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleafac%2Fcaddy-express-sse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafac%2Fcaddy-express-sse/lists"}