10

I just discovered an oddity in PHP's header() method silently converting some of my statuses to 500. Since I had no luck in finding mention of this behavior in various web searches, I'm adding this here in the hope of saving others some aggravation, but also to ask if anyone has discovered a better workaround (either with PHP or Zend1) than I've come up with.

Given a simple PHP script like:

<?php
header('HTTP/1.1 429');
echo "Too Many Requests\n";

I would expect to receive something like:

HTTP/1.1 429
Date: Thu, 18 Jul 2013 22:19:45 GMT
Content-Length: 11
Content-Type: text/html; charset=UTF-8

Too Many Requests

Instead, it actually returns:

HTTP/1.1 500 Internal Server Error
Date: Thu, 18 Jul 2013 22:19:45 GMT
Content-Length: 11
Content-Type: text/html; charset=UTF-8

Too Many Requests

Adding to the mystery, there are no events in my apache error log, and the access log shows the correct status code (thus different from what got sent to the browser):

$IP - - [18/Jul/2013:16:31:34 -0700] "GET /test/429.php HTTP/1.1" 429 11 "-" "curl/7.30.0"

Everything works fine when testing with many other status codes like 401, 420, 426.

Everything also works fine if I am explicit and send header('HTTP/1.1 429 Too Many Requests'); This would be a useful workaround except that I'm using Zend Framework and its setHttpResponseCode method expects an integer, which it uses as the third parameter to php's header() function.

I've since discovered that it seems to apply specifically to statuses added in RFC 6585 (see https://github.com/php/php-src/pull/274), though I'm a little confused why statuses like 426 work when they're clearly not present in the source code for 5.4.14 and 5.4.16 (the two versions I've tested against) but non-functional ones like 429 are.

Update:

As answers have indicated, this is mostly an Apache issue, not PHP, I've updated the title accordingly. Most interesting seems to be that this is fixed only in certain versions of Apache (with no apparent consistency between old and new). I believe the upstream issue in question is here: https://issues.apache.org/bugzilla/show_bug.cgi?id=44995

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
ex-nerd
  • 1,317
  • 12
  • 19
  • For reference, here's a link showing the 429 clearly exists in the PHP source: https://github.com/php/php-src/blob/PHP-5.4.14/sapi/cgi/cgi_main.c#L388 – ex-nerd Jul 18 '13 at 23:12
  • 1
    FWIW I can't dupe this using the built-in dev server in 5.4.16. You're testing using Apache, right? – Charles Jul 18 '13 at 23:16
  • 1
    there is also a parameter to set the return code. Have you tried that one? Also a script that permutates many of the codes just starting from 100 up to 499 step size of one would be good to know which ones are possible and which ones not. Also please tell the webserver and version you're using as well as the PHP SAPI so the context is clear. – hakre Jul 18 '13 at 23:17
  • 1
    I can confirm the 3rd parameter doesn't work either, and it's also not client issue (raw tcp connect shows a `500` also), `PHP 5.4.9` as a module. However, I get the feeling Apache is to blame.. – Wrikken Jul 18 '13 at 23:26
  • 1
    I'm just reading the sauce for the method immediately following the code you linked, in particular [line 440](http://lxr.php.net/xref/PHP_5_4/sapi/cgi/cgi_main.c#440) onwards, and I'm wondering for setting a `Status:` header would work? It's a little too late in the day for me to fully comprehend that code, but I do know off the top of my head that `Status` is not a valid HTTP header, so I'm wondering if it's part of the way some CGI implementations work? Seems like it would be worth a shot. – DaveRandom Jul 18 '13 at 23:32
  • Okay, retested with 5.4.17. **Verified** behavior under Apache 2.2, don't see the behavior under the built-in server. – Charles Jul 18 '13 at 23:35

2 Answers2

5

It's Apache, 99% sure, I can't find it direcly in it's docs, but I can infer it from the test below (Apache version 2.2.22)

Add this in your config:

ErrorDocument 429 Aaargh to heavy

Restart:

$ sudo /etc/init.d/apache2 restart
Syntax error on line 6 of /etc/apache2/conf.d/localized-error-pages:
Unsupported HTTP response code 429
Action 'configtest' failed.
The Apache error log may have more information.
   ...fail!

429 also seems a recent addition in rfc6585, status: proposed, date: April 2012. One year old for HTTP RFCs is... just a baby in my experience. Add to that the process of getting it in Apache, and then in your package repositories... Well, you could try Apache 2.4...

Community
  • 1
  • 1
Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • `but I can infer it from this` - did you forget to add a link? – DaveRandom Jul 18 '13 at 23:40
  • Erm, no, from the stuff that follows.... That apache balks if I try tu use 429 in an `ErrorDocument` context. – Wrikken Jul 18 '13 at 23:44
  • 1
    Yes, that does indeed seem to be the issue. I checked PHP's built-in web server and it returns the 429 correctly. It's a pity I'm stuck on 2.2.x. Time to accelerate plans to migrate to nginx. – ex-nerd Jul 18 '13 at 23:45
  • 1
    A quick test here shows that `nginx/1.2.6` at least supports sending the `429`. – Wrikken Jul 18 '13 at 23:54
  • 1
    Apache also can send that status-line, see: http://stackoverflow.com/a/17735914/367456 – hakre Jul 19 '13 at 00:02
  • Hm, so, some more examination required. @hakre: Judging by your answer, differences are (1) Windows (happen to have PHP as a module handy there?), (2) that cgi / module discrepancy, (3) perhaps in 2.2.N the minor revision matters? – Wrikken Jul 19 '13 at 00:07
  • 4
    It appears the missing status codes are [supported](https://github.com/apache/httpd/blob/2.4.x/modules/http/http_protocol.c#L146) in the 2.4.x branch of Apache, where they [aren't](https://github.com/apache/httpd/blob/2.2.x/modules/http/http_protocol.c#L129) in 2.2.x. There's also a [nice demonstration](https://github.com/apache/httpd/blob/2.2.x/modules/http/http_protocol.c#L125) of the classic Apache "oh sod it, that'll do" attitude. The [commit](https://github.com/apache/httpd/commit/81cf3a6177ce7f6a00bf99a1a15cf1546bfd35c9) in question was, coincidentally, almost exactly a year ago today. – DaveRandom Jul 19 '13 at 00:09
4

It's perhaps your SAPI configuraiton. Last time I tested something similar, the conclusion looked like this:

<?php
header('HTTP/ 429 Too Many Requests', false, 429);
echo "Too Many Requests\n";

Which in your case works still well for me (Apache 2.2 / FCGI / Windows):

>curl -i "http://local.example.com/header-test.php"
HTTP/1.1 429 Too Many Requests
Date: Thu, 18 Jul 2013 23:49:09 GMT
Server: Apache/2.2.22 (Win32) mod_fcgid/2.3.6
X-Powered-By: PHP/5.4.13
Transfer-Encoding: chunked
Content-Type: text/html

Too Many Requests
hakre
  • 193,403
  • 52
  • 435
  • 836
  • Hm, same version (2.2.22) here, neither php as a module nor as `cgi-fcgi` works.... could it be running under Windows has an advantage? – Wrikken Jul 19 '13 at 00:18
  • @Wrikken: Well under windows I can't have `*` inside URLs even it's valid. Apache somehow does a lot of magic stuff for not much of a reason it feels like ... :/ – hakre Jul 19 '13 at 00:36