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

发布于 2024-07-25 08:40:00 字数 290 浏览 2 评论 0原文

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

轮廓§ 2024-08-01 08:40:00

我的库中的小片段:

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

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'));
微凉 2024-08-01 08:40:00

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

  • 创建一个 .htaccess 文件,其中
    AddHandler type-map .var

    作为内容

  • 创建一个文件 foo.var,其中
    URI: foo
    URI:foo.php/html 内容类型:text/html; qs=0.7
    URI:foo.php/xhtml 内容类型:application/xhtml+xml; qs=0.8

    as 内容

  • 创建一个带有
    作为内容。
  • 请求 http://localhost/whatever/foo.var

为此,您需要启用 mod_negotiation,相应的未为 $_SERVER 禁用 AddHandler 和 AcceptPathInfo 的 AllowOverride 权限[ 'PATH_INFO']。
当我的 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 脚本“读取”所选内容类型。

那么,有谁知道一段经过考验的 PHP 代码可供选择

It's not a pure php solution but I'd say mod_negotiation has been tried and tested ;-)

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 preferences for the content type (e,g, "I really want to deliver application/xhtml+xml, unless the client very much prefers something else").
basic solution:

  • 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

For this to work you need mod_negotiation enabled, the appropriate AllowOverride privileges for AddHandler and AcceptPathInfo not 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.

So, does anyone know of a tried and tested piece of PHP code to select

It's not a pure php solution but I'd say mod_negotiation has been tried and tested ;-)

赴月观长安 2024-08-01 08:40:00

Pear::HTTP 1.4.1 有一个方法 string NegotiMimeType( 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

edit: this might not be so good after all...
My firefox sends Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
text/html and application/xhtml+xml have q=1.0 but PEAR::HTTP (afaik) doesn't let you chose which one you prefer, it returns text/html no matter what you pass as $supported. This may or may not be sufficient for you. see my other answer(s).

Pear::HTTP 1.4.1 has a method 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";
}

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

edit: this might not be so good after all...
My firefox sends Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
text/html and application/xhtml+xml have q=1.0 but PEAR::HTTP (afaik) doesn't let you chose which one you prefer, it returns text/html no matter what you pass as $supported. This may or may not be sufficient for you. see my other answer(s).

南街九尾狐 2024-08-01 08:40:00

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

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

稚然 2024-08-01 08:40:00

将 @maciej-Łebkowski 和 @chacham15 解决方案与我的问题修复和改进合并。 如果您传递 $desiredTypes = 'text/*'Accept 包含 text/html;q=1text/html< /code> 将被返回。

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

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

/**
 * 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;
}
御守 2024-08-01 08:40:00

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

示例可以在文档我的博客文章

PEAR's HTTP2 library supports parsing all types of Accept headers. It's installable via composer and PEAR.

Examples can be found at the documentation or my blog post.

捎一片雪花 2024-08-01 08:40:00

客户端可以接受响应中的 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-谈判.htm

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 HTTP2 is the best to deal with language, charset, and 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文