Skip to content

Transfer-Encoding: chunked causing php_fastcgi to hang #5236

@arabcoders

Description

@arabcoders

Hello, I was attempting to support Transfer-Encoding: chunked header into my application, and noticed that the server never responses if the header is used.

$ caddy version
v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=

Caddyfile

{
	debug
	log stdout
	admin 0.0.0.0:2018
	grace_period 1s
}

:8888 {
	log
	root * /home/user/test
	php_fastcgi unix//var/run/php/php8.1-fpm.sock {
		#buffer_requests
		#flush_interval -1
	}
}

php file (index.php)

<?php
echo "i am alive\n";

Example of working request/response

$ curl -k http://localhost:8888/ --header "Pragma: no-cache" -X POST -d file=test

i am alive

Caddy Logs

2022/12/06 14:41:18.348 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "127.0.0.1", "remote_port": "49996", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/", "headers": {"Content-Length": ["9"], "Content-Type": ["application/x-www-form-urlencoded"], "Cache-Control": ["no-cache"], "User-Agent": ["curl/7.81.0"], "Accept": ["*/*"], "Pragma": ["no-cache"]}}, "method": "POST", "uri": "/index.php"}
2022/12/06 14:41:18.348 DEBUG   http.handlers.reverse_proxy     selected upstream       {"dial": "/var/run/php/php8.1-fpm.sock", "total_upstreams": 1}
2022/12/06 14:41:18.348 DEBUG   http.reverse_proxy.transport.fastcgi    roundtrip       {"request": {"remote_ip": "127.0.0.1", "remote_port": "49996", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/index.php", "headers": {"User-Agent": ["curl/7.81.0"], "Pragma": ["no-cache"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:8888"], "Cache-Control": ["no-cache"], "Accept": ["*/*"], "Content-Length": ["9"], "Content-Type": ["application/x-www-form-urlencoded"]}}, "env": {"GATEWAY_INTERFACE": "CGI/1.1", "REMOTE_IDENT": "", "CONTENT_LENGTH": "9", "SERVER_PORT": "8888", "HTTP_CONTENT_LENGTH": "9", "REMOTE_HOST": "127.0.0.1", "REMOTE_PORT": "49996", "HTTP_HOST": "localhost:8888", "HTTP_CACHE_CONTROL": "no-cache", "HTTP_PRAGMA": "no-cache", "REQUEST_METHOD": "POST", "DOCUMENT_ROOT": "/home/user/test", "REQUEST_URI": "/", "REMOTE_ADDR": "127.0.0.1", "REMOTE_USER": "", "HTTP_CONTENT_TYPE": "application/x-www-form-urlencoded", "PATH_INFO": "", "SERVER_PROTOCOL": "HTTP/1.1", "HTTP_ACCEPT": "*/*", "CONTENT_TYPE": "application/x-www-form-urlencoded", "AUTH_TYPE": "", "REQUEST_SCHEME": "http", "SERVER_NAME": "localhost", "SCRIPT_FILENAME": "/home/user/testindex.php", "HTTP_X_FORWARDED_PROTO": "http", "HTTP_X_FORWARDED_HOST": "localhost:8888", "HTTP_USER_AGENT": "curl/7.81.0", "QUERY_STRING": "", "SERVER_SOFTWARE": "Caddy/v2.6.2", "DOCUMENT_URI": "/index.php", "SCRIPT_NAME": "/index.php", "HTTP_X_FORWARDED_FOR": "127.0.0.1"}, "dial": "/var/run/php/php8.1-fpm.sock", "env": {"REQUEST_METHOD": "POST", "DOCUMENT_ROOT": "/home/user/test", "REQUEST_URI": "/", "REMOTE_ADDR": "127.0.0.1", "REMOTE_USER": "", "HTTP_CONTENT_TYPE": "application/x-www-form-urlencoded", "PATH_INFO": "", "SERVER_PROTOCOL": "HTTP/1.1", "HTTP_ACCEPT": "*/*", "CONTENT_TYPE": "application/x-www-form-urlencoded", "SCRIPT_FILENAME": "/home/user/testindex.php", "HTTP_X_FORWARDED_PROTO": "http", "HTTP_X_FORWARDED_HOST": "localhost:8888", "HTTP_USER_AGENT": "curl/7.81.0", "AUTH_TYPE": "", "REQUEST_SCHEME": "http", "SERVER_NAME": "localhost", "SCRIPT_NAME": "/index.php", "HTTP_X_FORWARDED_FOR": "127.0.0.1", "QUERY_STRING": "", "SERVER_SOFTWARE": "Caddy/v2.6.2", "DOCUMENT_URI": "/index.php", "SERVER_PORT": "8888", "HTTP_CONTENT_LENGTH": "9", "GATEWAY_INTERFACE": "CGI/1.1", "REMOTE_IDENT": "", "CONTENT_LENGTH": "9", "HTTP_CACHE_CONTROL": "no-cache", "HTTP_PRAGMA": "no-cache", "REMOTE_HOST": "127.0.0.1", "REMOTE_PORT": "49996", "HTTP_HOST": "localhost:8888"}, "request": {"remote_ip": "127.0.0.1", "remote_port": "49996", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/index.php", "headers": {"Cache-Control": ["no-cache"], "Accept": ["*/*"], "Content-Length": ["9"], "Content-Type": ["application/x-www-form-urlencoded"], "User-Agent": ["curl/7.81.0"], "Pragma": ["no-cache"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:8888"]}}}
2022/12/06 14:41:18.349 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "unix//var/run/php/php8.1-fpm.sock", "duration": 0.000485466, "request": {"remote_ip": "127.0.0.1", "remote_port": "49996", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/index.php", "headers": {"Accept": ["*/*"], "Content-Length": ["9"], "Content-Type": ["application/x-www-form-urlencoded"], "Cache-Control": ["no-cache"], "Pragma": ["no-cache"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:8888"], "User-Agent": ["curl/7.81.0"]}}, "headers": {"Content-Type": ["text/html; charset=UTF-8"]}, "status": 200}
2022/12/06 14:41:18.349 INFO    http.log.access handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "49996", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/", "headers": {"Cache-Control": ["no-cache"], "User-Agent": ["curl/7.81.0"], "Accept": ["*/*"], "Pragma": ["no-cache"], "Content-Length": ["9"], "Content-Type": ["application/x-www-form-urlencoded"]}}, "user_id": "", "duration": 0.000779217, "size": 1788, "status": 200, "resp_headers": {"Server": ["Caddy"], "Content-Type": ["text/html; charset=UTF-8"]}}

Example of not working request/response

$ curl -k http://localhost:8888/ --header "Pragma: no-cache" -X POST -d file=test --header "Transfer-Encoding: chunked" -vvv
hangs forever. no response.

Caddy logs.

2022/12/06 14:46:23.786 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "127.0.0.1", "remote_port": "43034", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/", "headers": {"Accept": ["*/*"], "User-Agent": ["curl/7.81.0"], "Pragma": ["no-cache"], "Content-Type": ["application/x-www-form-urlencoded"], "Cache-Control": ["no-cache"]}}, "method": "POST", "uri": "/index.php"}
2022/12/06 14:46:23.786 DEBUG   http.handlers.reverse_proxy     selected upstream       {"dial": "/var/run/php/php8.1-fpm.sock", "total_upstreams": 1}
2022/12/06 14:46:23.786 DEBUG   http.reverse_proxy.transport.fastcgi    roundtrip       {"request": {"remote_ip": "127.0.0.1", "remote_port": "43034", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/index.php", "headers": {"Pragma": ["no-cache"], "Content-Type": ["application/x-www-form-urlencoded"], "Cache-Control": ["no-cache"], "User-Agent": ["curl/7.81.0"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:8888"], "Accept": ["*/*"]}}, "env": {"SCRIPT_FILENAME": "/home/user/test/index.php", "HTTP_CONTENT_TYPE": "application/x-www-form-urlencoded", "HTTP_USER_AGENT": "curl/7.81.0", "HTTP_X_FORWARDED_PROTO": "http", "AUTH_TYPE": "", "REMOTE_IDENT": "", "REMOTE_USER": "", "REQUEST_SCHEME": "http", "DOCUMENT_ROOT": "/home/user/test", "SERVER_NAME": "localhost", "HTTP_X_FORWARDED_FOR": "127.0.0.1", "REMOTE_ADDR": "127.0.0.1", "HTTP_X_FORWARDED_HOST": "localhost:8888", "PATH_INFO": "", "QUERY_STRING": "", "SERVER_SOFTWARE": "Caddy/v2.6.2", "GATEWAY_INTERFACE": "CGI/1.1", "SERVER_PROTOCOL": "HTTP/1.1", "REQUEST_URI": "/", "HTTP_ACCEPT": "*/*", "HTTP_PRAGMA": "no-cache", "HTTP_CACHE_CONTROL": "no-cache", "CONTENT_LENGTH": "", "REMOTE_HOST": "127.0.0.1", "DOCUMENT_URI": "/index.php", "CONTENT_TYPE": "application/x-www-form-urlencoded", "REMOTE_PORT": "43034", "REQUEST_METHOD": "POST", "HTTP_HOST": "localhost:8888", "SCRIPT_NAME": "/index.php", "SERVER_PORT": "8888"}, "dial": "/var/run/php/php8.1-fpm.sock", "env": {"REMOTE_ADDR": "127.0.0.1", "HTTP_X_FORWARDED_HOST": "localhost:8888", "PATH_INFO": "", "QUERY_STRING": "", "SERVER_SOFTWARE": "Caddy/v2.6.2", "HTTP_ACCEPT": "*/*", "HTTP_PRAGMA": "no-cache", "HTTP_CACHE_CONTROL": "no-cache", "GATEWAY_INTERFACE": "CGI/1.1", "SERVER_PROTOCOL": "HTTP/1.1", "REQUEST_URI": "/", "CONTENT_LENGTH": "", "REMOTE_HOST": "127.0.0.1", "DOCUMENT_URI": "/index.php", "HTTP_HOST": "localhost:8888", "SCRIPT_NAME": "/index.php", "SERVER_PORT": "8888", "CONTENT_TYPE": "application/x-www-form-urlencoded", "REMOTE_PORT": "43034", "REQUEST_METHOD": "POST", "HTTP_X_FORWARDED_PROTO": "http", "SCRIPT_FILENAME": "/home/user/test/index.php", "HTTP_CONTENT_TYPE": "application/x-www-form-urlencoded", "HTTP_USER_AGENT": "curl/7.81.0", "REQUEST_SCHEME": "http", "DOCUMENT_ROOT": "/home/user/test", "AUTH_TYPE": "", "REMOTE_IDENT": "", "REMOTE_USER": "", "SERVER_NAME": "localhost", "HTTP_X_FORWARDED_FOR": "127.0.0.1"}, "request": {"remote_ip": "127.0.0.1", "remote_port": "43034", "proto": "HTTP/1.1", "method": "POST", "host": "localhost:8888", "uri": "/index.php", "headers": {"Content-Type": ["application/x-www-form-urlencoded"], "Cache-Control": ["no-cache"], "User-Agent": ["curl/7.81.0"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:8888"], "Accept": ["*/*"], "Pragma": ["no-cache"]}}}

i also tried with buffer_requests and flush_interval -1 and it seems to have no effect.

This behavior led me to believe it was an issue with PHP itself, however digging deeper i was able to find this issue php bug #60826, reading the replies this suggest the problem is not conforming to fastcgi spec

Essentially, PHP is following the spec, and not reading beyond the FastCGI CONTENT_LENGTH header value - so correct fixes are necessarily above PHP, in the FastCGI or web server implementation.

In a strict sense this is indeed not a PHP bug, because the CGI specification (which is only informational, though), mandates[1]:

As transfer-codings are not supported on the request-body, the server MUST remove any such codings from the message-body, and recalculate the CONTENT_LENGTH. If this is not possible (for example, because of large buffering requirements), the server SHOULD reject the client request.

It seems apache also had the bug and they have somewhat working fix at https://bz.apache.org/bugzilla/show_bug.cgi?id=57087

I tried to see if the request itself is valid i used python gunicorn to test

curl http://httpbin/post --header "Pragma: no-cache" -d file=test --header "Transfer-Encoding: chunked" -vvv
*   Trying 172.17.0.2:80...
* Connected to httpbin (172.17.0.2) port 80 (#0)
> POST /post HTTP/1.1
> Host: httpbin
> User-Agent: curl/7.81.0
> Accept: */*
> Pragma: no-cache
> Transfer-Encoding: chunked
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: gunicorn/19.9.0
< Date: Tue, 06 Dec 2022 15:01:59 GMT
< Connection: keep-alive
< Content-Type: application/json
< Content-Length: 383
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "file": "test"
  },
  "headers": {
    "Accept": "*/*",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin",
    "Pragma": "no-cache",
    "Transfer-Encoding": "chunked",
    "User-Agent": "curl/7.81.0"
  },
  "json": null,
  "origin": "172.17.0.1",
  "url": "http://httpbin/post"
}
* Connection #0 to host httpbin left intact

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🐞Something isn't workinghelp wanted 🆘Extra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions