捕获PHP致命错误

20

我有一个启用了 restful 的 web 服务站点,因此其他网站/ ajax 脚本可以调用该站点以获取/设置数据。 但是,每当 Web 服务站点出现 PHP 致命错误时,返回的 HTTP 状态码是 200 而不是 500。 是否有办法修复它,使得发生致命错误时返回 500 而不是 200?如果不可能,我该如何更改客户端以识别 Web 服务返回的致命错误?


与 https://dev59.com/0nA75IYBdhLWcg3w4tR7 有关。 - cweiske
6个回答

31

PHP在出现致命错误时确实会发送HTTP 500错误。
让我引用这个 PHP bug报告中的评论(一个xdebug bug阻止了这种行为):

它确实会发生。 这需要满足以下条件:

1)display_errors = off
2)在错误发生时还没有发送任何标头
3)此时状态码为200。

<?
    ini_set('display_errors', 0); 
    foobar();
    // This will return a http 500
?>

你可以使用一个关闭函数error_get_last函数捕获致命错误:

register_shutdown_function(function() {
    $lastError = error_get_last();

    if (!empty($lastError) && $lastError['type'] == E_ERROR) {
        header('Status: 500 Internal Server Error');
        header('HTTP/1.0 500 Internal Server Error');
    }
});

2
如果您启用了xdebug,它将覆盖此设置并使用自己的错误处理程序,该处理程序似乎也会返回HTTP 200。 - thaddeusmt

14

一种可能的方法是将默认响应设置为500,如果一切执行成功,则将响应设置为200:

http_response_code(500);
render_my_page();
http_response_code(200);

7
加入一个大的页面缓冲区以防止输出已经开始的臭名昭著的错误,这样就可以了。 - TheJacobTaylor
1
要么确保在脚本开头而不是在棘手的部分之前放置 <?php header('500 Internal Server Error'); ?> - Christian Mann

3
function fatal_error_handler() {

  if (@is_array($e = @error_get_last())) {
    if (isset($e['type']) && $e['type']==1) {
      header("Status: 500 Internal Server Error");
      header("HTTP/1.0 500 Internal Server Error");
      }
    }

}
register_shutdown_function('fatal_error_handler');

3
创建一个自定义错误处理程序(set_error_handler),并调用header(“HTTP / 1.0 500服务不可用”);
编辑:
根据我的答案的第一个评论,您无法捕获真正的致命错误。但是,如果禁用输出缓冲区并且不将错误显示在屏幕上,则PHP将默认设置500个错误代码以处理致命错误。
<?php
        $x = y();
?>

如果屏幕上没有发送任何内容,上述代码将返回500错误代码。

因此,如果你想要这种错误设置正确的代码,请自行进行缓冲:

<?php
        $buffer = 'blah';
        $x = y();  // will trigger 500 error
        echo $buffer;
?>

9
自定义错误处理程序无法捕获致命错误(例如,对非对象的方法进行调用;时间限制/内存限制耗尽)或解析错误。根据文档:以下错误类型无法使用用户自定义函数处理: E_ERROR,E_PARSE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING。http://php.net/set_error_handler - Frank Farmer
是的,这值得一提。我并没有假设他字面上意思是致命的,因为有些人使用它来表示任何无法恢复的错误类型。 - Matthew
然后,您需要禁用所有输出缓冲并在脚本结束之前避免显示任何数据(正如我的编辑答案所示),或者默认为500代码并在成功时调整为200(正如其他人建议的那样)。 - Matthew
你可以使用register_shutdown_function来捕获它们,但这会在执行结束时发生。 - John Hunt

2
我开发了一种方法来捕获PHP中的所有错误类型(几乎所有)!我不确定E_CORE_ERROR是否能够正常工作(我认为它不能处理该错误)!但是,对于其他致命错误(E_ERROR、E_PARSE、E_COMPILE等),只使用一个错误处理程序函数就可以正常工作!这是我的解决方案:
将以下代码放在您的主文件(index.php)中:
<?php

define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | 
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

define('ENV', 'dev');

//Custom error handling vars
define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);
define('LOG_ERRORS', TRUE);

register_shutdown_function('shut');

set_error_handler('handler');

//Function to catch no user error handler function errors...
function shut(){

    $error = error_get_last();

    if($error && ($error['type'] & E_FATAL)){
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }

}

function handler( $errno, $errstr, $errfile, $errline ) {

    switch ($errno){

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR'; break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING'; break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE'; break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE'; break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR'; break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING'; break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR'; break;
        case E_CORE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING'; break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR'; break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING'; break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE'; break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT'; break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR'; break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED'; break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED'; break;

    }

    $message = '<b>'.$typestr.': </b>'.$errstr.' in <b>'.$errfile.'</b> on line <b>'.$errline.'</b><br/>';

    if(($errno & E_FATAL) && ENV === 'production'){

        header('Location: 500.html');
        header('Status: 500 Internal Server Error');

    }

    if(!($errno & ERROR_REPORTING))
        return;

    if(DISPLAY_ERRORS)
        printf('%s', $message);

    //Logging error on php file error log...
    if(LOG_ERRORS)
        error_log(strip_tags($message), 0);

}

ob_start();

@include 'content.php';

ob_end_flush();

?>

我希望这对许多人有所帮助!


这是我第一次看到“Status:” HTTP头。 - wadim

-4
我认为在部署应用程序之前,您应该捕获并修复所有致命错误,因为其中许多是代码错误、缺少包含文件、不存在的对象等开发错误。即使是内存不足的错误也可以通过使用节省内存的编码技术来最小化(其中最大的优势之一是使用非缓冲查询,并在返回结果集时处理数据和发出输出,而不是传递大型数组)。

1
是的,我应该这样做,但在开发过程中有点烦人。 - Jeffrey04
1
有时致命错误是超出您的控制范围的。APC 有时会陷入糟糕的状态,并在您重新启动 Apache 之前抛出错误。然后,在没有任何代码更改的情况下,这些致命错误就神奇地消失了。 - Frank Farmer
2
这个回答基本上是在说:“只需编写完美的代码,并确保它是完美的,以及您的代码所依赖的任何内容也都是完美的。”好的,我会立即开始做。 - Ellesedil

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