Search code examples
phphttp-headersfastcgihttp-response-codes

Why is function http_response_code() acting strange that was called after function header() in PHP?


Well. I have a problem with http_response_code(), and I can't find an explanations. If I use header() before http_response_code(), PHP returns HTTP-status which was set by header() and ignores any http_response_code().

For example, I have a file:

<?php
header('HTTP/1.1 404 Not Found');
file_put_contents('./log',http_response_code(501).PHP_EOL,FILE_APPEND);
file_put_contents('./log',http_response_code(502).PHP_EOL,FILE_APPEND);
file_put_contents('./log',http_response_code(503).PHP_EOL,FILE_APPEND);

(I used file_put_contents() for preventing any output, because somebody can say, that it is the answer to this question)

I used default php-server (but the problem can be reproduced with NGINX):

php -S localhost:9985

There is request:

curl -D - http://localhost:9985

There is response:

HTTP/1.1 404 Not Found
Host: localhost:9958
Date: Wed, 15 Sep 2021 17:20:05 GMT
Connection: close
X-Powered-By: PHP/8.0.10
Content-type: text/html; charset=UTF-8

And there is log:

404
501
502

The response includes first line:

HTTP/1.1 404 Not Found 

but I was waiting for HTTP/1.1 503 Service Unavailable. The log file includes an expected data and it shows that http_response_code returned statuses correctly. But it didn't affect for HTTP response code.

I thought that a reason is some data which could have been sent to OUTPUT because I used header(). But if I use header() twice, it doesn't create problems. I changed my file:

<?php
header('HTTP/1.1 404 Not Found');
header('HTTP/1.1 503 Service Unavailable');

And repeated request:

curl -I http://localhost:9958

There was response:

HTTP/1.1 503 Service Unavailable
Host: localhost:9958
Date: Wed, 15 Sep 2021 17:26:08 GMT
Connection: close
X-Powered-By: PHP/8.0.10
Content-type: text/html; charset=UTF-8

This works great! 😭

The problem can be reproduced on PHP 7.2, PHP 7.4, PHP 8. I found a comment https://www.php.net/manual/ru/function.http-response-code.php#125538 where it is. But there is apache. I should say that with if I use Apache, the problem is not reproducible for me.

Please, describe, why the function (http_response_code) doesn't work how expect.

Please, don't advise not to use the function because I would want to know true reason for the behavior of the function (the sacred meaning).


Update: Bug report https://bugs.php.net/bug.php?id=81451&edit=2


Solution

  • Short explanation

    It's because the status line passed to header() is prioritized over http_response_code() in some (all?) PHP SAPI implementations.

    Technical explanation (specific to PHP 8.0.10)

    PHP tracks the status line and HTTP response code in two separate variables: SG(sapi_headers).http_status_line and SG(sapi_headers).http_response_code.

    header('HTTP/1.1 404 Not Found') sets http_status_line to "HTTP/1.1 404 Not Found" here and updates SG(sapi_headers).http_response_code a few lines earlier, while http_response_code(503) only sets SG(sapi_headers).http_response_code to 503 here.

    The code that PHP's builtin server uses for sending the headers can be found in the sapi_cli_server_send_headers function (php_cli_server.c). In that function, we see that SG(sapi_headers).http_response_code is ignored when SG(sapi_headers).http_status_line is set. The sapi_cgi_send_headers function used by PHP-FPM shows a similar story. The apache2handler SAPI uses both http_status_line and http_response_code. Theoretically, they could point to different statuses!

    Bug?

    Maybe. But changing/fixing this behaviour after who knows how many years breaks backwards compatibility, so it should probably be left alone. It's probably best to avoid this situation altogether by sticking to either header() or http_response_code().