【问题标题】:How to select content type from HTTP Accept header in PHP如何从 PHP 中的 HTTP Accept 标头中选择内容类型
【发布时间】:2009-06-26 14:19:55
【问题描述】:

我正在尝试构建一个符合标准的网站框架,该框架将 XHTML 1.1 作为 application/xhtml+xml 或 HTML 4.01 作为 text/html 服务,具体取决于浏览器的支持。目前它只是在接受标头中的任何位置查找“application/xhtml+xml”,并在存在时使用它,但这并不灵活 - text/html 可能有更高的分数。此外,当添加其他格式(WAP、SVG、XForms 等)时,它会变得更加复杂。那么,有没有人知道一段经过试验和测试的 PHP 代码,可以从服务器提供的字符串数组中选择客户端最支持的数组,或者根据客户端分数排序的列表?

【问题讨论】:

  • 虽然尝试符合标准并“正确地做事”是件好事,但我认为值得花一点时间考虑一下您是否真的能从这一切中获得任何好处。例如。当 text/html 工作正常时提供 application/xhtml+xml 没有太多理由等等。

标签: php http-headers


【解决方案1】:

我图书馆里的小sn-p:

function getBestSupportedMimeType($mimeTypes = null) {
    // Values will be stored in this array
    $AcceptTypes = Array ();

    // Accept header is case insensitive, and whitespace isn’t important
    $accept = strtolower(str_replace(' ', '', $_SERVER['HTTP_ACCEPT']));
    // divide it into parts in the place of a ","
    $accept = explode(',', $accept);
    foreach ($accept as $a) {
        // the default quality is 1.
        $q = 1;
        // check if there is a different quality
        if (strpos($a, ';q=')) {
            // divide "mime/type;q=X" into two parts: "mime/type" i "X"
            list($a, $q) = explode(';q=', $a);
        }
        // mime-type $a is accepted with the quality $q
        // WARNING: $q == 0 means, that mime-type isn’t supported!
        $AcceptTypes[$a] = $q;
    }
    arsort($AcceptTypes);

    // if no parameter was passed, just return parsed data
    if (!$mimeTypes) return $AcceptTypes;

    $mimeTypes = array_map('strtolower', (array)$mimeTypes);

    // let’s check our supported types:
    foreach ($AcceptTypes as $mime => $q) {
       if ($q && in_array($mime, $mimeTypes)) return $mime;
    }
    // no mime-type found
    return null;
}

示例用法:

$mime = getBestSupportedMimeType(Array ('application/xhtml+xml', 'text/html'));

【讨论】:

  • 只是一个小小的改进:将函数原型更改为function getBestSupportedMimeType($mimeTypes = null, $acceptedTypes = FALSE){ if ($acceptedTypes === FALSE){ $acceptedTypes = $_SERVER['HTTP_ACCEPT']; } ... 。如果程序需要做一些更自定义的事情,基本上允许自定义接受类型。
  • 很遗憾,这不适用于同样是有效条目的“/”或“application/*”(w3.org/Protocols/rfc2616/rfc2616-sec14.html
【解决方案2】:

您可以利用apache's mod_negotiation module。这样您就可以使用该模块提供的所有协商功能,包括您自己的偏好内容类型(例如,“我真的想交付应用程序/xhtml+xml,除非客户端非常喜欢别的东西”)。 基本解决方案:

  • 使用
    AddHandler type-map .var
    创建一个 .htaccess 文件作为内容
  • 使用
    URI: foo
    创建文件 foo.var URI: foo.php/html 内容类型:text/html; qs=0.7
    URI: foo.php/xhtml 内容类型:application/xhtml+xml; qs=0.8
    作为内容
  • 作为内容。
  • 请求http://localhost/whatever/foo.var

为此,您需要启用 mod_negotiation,并没有为 $_SERVER['PATH_INFO'] 禁用 AddHandler 和 AcceptPathInfo 的适当 AllowOverride 权限。
使用我的 Firefox 发送“接受:text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8”并且示例 .var 映射的结果是“选定类型: xhtml"。
您可以使用其他“调整”来摆脱 PATH_INFO 或请求 foo.var 的需要,但基本概念是:让 mod_negotiation 以脚本可以的方式将请求重定向到您的 php 脚本“读取”选定的内容类型。

那么,有谁知道可供选择的久经考验的 PHP 代码
这不是一个纯粹的 php 解决方案,但我想说 mod_negotiation 已经过尝试和测试 ;-)

【讨论】:

  • 接受 - 甚至比 PHP 更好。
【解决方案3】:

Pear::HTTP 1.4.1 有一个方法string negotiateMimeType( array $supported, string $default)

<?php
require 'HTTP.php';

foreach(
  array(
    'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
    'text/*;q=0.3, text/html;q=0.8, application/xhtml+xml;q=0.7, */*;q=0.2',
    'text/*;q=0.3, text/html;q=0.7, */*;q=0.8',
    'text/*, application/xhtml+xml',
    'text/html, application/xhtml+xml'
  ) as $testheader) {  
  $_SERVER['HTTP_ACCEPT'] = $testheader;

  $http = new HTTP;
  echo $testheader, ' -> ',
    $http->negotiateMimeType( array('application/xhtml+xml', 'text/html'), 'application/xhtml+xml'),
    "\n";
}

打印

text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, /;q= 0.5 -> 应用程序/xhtml+xml
text/*;q=0.3, text/html;q=0.8, application/xhtml+xml;q=0.7, */*;q=0.2 -> text/html
text/*;q=0.3, text/html;q=0.7, */*;q=0.8 -> application/xhtml+xml
文本/*,应用程序/xhtml+xml -> 应用程序/xhtml+xml
text/html, application/xhtml+xml -> text/html

编辑:毕竟这可能不是那么好......
我的 Firefox 发送 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
text/html 和 application/xhtml+xml 有 q=1.0 但 PEAR::HTTP (afaik) 不会让 you 选择你喜欢的那个,无论你传递什么,它都会返回 text/html $支持。这对您来说可能不够,也可能不够。请参阅我的其他答案。

【讨论】:

【解决方案4】:

仅作记录,Negotiation 是用于处理内容协商的纯 PHP 实现。

【讨论】:

    【解决方案5】:

    将@maciej-Łebkowski 和@chacham15 解决方案与我的问题修复和改进合并。如果你通过$desiredTypes = 'text/*' 并且Accept 包含text/html;q=1 那么text/html 将被返回。

    /**
     * Parse, sort and select best Content-type, supported by a user browser.
     *
     * @param string|string[] $desiredTypes The filter of desired types. If &null then the all supported types will returned.
     * @param string $acceptRules Supported types in the HTTP Accept header format. $_SERVER['HTTP_ACCEPT'] by default.
     * @return string|string[]|null Matched by $desiredTypes type or all accepted types.
     * @link Inspired by http://stackoverflow.com/a/1087498/3155344
     */
    function resolveContentNegotiation($desiredTypes = null, $acceptRules = null)
    {
        if (!$acceptRules) {
            $acceptRules = @$_SERVER['HTTP_ACCEPT'];
        }
        // Accept header is case insensitive, and whitespace isn't important.
        $acceptRules = strtolower(str_replace(' ', '', $acceptRules));
    
        $sortedAcceptTypes = array();
        foreach (explode(',', $acceptRules) as $acceptRule) {
            $q = 1; // the default accept quality (rating).
            // Check if there is a different quality.
            if (strpos($acceptRule, ';q=') !== false) {
                // Divide "type;q=X" into two parts: "type" and "X"
                list($acceptRule, $q) = explode(';q=', $acceptRule, 2);
            }
            $sortedAcceptTypes[$acceptRule] = $q;
        }
        // WARNING: zero quality is means, that type isn't supported! Thus remove them.
        $sortedAcceptTypes = array_filter($sortedAcceptTypes);
        arsort($sortedAcceptTypes, SORT_NUMERIC);
    
        // If no parameter was passed, just return parsed data.
        if (!$desiredTypes) {
            return $sortedAcceptTypes;
        }
    
        $desiredTypes = array_map('strtolower', (array) $desiredTypes);
    
        // Let's check our supported types.
        foreach (array_keys($sortedAcceptTypes) as $type) {
            foreach ($desiredTypes as $desired) {
                if (fnmatch($desired, $type)) {
                    return $type;
                }
            }
        }
    
        // No matched type.
        return null;
    }
    

    【讨论】:

    • 您不能从客户端 Accept 标头中过滤掉 q=0。这意味着客户端将不接受该类型,例如Accept-Language: en, en-US;q=0 表示只要不是美式英语,我都会接受。
    【解决方案6】:

    PEAR's HTTP2 library 支持解析所有类型的Accept 标头。它可以通过composer 和 PEAR 安装。

    可以在documentationmy blog post 找到示例。

    【讨论】:

      【解决方案7】:

      客户端可以在响应中接受 MIME 类型列表。另一方面,响应的顺序对于客户端非常重要。 PHP Pear HTTP2 是处理语言、字符集和 mimetypes 的最佳选择。

      $http = new HTTP2();
      $supportedTypes = array(
          'text/html',
          'application/json'
      );
      
      $type = $http->negotiateMimeType($supportedTypes, false);
      if ($type === false) {
          header('HTTP/1.1 406 Not Acceptable');
          echo "You don't want any of the content types I have to offer\n";
      } else {
          echo 'I\'d give you data of type: ' . $type . "\n";
      }
      

      这里有一个很好的教程:https://cweiske.de/tagebuch/php-http-negotiation.htm

      【讨论】:

        猜你喜欢
        • 2016-04-17
        • 2017-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-30
        • 2013-10-01
        • 1970-01-01
        • 2010-12-31
        相关资源
        最近更新 更多