如何从PHP中的HTTP Accept标头中选择内容类型

时间:2009-06-26 14:19:56

标签: php http-headers

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

8 个答案:

答案 0 :(得分:20)

我图书馆的小片段:

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'));

答案 1 :(得分:11)

您可以利用apache's mod_negotiation module。这样您就可以使用模块提供的全方位协商功能,包括您自己的首选项的内容类型(例如,“我真的想要提供application / xhtml + xml,除非客户端非常喜欢别的东西“)。 基本解决方案:

  • 使用
    AddHandler type-map .var
    作为内容创建.htaccess文件
  • 使用
    URI: foo
    URI: foo.php/html Content-type: text/html; qs=0.7
    URI: foo.php/xhtml Content-type: application/xhtml+xml; qs=0.8
    作为内容创建文件foo.var
  • 创建一个文件foo.php,内容为
    <?php
    echo 'selected type: ', substr($_SERVER['PATH_INFO'], 1);
  • 请求http://localhost/whatever/foo.var

要使其正常工作,您需要启用mod_negotiation,为$ _SERVER ['PATH_INFO']禁用AddHandler和AcceptPathInfo的相应AllowOverride权限。
我的Firefox发送“Accept: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已经过试用和测试;-)

答案 2 :(得分:10)

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 -> application/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
text/*, application/xhtml+xml -> application/xhtml+xml
text/html, application/xhtml+xml -> text/html

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

答案 3 :(得分:9)

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

答案 4 :(得分:1)

将@ 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;
}

答案 5 :(得分:1)

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

可以在documentationmy blog post找到示例。

答案 6 :(得分:0)

http://www.dev-archive.net/articles/xhtml.html#content-negotiation是用Perl编写的,但它清楚地布局,只包含一些if / else和regex。将它移植到PHP应该是微不足道的。

答案 7 :(得分:0)

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

$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