Commit ad06b80e by Qiang Xue

Enhanced parsing the accept header.

parent 452c486f
...@@ -885,9 +885,23 @@ class Request extends \yii\base\Request ...@@ -885,9 +885,23 @@ class Request extends \yii\base\Request
/** /**
* Returns the content types acceptable by the end user. * Returns the content types acceptable by the end user.
* This is determined by the `Accept` HTTP header. * This is determined by the `Accept` HTTP header. For example,
* @return array the content types ordered by the preference level. The first element *
* represents the most preferred content type. * ```php
* $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
* $types = $request->getAcceptableContentTypes();
* print_r($types);
* // displays:
* // [
* // 'application/json' => ['q' => 1, 'version' => '1.0'],
* // 'application/xml' => ['q' => 1, 'version' => '2.0'],
* // 'text/plain' => ['q' => 0.5],
* // ]
* ```
*
* @return array the content types ordered by the quality score. Types with the highest scores
* will be returned first. The array keys are the content types, while the array values
* are the corresponding quality score and other parameters as given in the header.
*/ */
public function getAcceptableContentTypes() public function getAcceptableContentTypes()
{ {
...@@ -902,8 +916,12 @@ class Request extends \yii\base\Request ...@@ -902,8 +916,12 @@ class Request extends \yii\base\Request
} }
/** /**
* Sets the acceptable content types.
* Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
* @param array $value the content types that are acceptable by the end user. They should * @param array $value the content types that are acceptable by the end user. They should
* be ordered by the preference level. * be ordered by the preference level.
* @see getAcceptableContentTypes()
* @see parseAcceptHeader()
*/ */
public function setAcceptableContentTypes($value) public function setAcceptableContentTypes($value)
{ {
...@@ -937,7 +955,7 @@ class Request extends \yii\base\Request ...@@ -937,7 +955,7 @@ class Request extends \yii\base\Request
{ {
if ($this->_languages === null) { if ($this->_languages === null) {
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$this->_languages = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']); $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
} else { } else {
$this->_languages = []; $this->_languages = [];
} }
...@@ -956,45 +974,86 @@ class Request extends \yii\base\Request ...@@ -956,45 +974,86 @@ class Request extends \yii\base\Request
/** /**
* Parses the given `Accept` (or `Accept-Language`) header. * Parses the given `Accept` (or `Accept-Language`) header.
* This method will return the acceptable values ordered by their preference level. *
* This method will return the acceptable values with their quality scores and the corresponding parameters
* as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
* while the array values consisting of the corresponding quality scores and parameters. The acceptable
* values with the highest quality scores will be returned first. For example,
*
* ```php
* $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
* $accepts = $request->parseAcceptHeader($header);
* print_r($accepts);
* // displays:
* // [
* // 'application/json' => ['q' => 1, 'version' => '1.0'],
* // 'application/xml' => ['q' => 1, 'version' => '2.0'],
* // 'text/plain' => ['q' => 0.5],
* // ]
* ```
*
* @param string $header the header to be parsed * @param string $header the header to be parsed
* @return array the accept values ordered by their preference level. * @return array the acceptable values ordered by their quality score. The values with the highest scores
* will be returned first.
*/ */
protected function parseAcceptHeader($header) public function parseAcceptHeader($header)
{ {
$accepts = []; $accepts = [];
$n = preg_match_all('/\s*([\w\/\-\*]+)\s*(?:;\s*q\s*=\s*([\d\.]+))?[^,]*/', $header, $matches, PREG_SET_ORDER); foreach (explode(',', $header) as $i => $part) {
for ($i = 0; $i < $n; ++$i) { $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
if (!empty($matches[$i][1])) { if (empty($params)) {
$accepts[] = [$matches[$i][1], isset($matches[$i][2]) ? (float)$matches[$i][2] : 1, $i]; continue;
} }
$values = [
'q' => [$i, array_shift($params), 1],
];
foreach ($params as $param) {
if (strpos($param, '=') !== false) {
list ($key, $value) = explode('=', $param, 2);
if ($key === 'q') {
$values['q'][2] = (double)$value;
} else {
$values[$key] = $value;
}
} else {
$values[] = $param;
}
}
$accepts[] = $values;
} }
usort($accepts, function ($a, $b) { usort($accepts, function ($a, $b) {
if ($a[1] > $b[1]) { $a = $a['q']; // index, name, q
$b = $b['q'];
if ($a[2] > $b[2]) {
return -1; return -1;
} elseif ($a[1] < $b[1]) { } elseif ($a[2] < $b[2]) {
return 1; return 1;
} elseif ($a[0] === $b[0]) { } elseif ($a[1] === $b[1]) {
return $a[2] > $b[2] ? 1 : -1; return $a[0] > $b[0] ? 1 : -1;
} elseif ($a[0] === '*/*') { } elseif ($a[1] === '*/*') {
return 1; return 1;
} elseif ($b[0] === '*/*') { } elseif ($b[1] === '*/*') {
return -1; return -1;
} else { } else {
$wa = $a[0][strlen($a[0]) - 1] === '*'; $wa = $a[1][strlen($a[1]) - 1] === '*';
$wb = $b[0][strlen($b[0]) - 1] === '*'; $wb = $b[1][strlen($b[1]) - 1] === '*';
if ($wa xor $wb) { if ($wa xor $wb) {
return $wa ? 1 : -1; return $wa ? 1 : -1;
} else { } else {
return $a[2] > $b[2] ? 1 : -1; return $a[0] > $b[0] ? 1 : -1;
} }
} }
}); });
$result = []; $result = [];
foreach ($accepts as $accept) { foreach ($accepts as $accept) {
$result[] = $accept[0]; $name = $accept['q'][1];
$accept['q'] = $accept['q'][2];
$result[$name] = $accept;
} }
return array_unique($result);
return $result;
} }
/** /**
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\web;
use yii\web\Request;
use yiiunit\TestCase;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RequestTest extends TestCase
{
public function testParseAcceptHeader()
{
$request = new Request;
$this->assertEquals([], $request->parseAcceptHeader(' '));
$this->assertEquals([
'audio/basic' => ['q' => 1],
'audio/*' => ['q' => 0.2],
], $request->parseAcceptHeader('audio/*; q=0.2, audio/basic'));
$this->assertEquals([
'application/json' => ['q' => 1, 'version' => '1.0'],
'application/xml' => ['q' => 1, 'version' => '2.0', 'x'],
'text/x-c' => ['q' => 1],
'text/x-dvi' => ['q' => 0.8],
'text/plain' => ['q' => 0.5],
], $request->parseAcceptHeader('text/plain; q=0.5,
application/json; version=1.0,
application/xml; version=2.0; x,
text/x-dvi; q=0.8, text/x-c'));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment