frankenphp icon indicating copy to clipboard operation
frankenphp copied to clipboard

Server-Sent-Events not working through HTTP/1.1

Open ninsuo opened this issue 1 year ago • 9 comments

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 [email protected]: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āŽ                            

ninsuo avatar Mar 28 '24 09:03 ninsuo

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 avatar Mar 28 '24 18:03 withinboredom

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

withinboredom avatar Mar 28 '24 19:03 withinboredom

Thank you @withinboredom , I only tried using Docker.

ninsuo avatar Mar 28 '24 19:03 ninsuo

@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 avatar Apr 18 '24 17:04 LukeAbell

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)

LukeAbell avatar Apr 18 '24 17:04 LukeAbell

@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.

dunglas avatar Apr 18 '24 19:04 dunglas

@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.

LukeAbell avatar Apr 18 '24 19:04 LukeAbell

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

dunglas avatar Apr 18 '24 19:04 dunglas

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.

dunglas avatar Apr 18 '24 19:04 dunglas