Bug #73936 [Fbk->Opn]: Certain special header() calls wrongly prevent connection from closing
| From: | cmb@php.net | Date: | Mon, 08 Nov 2021 09:50:26 +0000 |
| Subject: | Bug #73936 [Fbk->Opn]: Certain special header() calls wrongly prevent connection from closing | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-237600@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=73936&edit=1
ID: 73936
Updated by: cmb@php.net
Reported by: markamery at btinternet dot com
Summary: Certain special header() calls wrongly prevent
connection from closing
-Status: Feedback
+Status: Open
Type: Bug
Package: Apache2 related
Operating System: Ubuntu
-PHP Version: Irrelevant
+PHP Version: 7.4.3
-Assigned To: cmb
+Assigned To:
Block user comment: N
Private report: N
New Comment:
Well, PHP 7.4.3 isn't exactly a current release, but okay.
Previous Comments:
------------------------------------------------------------------------
[2021-11-06 15:19:13] markamery at btinternet dot com
I can reproduce on Ubuntu. I just tried installing PHP and Apache with
sudo apt install php
which gives me these versions:
$ php --version
PHP 7.4.3 (cli) (built: Oct 25 2021 18:20:54) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies
$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built: 2021-10-14T16:24:43
If I create a test file containing
<?php
http_response_code(200);
then ab takes 14ms per request:
$ ab -n 1000 -c 200 localhost/marktest.php
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
Server Software: Apache/2.4.41
Server Hostname: localhost
Server Port: 80
Document Path: /marktest.php
Document Length: 0 bytes
Concurrency Level: 200
Time taken for tests: 0.070 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 166000 bytes
HTML transferred: 0 bytes
Requests per second: 14259.44 [#/sec] (mean)
Time per request: 14.026 [ms] (mean)
Time per request: 0.070 [ms] (mean, across all concurrent requests)
Transfer rate: 2311.59 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 5 1.0 5 7
Processing: 3 8 2.1 9 12
Waiting: 0 6 1.7 7 8
Total: 4 13 1.9 13 16
Percentage of the requests served within a certain time (ms)
50% 13
66% 14
75% 14
80% 14
90% 15
95% 15
98% 16
99% 16
100% 16 (longest request)
but if I change it to call
header('HTTP/1.1 200 OK');
instead, then it take 8 seconds per request:
$ ab -n 1000 -c 200 localhost/marktest.php
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
Server Software: Apache/2.4.41
Server Hostname: localhost
Server Port: 80
Document Path: /marktest.php
Document Length: 0 bytes
Concurrency Level: 200
Time taken for tests: 41.478 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 147000 bytes
HTML transferred: 0 bytes
Requests per second: 24.11 [#/sec] (mean)
Time per request: 8295.664 [ms] (mean)
Time per request: 41.478 [ms] (mean, across all concurrent requests)
Transfer rate: 3.46 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.1 0 4
Processing: 5005 7684 2262.9 6939 15408
Waiting: 1 2680 2262.7 1935 10404
Total: 5006 7685 2263.6 6940 15410
Percentage of the requests served within a certain time (ms)
50% 6940
66% 7055
75% 7062
80% 8056
90% 11452
95% 13384
98% 14394
99% 15018
100% 15410 (longest request)
------------------------------------------------------------------------
[2021-11-05 19:17:29] cmb@php.net
Anyway, I cannot reproduce any difference between the three
scripts with PHP-7.4 on Windows using Apache 2.4.39.0. The
response always has
Connection: Keep-Alive
Keep-Alive: timeout=5, max=100
and the connection is closed after ~ 5 seconds.
If I add
Connection: close
to the request, the connection is immediately closed after the
response has been received.
So either the issue has been fixed in the meantime, or is specific
to some Apache versions or the operating system.
Can you still reproduce this with any of the actively supported
PHP versions[1]? If so, what's your Apache version?
[1] <https://www.php.net/supported-versions.php>
------------------------------------------------------------------------
[2021-11-05 14:22:56] markamery at btinternet dot com
> > header('http/1.1 200 OK')
> This is not valid according to RFC 7230, since the HTTP-name needs
> to be upper case
It's valid according to PHP's own docs at https://www.php.net/manual/en/function.header.php,
which state that
> There are two special-case header calls. The first is a header that starts with the string
> "HTTP/" (case is not significant) ...
For whatever reason, the design decision has been made by PHP to let you pass such strings in
lowercase and then convert them to uppercase for you to make them spec-compliant. The really
mysterious thing is that invoking that case-conversion behaviour somehow prevents this bug from
exhibiting.
------------------------------------------------------------------------
[2021-11-04 14:03:49] cmb@php.net
> Unless
Connection: keep-alive is specified in the request, the
> connection should close once the PHP script finishes.
For HTTP/1.1 requests, keep-alive is the default. Only HTTP/1.0
defaults to close.[1]
> header('http/1.1 200 OK')
This is not valid according to RFC 7230, since the HTTP-name needs
to be upper case[2].
> http_response_code(200);
I tentatively agree that this should behave identically to
header('HTTP/1.1 200 OK'). I'll' have a closer look.
[1] <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection>
[2] <https://datatracker.ietf.org/doc/html/rfc7230#appendix-B>
------------------------------------------------------------------------
[2017-01-14 18:38:54] markamery at btinternet dot com
Description:
------------
Running under mod_php, calling
header('HTTP/1.1 200 OK');
seems to prevent the connection from being closed once the PHP script finishes. This can be
demonstrated by, for instance, hitting the script with ab (the Apache benchmark tool), which (unlike
most HTTP clients, including browsers) only considers a request to have finished once the connection
is closed, rather than when Content-Length bytes have been received. http://stackoverflow.com/q/34367115/1709587
describes this case in more detail.
Bizarrely,
header('http/1.1 200 OK');
does not have the same effect, despite producing a character-for-character identical HTTP response
(as can be observed with curl -i --raw).
Test script:
---------------
<?php
header('HTTP/1.1 200 OK');
Expected result:
----------------
Unless Connection: keep-alive is specified in the request, the connection should close
once the PHP script finishes. In particular, the behaviour should be identical to other calls with
the same meaning, like
<?php
http_response_code(200);
or
<?php
header('http/1.1 200 OK')
Actual result:
--------------
The connection does not close, and the alternate scripts suggested above have different behaviours
to the test script.
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=73936&edit=1