dunglas / frankenphp

🧟 The modern PHP app server
https://frankenphp.dev
MIT License
6.74k stars 226 forks source link

Server-Sent-Events not working through HTTP/1.1 #690

Closed ninsuo closed 4 months ago

ninsuo commented 6 months ago

What happened?

Following days of investigations on load-balancing problems, we've found out that SSEs seem not working over HTTP/1 with frankenphp.

Using caddy along with php-fpm works well (wait 30s to get a ping):

# OK
curl -kNvs --http1.1 'https://api.draft.wetransform.com/events?channels[]=test'
# OK
curl -kNvs --http2 'https://api.draft.wetransform.com/events?channels[]=test'

But using frankenphp, it doesn't seem to work:

# NOK
curl -kNvs --http1.1 'https://api.wetransform.com/events?channels[]=test'
# OK
curl -kNvs --http2 'https://api.wetransform.com/events?channels[]=test'

Reproduction

I've developed a simple controller that streams a response through SSEs.

You can reproduce and run the following project (you may need to install php and composer):

git clone git@github.com:ninsuo/frankenphp-sse-http1.git test
cd test
composer install
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp dunglas/frankenphp

The following command works as expected:

curl -kNvs --http2 'https://localhost/public/index.php/demo'

The following command does not work (response seems buffered):

curl -kNvs --http1.1 'https://localhost/public/index.php/demo'

Credits

Credits for finding the bug go to @tharyrok

Relevant log output

http 1

$ curl -kNvs --http1.1 'https://localhost/public/index.php/demo'
*   Trying 127.0.0.1:443...
* Connected to localhost (127.0.0.1) port 443 (#0)
* ALPN: offers http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: [NONE]
*  start date: Mar 28 09:05:40 2024 GMT
*  expire date: Mar 28 21:05:40 2024 GMT
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.1
> GET /public/index.php/demo HTTP/1.1
> Host: localhost
> User-Agent: curl/8.1.2
> Accept: */*
> 

^C⏎                            

http 2

$ curl -kNvs --http2 'https://localhost/public/index.php/demo'
*   Trying 127.0.0.1:443...
* Connected to localhost (127.0.0.1) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: [NONE]
*  start date: Mar 28 09:05:40 2024 GMT
*  expire date: Mar 28 21:05:40 2024 GMT
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: localhost]
* h2 [:path: /public/index.php/demo]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x139011e00)
> GET /public/index.php/demo HTTP/2
> Host: localhost
> User-Agent: curl/8.1.2
> Accept: */*
> 
< HTTP/2 200 
< access-control-allow-origin: *
< alt-svc: h3=":443"; ma=2592000
< cache-control: no-cache, private
< content-type: text/event-stream; charset=UTF-8
< date: Thu, 28 Mar 2024 09:28:48 GMT
< server: Caddy
< x-accel-buffering: no
< x-powered-by: PHP/8.3.4
< x-robots-tag: noindex
< 
data:{"time":"09:28:48"}

data:{"time":"09:28:49"}

data:{"time":"09:28:50"}

^C⏎                            
withinboredom commented 6 months ago

You don't mention which version: standalone binary, i.e., the one from the downloads page, or the docker image.

One is a static build of PHP that has it's own php.ini, and the other is a more traditional shared lib build that uses the regular docker php.ini.

Regardless, I have a suspicion on the issue; I'll take a look.

withinboredom commented 6 months ago

I found the issue, and I'm creating a PR.

ninsuo commented 6 months ago

Thank you @withinboredom , I only tried using Docker.

LukeAbell commented 5 months ago

@withinboredom Any idea when we can get #692 merged? I'm pretty sure it's causing our issues with SSEs in our laravel application on production using the docker container.

LukeAbell commented 5 months ago

Scratch that, I might be experiencing a different problem. It's working locally on my M1 mac using http2, but not on production using http3. (Laravel Octane)

dunglas commented 5 months ago

@LukeAbell Indeed, this problem le should only occur when using HTTP/1, not when using 2 or 3.

We're waiting for a new Caddy release to merge #692. The Caddy team is working on it.

LukeAbell commented 5 months ago

@dunglas Got it. Any idea what would cause it to work locally but not on prod using docker? I've confirmed the PHP config is the same. I can open a different ticket if that's better.

dunglas commented 5 months ago

To be honest I've no idea. A reproducer would be great!

dunglas commented 5 months ago

And yes, please open another issue as this one will be closed when we will merge the current fix, that is unlikely to fix the issue you're describing.