使用file_get_contents发送HTTP请求,获取响应代码

106

我正在尝试使用 file_get_contentsstream_context_create 来进行POST请求。目前我的代码如下:

    $options = array('http' => array(
        'method'  => 'POST',
        'content' => $data,
        'header'  => 
            "Content-Type: text/plain\r\n" .
            "Content-Length: " . strlen($data) . "\r\n"
    ));
    $context  = stream_context_create($options);
    $response = file_get_contents($url, false, $context);

它运行良好,但是当发生HTTP错误时,它会发出警告:

file_get_contents(...): failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request

并返回false。有没有办法:

  • 抑制警告(我计划在失败的情况下抛出自己的异常)
  • 从流中获取错误信息(至少是响应代码)
6个回答

177

41
对于那些想知道的人,第一个问题的答案是在 $options 中添加 'ignore_errors' => TRUE - georg

37

没有一个答案(包括被问题提出者接受的答案)实际上满足这两个要求:

  • 抑制警告(如果失败则打算抛出自己的异常)
  • 从流中获取错误信息(至少是响应代码)

以下是我的看法:

function fetch(string $method, string $url, string $body, array $headers = []) {
    $context = stream_context_create([
        "http" => [
            // http://docs.php.net/manual/en/context.http.php
            "method"        => $method,
            "header"        => implode("\r\n", $headers),
            "content"       => $body,
            "ignore_errors" => true,
        ],
    ]);

    $response = file_get_contents($url, false, $context);

    /**
     * @var array $http_response_header materializes out of thin air
     */

    $status_line = $http_response_header[0];

    preg_match('{HTTP\/\S*\s(\d{3})}', $status_line, $match);

    $status = $match[1];

    if ($status !== "200") {
        throw new RuntimeException("unexpected response status: {$status_line}\n" . $response);
    }

    return $response;
}

这将抛出一个非200的响应,但你可以从那里轻松地继续工作,例如添加一个简单的Response类并return new Response((int) $status, $response);如果它更适合你的用例。

例如,要向API端点进行JSON POST

$response = fetch(
    "POST",
    "http://example.com/",
    json_encode([
        "foo" => "bar",
    ]),
    [
        "Content-Type: application/json",
        "X-API-Key: 123456789",
    ]
);

注意在http上下文映射中使用"ignore_errors" => true - 这将防止该函数因非2xx状态代码而抛出错误。

对于大多数用例,这很可能是“正确”的错误抑制量 - 我不建议使用@错误抑制运算符,因为这也会抑制诸如仅仅传递错误参数之类的错误,这可能会无意中隐藏调用代码中的漏洞。


太好了!但我想知道:是否有一种不那么hacky的方法直接获取状态码作为整数值? - at54321
@at54321 不要使用 file_get_contents,那是 PHP 的深度、黑暗时代的设计。在我的客户端中,我最终使用了 fopen,这避免了这种奇怪、神奇的 API - 你可以在这里看到如何做到这一点。 - mindplay.dk
$http_response_header的文档,在调用file_get_contents()后确实会出现。 - GPHemsley

8

在接受的响应中添加几行代码以获取HTTP状态码。

function getHttpCode($http_response_header)
{
    if(is_array($http_response_header))
    {
        $parts=explode(' ',$http_response_header[0]);
        if(count($parts)>1) //HTTP/1.0 <code> <text>
            return intval($parts[1]); //Get code
    }
    return 0;
}

@file_get_contents("http://example.com");
$code=getHttpCode($http_response_header);

为了隐藏错误输出,两种注释都可以使用,ignore_errors = true 或 @(我更喜欢@)


1
“prefer @” 是一个错误的想法。ignore_errors = true 只会抑制 HTTP 错误,而 @ 会阻止所有可能导致程序员无法了解其代码为何不起作用的错误。例如,当未配置 HTTP 包装器或参数未按顺序排列时。使用 @ 是非常非常糟糕的做法。我希望 Stack Overflow 上有更少这样的错误信息。 - Your Common Sense
我建议使用@,但我说你可以同时使用@YourCommonSense。我同意在其他PHP函数中放置@的做法,但在file_get_contents中,如果它不起作用,你只需要删除@来检查发生了什么。 - Hamboy75
但是如果没有错误消息,您将不知道它的file_get_contents出了什么问题。可惜的是,四年中您没有意识到错误消息的重要性。 - Your Common Sense

0
为了在file_get_contents返回FALSE时捕获错误消息,请编写一个函数,该函数使用ob_startob_get_contents来捕获file_get_contents写入stderr的错误消息。
function fileGetContents( $fileName )
{
  $errmsg = '' ;
  ob_start( ) ;
  $contents = file_get_contents( $fileName );
  if ( $contents === FALSE )
  {
    $errmsg = ob_get_contents( ) ;
    $errmsg .= "\nfile name:$fileName";
    $contents = '' ;
  }
  ob_end_clean( ) ;
  return (object)[ 'errmsg' => $errmsg, 'contents' => $contents ];
}

-1

我来到这个页面是因为有一个不同的问题,所以我要发表我的答案。我的问题是我只是想压制警告通知并为用户显示自定义警告消息,所以这个简单而明显的修复方法对我很有帮助:

// Suppress the warning messages
error_reporting(0);

$contents = file_get_contents($url);
if ($contents === false) {
  print 'My warning message';
}

如果需要的话,之后可以重新打开错误报告:

// Enable warning messages again
error_reporting(-1);

-5

@file_get_contentsignore_errors = true 不是同一个东西: 第一个不返回任何内容; 第二个抑制错误消息,但返回服务器响应(例如400 Bad request)。

我使用类似这样的函数:

$result = file_get_contents(
  $url_of_API, 
  false, 
  stream_context_create([
    'http' => [
      'content' => json_encode(['value1' => $value1, 'value2' => $value2]), 
      'header' => 'Authorization: Basic XXXXXXXXXXXXXXX', 
      'ignore_errors' => 1, 
      'method' => 'POST', 
      'timeout' => 10
    ]
  ])
);

return json_decode($result)->status;

它返回200(正常)或400(错误请求)。

它运行完美,比cURL更容易使用。


1
这样做如何解决问题?你能解释一下如何获取响应代码吗? - Nico Haase
1
这仍然是一个糟糕的例子,因为您解析JSON结果时读取了一个名为status的随机字段 - 它与API调用的HTTP状态代码没有任何关联。 - Nico Haase
很明显,我连接的RESTful API返回一个JSON响应,并且我需要知道的(200或400)包含在“状态”字段中。这就是我所需要的,也是我得到的。如果您需要知道HTTP状态代码,请执行以下操作:return $http_response_header[0];但请注意,它不适用于@file_get_contents。 - Besen
这怎么回答原问题呢?为什么您认为 $http_response_header[0] 这个得到高赞的答案在使用 file_get_contents 时不起作用呢? - Nico Haase
它不适用于 @file_get_contents(而不是 file_get_contents)。试一下! - Besen
@Besen,“->status”并不总是一个选项,因为它不是规范的一部分。这是由您所使用的流作为礼貌(很好)公开的属性,但并非标准。如果该流未明确定义此属性,则您的答案将无法提供可行的解决方案。 - MLK.DEV

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