Edit report at https://bugs.php.net/bug.php?id=80931&edit=1
ID: 80931
Comment by: kelunik@php.net
Reported by: gilperon at gmail dot com
Summary: file_get_contents() hangs on PHP 8
Status: Verified
Type: Bug
Package: Streams related
Operating System: any
PHP Version: 7.4
Block user comment: N
Private report: N
New Comment:
Simple reproduce script:
php -r '$server = stream_socket_server("tcp://127.0.0.1:8080"); while ($client =
stream_socket_accept($server)) { fwrite($client, "HTTP/1.1 200 OK\r\ncontent-length:
0\r\n\r\n"); }'
php -r 'file_get_contents("http://127.0.0.1:8080/");'
Previous Comments:
------------------------------------------------------------------------
[2021-04-08 18:34:07] gilperon at gmail dot com
I think you guys could check how CURL does it, because it handles this "buggy coreios
server" well; it does not hang.
Also, could you please take a look at https://stackoverflow.com/questions/34864179/prevent-php-http-wrapper-from-waiting-for-close-of-persistent-connection
? This looks a similar problem from 5 years ago that I just found out talking with people on discord
server.
------------------------------------------------------------------------
[2021-04-08 18:20:30] rowan dot collins at gmail dot com
To confirm the below is enough to make this particular server respond in a way that PHP handles OK:
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index da822d9160..7fdfa9448c 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -574,7 +574,7 @@ finish:
* HTTP/1.0 to avoid issues when the server respond with a HTTP/1.1
* keep-alive response, which is the preferred response type. */
if ((have_header & HTTP_HEADER_CONNECTION) == 0) {
- smart_str_appends(&req_buf, "Connection: close\r\n");
+ smart_str_appends(&req_buf, "Connection: Close\r\n");
}
if (context &&
However, the correct solution is probably to make the PHP implementation forcefully close
connections when the server defaults to Keep-Alive behaviour.
------------------------------------------------------------------------
[2021-04-08 18:00:41] rowan dot collins at gmail dot com
OK, I think I have figured out what's happening here:
* If you send the server an HTTP/1.1 request with the header "Connection: Close", it
acknowledges with "connection: close"; if you send "Connection: close" (as PHP
does), it does not acknowledge it, and presumably defaults to Connection: Keep-Alive
* RFC 7230 clearly states that "Connection options are case-insensitive." so this is
definitely a bug in the server. https://tools.ietf.org/html/rfc7230#section-6.1
* A local and up to date IIS server does not exhibit the bug.
* The server at ws.correios.com.br is probably running an old version of IIS. The headers include
"x-aspnet-version: 4.0.30319" which was released sometime between 2010 and 2012
------------------------------------------------------------------------
[2021-04-08 13:43:25] rowan dot collins at gmail dot com
Note that the PHP client code always sends a "Connection: Close" header in the request,
for both HTTP/1.0 and HTTP/1.1 requests: https://heap.space/xref/php-src/ext/standard/http_fopen_wrapper.c?r=5787f91c#570
For some reason, the server appears to only be honouring that for HTTP/1.0 requests, which makes no
sense, because it's an HTTP/1.1 feature.
------------------------------------------------------------------------
[2021-04-08 12:57:23] danack@php.net
Okay, so looking at the packets, what is happening, from the response on is:
# http 1_0 protocol
6. server sends response.
7. php acks 6.
8. server sends finack.
9. php sends finack.
10. servers acks 9.
# http 1_1 protocol
6. server sends response.
7. php acks 10.
8. php sends finack.
9. server acks 8.
10. server sends finack.
11. php acks 10.
That all looks correct, but the difference is that for http 1.1 the client is initiating the
connection close. In http 1.0 the server is intiating the connection close.
All the packets look okay, according to https://gitlab.com/wireshark/wireshark/-/wikis/TCP-4-times-close
The problem seems to be that for whatever reason, after sending the last ack, PHP is sitting around
doing nothing. btw it does time out after 2 * 60 seconds, which probably confirms the socket is in
the appropriate TIME-WAIT status.
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=80931
--
Edit this bug report at https://bugs.php.net/bug.php?id=80931&edit=1