How to select content type from HTTP Accept header in PHP

后端 未结 7 1027
[愿得一人]
[愿得一人] 2020-12-16 13:10

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. Curre

相关标签:
7条回答
  • 2020-12-16 13:20

    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;
    }
    
    0 讨论(0)
  • 2020-12-16 13:21

    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).

    0 讨论(0)
  • 2020-12-16 13:23

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

    0 讨论(0)
  • 2020-12-16 13:27

    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

    0 讨论(0)
  • 2020-12-16 13:28

    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.

    0 讨论(0)
  • 2020-12-16 13:31

    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'));
    
    0 讨论(0)
提交回复
热议问题