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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1049401/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 00:46:35  来源:igfitidea点击:

How to select content type from HTTP Accept header in PHP

phphttp-headers

提问by l0b0

I'm trying to build a standard compliant website framework which serves XHTML 1.1 as application/xhtml+xml or HTML 4.01 as text/html depending on the browser support. Currently it just looks for "application/xhtml+xml" anywhere in the accept header, and uses that if it exists, but that's not flexible - text/html might have a higher score. Also, it will become more complex when other formats (WAP, SVG, XForms etc.) are added. So, does anyone know of a tried and tested piece of PHP code to select, from a string array given by the server, either the one best supported by the client or an ordered list based on the client score?

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

采纳答案by VolkerK

You can leverage apache's mod_negotiation module. This way you can use the full range of negotiation capabilities the module offers, including your own preferencesfor the content type (e,g, "I really want to deliver application/xhtml+xml, unless the client very much prefers something else"). basic solution:

您可以利用apache 的 mod_negotiation 模块。通过这种方式,您可以使用模块提供的全部协商功能,包括您自己对内容类型的偏好(例如,“我真的想交付 application/xhtml+xml,除非客户非常喜欢其他东西”) . 基本解决方案:

  • create a .htaccess file with
    AddHandler type-map .var
    as contents
  • create a file foo.var with
    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
    as contents
  • create a file foo.php with
    <?php
    echo 'selected type: ', substr($_SERVER['PATH_INFO'], 1);
    as contents.
  • request http://localhost/whatever/foo.var
  • 创建一个 .htaccess 文件
    AddHandler type-map .var
    作为内容
  • 创建一个文件 foo.var
    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.php
    <?php
    echo 'selected type: ', substr($_SERVER['PATH_INFO'], 1);
    作为内容。
  • 请求http://localhost/whatever/foo.var

For this to work you need mod_negotiation enabled, the appropriate AllowOverride privileges for AddHandler and AcceptPathInfonot being disabled for $_SERVER['PATH_INFO'].
With my Firefox sending "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8" and the example .var map the result is "selected type: xhtml".
You can use other "tweaks" to get rid of PATH_INFO or the need to request foo.var, but the basic concept is: let mod_negotiation redirect the request to your php script in a way that the script can "read" the selected content-type.

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

So, does anyone know of a tried and tested piece of PHP code to select
那么,有没有人知道一段经过试验和测试的 PHP 代码可供选择
这不是一个纯粹的 php 解决方案,但我想说 mod_negotiation 已经过尝试和测试;-)

回答by Maciej ?ebkowski

Little snippet from my library:

我图书馆的小片段:

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

example usage:

用法示例:

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

回答by VolkerK

Pear::HTTP 1.4.1 has a method string negotiateMimeType( array $supported, string $default)

Pear::HTTP 1.4.1 有一个方法stringnegotiateMimeType(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";
}

prints

印刷

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发送Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8接受: 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选择了你喜欢的那个,无论你作为 $supported 传递什么,它都会返回 text/html。这对您来说可能足够也可能不够。请参阅我的其他答案。

回答by William Durand

Just for the record, Negotiationis a pure PHP implementation for dealing with content negotiation.

只是为了记录,Negotiation是一个用于处理内容协商的纯 PHP 实现。

回答by Sergey Nevmerzhitsky

Merged @maciej-?ebkowski and @chacham15 solutions with my issues fixes and improvements. If you pass $desiredTypes = 'text/*'and Acceptcontains text/html;q=1then text/htmlwill be returned.

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

回答by cweiske

PEAR's HTTP2 librarysupports parsing all types of Acceptheaders. It's installable via composerand PEAR.

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

Examples can be found at the documentationor my blog post.

可以在文档我的博客文章中找到示例。

回答by Mostafa

Client may accept a list of mime-types in the response. In the other hand the order of the response is very important for client side. PHP Pear HTTP2is the best to deal with language, charset, and mimetypes.

客户端可以在响应中接受一个 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";
}

Here is a good tutorial: https://cweiske.de/tagebuch/php-http-negotiation.htm

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