【问题标题】:How to parse response headers in PHP?如何在 PHP 中解析响应头?
【发布时间】:2012-09-08 04:13:14
【问题描述】:

我向 REST API 发出了一个 oauth 签名请求,并将响应标头放在一个数组中,如下所示:

[0] => HTTP/1.1 200 OK
[1] => Cache-Control: private
[2] => Transfer-Encoding: chunked
[3] => Content-Type: text/html; charset=utf-8
[4] => Content-Location: https://***
[5] => Server: Microsoft-IIS/7.0
[6] => Set-Cookie: ASP.NET_SessionId=***; path=/; HttpOnly
[7] => X-AspNetMvc-Version: 2.0
[8] => oauth_token: ***
[9] => oauth_token_secret: ***
[10] => X-AspNet-Version: 4.0.30319
[11] => X-Powered-By: ASP.NET
[12] => Date: Sat, 15 Sep 2012 02:01:15 GMT

我想弄清楚如何解析标头以便轻松检索诸如 HTTP 状态代码、Content-Location、oauth_token 和 oauth_token_secret 之类的项目?

【问题讨论】:

    标签: php api rest http-headers


    【解决方案1】:

    您需要迭代数组并检查stripos() 以找到您要查找的标头。在大多数情况下,您会在 : 上使用 explode()(限制为 2 个结果部分),但 HTTP 响应代码将要求您在空格上展开。

    // Get any header except the HTTP response...
    function getResponseHeader($header, $response) {
      foreach ($response as $key => $r) {
         // Match the header name up to ':', compare lower case
         if (stripos($r, $header . ':') === 0) {
            list($headername, $headervalue) = explode(":", $r, 2);
            return trim($headervalue);
         }
      }
    }
    // example:
    echo getResponseHeader("Content-Type");
    // text/html; charset=utf-8
    
    // Get the HTTP response code
    foreach ($response as $key => $r) {
      if (stripos($r, 'HTTP/') === 0) {
        list(,$code, $status) = explode(' ', $r, 3);
        echo "Code: $code, Status: $status";
        break;
      }
    }
    

    【讨论】:

    • 不,你不能这样做。这将盲目地返回不存在但在其值中包含字段名称的标头。例如。 getResponseHeader("Session") 将返回 cookie。
    • 响应代码假定为“HTTP/1.1”。还包含一个错误,其中仅获取状态的第一个字。 “找不到 HTTP/1.1 404”呢?
    • @Phil_1984_ 这是真的。字符串的左锚匹配对于防止值匹配问题是必要的,并且对具有explode() 限制的状态代码进行稍微更智能的解析。以后有时间我会修改这些(函数定义也没有足够的参数)。您随时可以提出修改建议或发布新答案。
    • 请注意,有些标头具有相同的标头名称,例如Set-Cookie,此函数将仅返回它们的第一个标头。
    • 很好的答案,只有一个问题仍然准确。需要考虑其他 HTTP 协议版本,因为现在我们可以让第一个标头看起来像“HTTP/2 200”。所以在你的解析中要注意它!不限stripos($r, 'HTTP/1.1')更好用stripos($r, 'HTTP/')
    【解决方案2】:

    似乎唯一没有: 的标头是HTTP 版本和状态。 执行array_shift 来提取它,遍历其他创建数组,如下所示:

    $parsedHeaders = array();
    foreach ($headers as $header) {
        if (! preg_match('/^([^:]+):(.*)$/', $header, $output)) continue;
        $parsedArray[$output[1]] = $output[2];
    }
    

    ps:未经测试。

    ——编辑——

    享受 ;)

    /**
     * Parse a set of HTTP headers
     *
     * @param array The php headers to be parsed
     * @param [string] The name of the header to be retrieved
     * @return A header value if a header is passed;
     *         An array with all the headers otherwise
     */
    function parseHeaders(array $headers, $header = null)
    {
        $output = array();
    
        if ('HTTP' === substr($headers[0], 0, 4)) {
            list(, $output['status'], $output['status_text']) = explode(' ', $headers[0]);
            unset($headers[0]);
        }
    
        foreach ($headers as $v) {
            $h = preg_split('/:\s*/', $v);
            $output[strtolower($h[0])] = $h[1];
        }
    
        if (null !== $header) {
            if (isset($output[strtolower($header)])) {
                return $output[strtolower($header)];
            }
    
            return;
        }
    
        return $output;
    }
    

    【讨论】:

    • 您的第一个正则表达式需要分隔符 (/):if (! preg_match('/^([^:]):(.*)$/', $header, $output)) continue;
    【解决方案3】:

    如果你有 pecl_http,请简答:http://php.net/manual/it/function.explode.php

    答案稍长:

    $header = "...";
    $parsed = array_map(function($x) { return array_map("trim", explode(":", $x, 2)); }, array_filter(array_map("trim", explode("\n", $header))));
    

    【讨论】:

      【解决方案4】:

      我最终得到了这个解决方案,它使用正则表达式查找标头中的所有键和值,并结合来自 https://stackoverflow.com/a/43004994/271351 的一些数组突变,将正则表达式匹配到关联数组中。这不是 100% 适合这里提出的问题,因为它接受一个字符串,但是加入一个字符串数组以获得单个字符串将作为此问题的前兆。我的案例必须处理原始标题,因此这个解决方案。

      preg_match_all('/^([^:\n]*): ?(.*)$/m', $header, $headers, PREG_SET_ORDER);
      $headers = array_merge(...array_map(function ($set) {
          return array($set[1] => trim($set[2]));
      }, $headers));
      

      这会产生一个标题的关联数组。如果标题的第一行作为输入包含在内(例如GET / HTTP/1.1),则会在输出中忽略它。

      【讨论】:

        【解决方案5】:

        最好的方法没有 http_parse_headers();

        function strHeaders2Hash($r) {
            $o = array();
            $r = substr($r, stripos($r, "\r\n"));
            $r = explode("\r\n", $r);
            foreach ($r as $h) {
                list($v, $val) = explode(": ", $h);
                if ($v == null) continue;
                $o[$v] = $val;
            }
            return $o;
        }
        

        【讨论】:

          【解决方案6】:

          看起来您正在使用get_headers 函数,如果是这样,请使用此函数的第二个参数,它替换输出数组键的数值并用字符串键替换它们,请查看manual for get_headers 函数。

          一个小例子是:

          <?php
              $output = get_headers('http://google.com', 1);
              print_r($output);
          

          会产生类似下面的数组:

          Array
          (
              [0] => HTTP/1.0 301 Moved Permanently
              [Location] => http://www.google.com/
              [Content-Type] => Array
                  (
                      [0] => text/html; charset=UTF-8
                      [1] => text/html; charset=ISO-8859-1
                  )
          
              [Date] => Array
                  (
                      [0] => Tue, 24 Sep 2013 11:57:10 GMT
                      [1] => Tue, 24 Sep 2013 11:57:11 GMT
                  )
          
              [Expires] => Array
                  (
                      [0] => Thu, 24 Oct 2013 11:57:10 GMT
                      [1] => -1
                  )
          
              [Cache-Control] => Array
                  (
                      [0] => public, max-age=2592000
                      [1] => private, max-age=0
                  )
          
              [Server] => Array
                  (
                      [0] => gws
                      [1] => gws
                  )
          
              [Content-Length] => 219
              [X-XSS-Protection] => Array
                  (
                      [0] => 1; mode=block
                      [1] => 1; mode=block
                  )
          
              [X-Frame-Options] => Array
                  (
                      [0] => SAMEORIGIN
                      [1] => SAMEORIGIN
                  )
          
              [Alternate-Protocol] => Array
                  (
                      [0] => 80:quic
                      [1] => 80:quic
                  )
          
              [1] => HTTP/1.0 200 OK
              [Set-Cookie] => Array
                  (
                      [0] => PREF=ID=58c8f706594fae17:FF=0:TM=1380023831:LM=1380023831:S=_ehOnNWODZqIarXn; expires=Thu, 24-Sep-2015 11:57:11 GMT; path=/; domain=.google.com
                      [1] => NID=67=L85IlJW5yG4l9Suyf1LwKMUTcVHyGv4u9tuuMlBH4pfT1syOJvspcgRJ9uTde1xLTDhI2QcOG_fuJY3sfhw49mayT5WdMHnGeMyhh3SgFTRYVF0RAtBXXmjyDFzMqPKu; expires=Wed, 26-Mar-2014 11:57:11 GMT; path=/; domain=.google.com; HttpOnly
                  )
          
              [P3P] => CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
          )
          

          【讨论】:

            【解决方案7】:

            如果您想更加安全,请使用 Symfony HTTP Foundation:

            composer require symfony/http-foundation

            use Symfony\Component\HttpFoundation\Request;
            
            $request = Request::createFromGlobals();
            
            // retrieves an HTTP request header, with normalized, lowercase keys
            $request->headers->get('host');
            $request->headers->get('content-type');
            

            如果您不想拥有这种依赖关系,下面是我整理的一个示例,用于确定 Cache-Control 标头是否具有 no-cache 值,例如:

            /**
            *  [
            *    0 => 'Cache-Control: no-cache, no-store, no-validate',
            *    1 => 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0',
            *  ]
            */
            $headers = headers_list();
            
            foreach ( $headers as $header_string ) {
                 /*
                 * Regex Samples:
                 * "Foo: Bar"
                 * "Foo-Bar: Baz-Bar:1"
                 *
                 * Matches:
                 * "Foo"
                 * "Foo-Bar"
                 */
                preg_match( '#^.+?(?=:)#', $header_string, $key );
            
                if ( empty( $key ) ) {
                    continue;
                }
            
                $key   = strtolower( reset( $key ) );
                $value = strtolower( ltrim( strtolower( $header_string ), $key . ':' ) );
            
                if ( $key == 'cache-control' ) {
                    if ( strpos( $value, 'no-cache' ) !== false || strpos( $value, 'no-store' ) !== false ) {
                        $nocache = true;
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2014-07-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多