PHP:如何发送HTTP响应代码?

333

我有一个PHP脚本需要使用HTTP响应码(状态码)来进行响应,例如HTTP 200 OK或一些4XX或5XX的代码。

我该如何在PHP中实现这个功能?


没有一个回答说在 PHP 代码中调用 header() 后该怎么做来处理 404 错误。使用 exit() 函数可以吗? - David Spector
8个回答

565

我刚发现这个问题,认为它需要更全面的答案:

PHP 5.4开始,有三种方法可以实现这个目标:

自己组装响应代码(PHP >= 4.0)

header()函数有一个特殊的用例,可以检测到HTTP响应行,并让您替换为自定义的响应行。

header("HTTP/1.1 200 OK");

然而,这需要对(Fast)CGI PHP 进行特殊处理:
$sapi_type = php_sapi_name();
if (substr($sapi_type, 0, 3) == 'cgi')
    header("Status: 404 Not Found");
else
    header("HTTP/1.1 404 Not Found");

注意:根据HTTP RFCreason phrase可以是任何符合标准的自定义字符串,但为了客户端兼容性,我不建议在那里放置随机字符串。

注意:php_sapi_name()需要PHP 4.0.1

header函数的第三个参数(PHP >= 4.3)

当使用第一种变体时,显然存在一些问题。其中最大的问题我认为是它部分由PHP或Web服务器解析并且文档说明不足。

从4.3开始,header函数有一个第三个参数,让您相对轻松地设置响应代码,但使用它需要第一个参数是非空字符串。以下是两个选项:

header(':', true, 404);
header('X-PHP-Response-Code: 404', true, 404);

我推荐第二个。我已经测试过第一个在所有浏览器上都可以工作,但是一些小型浏览器或网络爬虫可能会对只包含冒号的标题行有问题。第二个变体中的标题字段名称当然没有任何标准化,并且可以进行修改,我只选择了一个希望具有描述性的名称。

http_response_code函数 (PHP >= 5.4)

http_response_code()函数是在PHP 5.4中引入的,它使事情变得容易得多

http_response_code(404);

我为您准备了一个函数,用于兼容低于5.4版本的PHP,但仍然想要“新”的http_response_code功能。我认为PHP 4.3已经足够向后兼容,但是您永远不知道...

兼容性

这里是一个函数,当我需要低于5.4的兼容性但又想要“新”的http_response_code功能时,就会用到它。我相信PHP 4.3已经足够向后兼容,但您永远不知道...

That's all.

// For 4.3.0 <= PHP <= 5.4.0
if (!function_exists('http_response_code'))
{
    function http_response_code($newcode = NULL)
    {
        static $code = 200;
        if($newcode !== NULL)
        {
            header('X-PHP-Response-Code: '.$newcode, true, $newcode);
            if(!headers_sent())
                $code = $newcode;
        }       
        return $code;
    }
}

1
@BobStein-VisiBone (1) 如果内容已经发送,而你不能再添加任何头信息,则 headers_sent() 为真,而不是你已经添加了头信息。 (2) 抱歉,不行。其他语言有更好的支持。 - dualed
1
@Perry,我不建议这样做的原因与我不建议只使用冒号一样。因为PHP在不同版本中可能会以不同的方式处理它,因为并未定义针对此类“header”的处理方式,它可能完全失败 - 既不设置header也不设置status,或者它可能添加一个无效的header(http1.1协议标准要求必须有冒号)。 - dualed
18
我花了几个小时的时间才意识到,在使用echo输出内容后,http_response_code(以及更一般地修改头部)不再起作用。希望这有所帮助。 - Neptilo
3
http_response_code() 不能用于自定义错误代码。例如,http_response_code(930) 将导致 Apache 日志文件正确显示 930,但实际上会向客户端发送错误 500。对于这种奇怪的用例,请改用 header() 方法。 - jlh
1
请注意,http_response_code()函数在读取状态码方面是不可靠的。使用header()函数设置的头部信息优先级更高,并且HTTP/1.1 <code> <format>格式优先于Status: <code> <msg>格式 - 至少在FCGI和nginx上是如此。 - undefined
显示剩余7条评论

54
很不幸,我发现@dualed提出的解决方案存在各种缺陷。
使用substr($sapi_type, 0, 3) == 'cgi'无法足够检测到FastCGI。当使用PHP-FPM FastCGI进程管理器时,php_sapi_name()返回的是fpm而不是cgi。
Fasctcgi和php-fpm还会暴露出另一个由@Josh提到的bug - 在PHP-FPM(FastCGI)下使用header('X-PHP-Response-Code: 404', true, 404);无法正常工作。
当协议不是HTTP/1.1(例如'HTTP/1.0')时,header("HTTP/1.1 404 Not Found");可能会失败。当前协议必须使用$_SERVER['SERVER_PROTOCOL']来检测(自PHP 4.1.0起可用)。
在调用http_response_code()时,至少有两种情况会导致意外行为: - 当PHP遇到无法理解的HTTP响应代码时,PHP会用同一组中它了解的代码替换该代码。例如,"521 Web server is down"会被替换为"500 Internal Server Error"。许多其他来自2xx、3xx、4xx等其他组的不常见响应代码也会以这种方式处理。 - 在具有php-fpm和nginx的服务器上,http_response_code()函数可能会按预期更改代码,但不会更改消息。这可能导致出现奇怪的"404 OK"头。这个问题也在PHP网站上的Richard F.的用户评论中提到过。
以下是HTTP响应状态码的完整列表供您参考(此列表包括来自IETF互联网标准以及其他IETF RFC的代码。其中许多代码目前不受PHP http_response_code函数的支持):http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 您可以通过调用以下方式轻松测试此错误:
http_response_code(521);

如果您的自定义客户端应用程序期望额外的HTTP代码,服务器将发送"500 Internal Server Error" HTTP响应代码,从而导致错误。
我的解决方案(适用于所有PHP版本,自4.1.0起):
$httpStatusCode = 521;
$httpStatusMsg  = 'Web server is down';
$phpSapiName    = substr(php_sapi_name(), 0, 3);
if ($phpSapiName == 'cgi' || $phpSapiName == 'fpm') {
    header('Status: '.$httpStatusCode.' '.$httpStatusMsg);
} else {
    $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
    header($protocol.' '.$httpStatusCode.' '.$httpStatusMsg);
}

结论

http_response_code() 的实现不支持所有的HTTP响应代码,并且可能会用同一组中的另一个代码覆盖指定的HTTP响应代码。

新的http_response_code() 函数并没有解决所有的问题,反而引入了新的错误。

@dualed 提供的“兼容性”解决方案并不能按预期工作,至少在PHP-FPM下不行。

@dualed 提供的其他解决方案也存在各种错误。快速CGI检测无法处理PHP-FPM。必须检测当前协议。

欢迎进行任何测试和评论。


适用于PHP 8.0 / Gecko和Blink浏览器上的HTTP 429 Too Many Requests。 - John

35
自 PHP 5.4 以来,您可以使用 http_response_code() 来获取和设置标头状态码。以下是一个示例:
<?php

// Get the current response code and set a new one
var_dump(http_response_code(404));

// Get the new response code
var_dump(http_response_code());
?>

这里是php.net上关于该函数的文档:

http_response_code


根据我的经验,这是最好的答案。 - Scruffy
为什么要使用 var_dump()? - Tomas Gonzalez
1
为什么要使用var_dump()而不是echo?结果难道不适合简单的echo吗?甚至print_r()也可以。var_dump()似乎对于生产代码来说并不足够。 - Tomas Gonzalez
@TomasGonzalez 这不是什么大事,我只是想通过使用 var_dump() 打印出所有内容来向您展示其中的内容,而且您是正确的,它们并不重要。 - Seyed Ali Roshan
好的,我明白了。引起我的注意的是,在官方文档中,示例也使用了var_dump()。所以我很好奇为什么要这样做。可能有些东西我没有理解到。https://www.php.net/manual/en/function.http-response-code.php - Tomas Gonzalez
1
@TomasGonzalez 如果您查看刚提到的页面中的“返回值”部分,您将了解“http_response_code”将返回哪些值。至于为什么使用“var_dump”而不是像“echo”这样的东西,您可以查看这些函数的工作方式。 “echo”只能获取字符串作为输入,而var_dump可以使用您提供的任何内容,并输出变量及其类型。 - Morteza Sadri

14

如果您没有使用输出缓冲,请在任何body输出之前添加此行。

header("HTTP/1.1 200 OK");

将消息部分('OK')替换为适当的消息,状态码也根据情况替换为您的代码(404、501等)


3
替代“OK”的信息可以是任何内容吗? - FMaz008
这对我很有用。我正在使用PHP 5.3在网站上开发联系表单,而这个解决方案对我有效。它将为AJAX请求提供响应文本和HTTP代码,以便在失败函数中使用。这就是我想要的全部内容。 - Surjith S M
返回 header("HTTP/1.1 200 OK"); - Ishmael Mavor Raines

7

如果您因为WordPress在加载环境时出现404错误而来到这里,这应该可以解决问题:

define('WP_USE_THEMES', false);
require('../wp-blog-header.php');
status_header( 200 );
//$wp_query->is_404=false; // if necessary

问题是由于它发送了一个状态为“404 Not Found”的标头。您需要覆盖它。 这个也可以起作用:
define('WP_USE_THEMES', false);
require('../wp-blog-header.php');
header("HTTP/1.1 200 OK");
header("Status: 200 All rosy");

6
使用header函数。在第一个参数所取的示例中,有一些部分需要注意。

4

非常感谢,"http_response_code(201);" 已经生效。 - tej shah

2
如果您的PHP版本不包括此函数:
<?php

function http_response_code($code = NULL) {
        if ($code !== NULL) {
            switch ($code) {
                case 100: $text = 'Continue';
                    break;
                case 101: $text = 'Switching Protocols';
                    break;
                case 200: $text = 'OK';
                    break;
                case 201: $text = 'Created';
                    break;
                case 202: $text = 'Accepted';
                    break;
                case 203: $text = 'Non-Authoritative Information';
                    break;
                case 204: $text = 'No Content';
                    break;
                case 205: $text = 'Reset Content';
                    break;
                case 206: $text = 'Partial Content';
                    break;
                case 300: $text = 'Multiple Choices';
                    break;
                case 301: $text = 'Moved Permanently';
                    break;
                case 302: $text = 'Moved Temporarily';
                    break;
                case 303: $text = 'See Other';
                    break;
                case 304: $text = 'Not Modified';
                    break;
                case 305: $text = 'Use Proxy';
                    break;
                case 400: $text = 'Bad Request';
                    break;
                case 401: $text = 'Unauthorized';
                    break;
                case 402: $text = 'Payment Required';
                    break;
                case 403: $text = 'Forbidden';
                    break;
                case 404: $text = 'Not Found';
                    break;
                case 405: $text = 'Method Not Allowed';
                    break;
                case 406: $text = 'Not Acceptable';
                    break;
                case 407: $text = 'Proxy Authentication Required';
                    break;
                case 408: $text = 'Request Time-out';
                    break;
                case 409: $text = 'Conflict';
                    break;
                case 410: $text = 'Gone';
                    break;
                case 411: $text = 'Length Required';
                    break;
                case 412: $text = 'Precondition Failed';
                    break;
                case 413: $text = 'Request Entity Too Large';
                    break;
                case 414: $text = 'Request-URI Too Large';
                    break;
                case 415: $text = 'Unsupported Media Type';
                    break;
                case 500: $text = 'Internal Server Error';
                    break;
                case 501: $text = 'Not Implemented';
                    break;
                case 502: $text = 'Bad Gateway';
                    break;
                case 503: $text = 'Service Unavailable';
                    break;
                case 504: $text = 'Gateway Time-out';
                    break;
                case 505: $text = 'HTTP Version not supported';
                    break;
                default:
                    exit('Unknown http status code "' . htmlentities($code) . '"');
                    break;
            }
            $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
            header($protocol . ' ' . $code . ' ' . $text);
            $GLOBALS['http_response_code'] = $code;
        } else {
            $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200);
        }
        return $code;
    }

这似乎是10年前在此处发布的完全相同的代码。如果是这种情况,在SO上给出来源是非常好的做法。 - Todd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接