implementing media type negotiation (based on language negotiation
logic) in cases both JSON and (X)HTML are being requested, resolving #68
This commit is contained in:
parent
9593ba7039
commit
3a92c940a9
|
@ -198,7 +198,8 @@ class i18n
|
||||||
if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
|
if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
|
||||||
{
|
{
|
||||||
$languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
|
$languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
|
||||||
foreach ($languageRanges as $languageRange) {
|
foreach ($languageRanges as $languageRange)
|
||||||
|
{
|
||||||
if (preg_match(
|
if (preg_match(
|
||||||
'/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/',
|
'/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/',
|
||||||
trim($languageRange), $match
|
trim($languageRange), $match
|
||||||
|
@ -325,7 +326,8 @@ class i18n
|
||||||
protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
|
protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
|
||||||
$matches = array();
|
$matches = array();
|
||||||
$any = false;
|
$any = false;
|
||||||
foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
|
foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues)
|
||||||
|
{
|
||||||
$acceptedQuality = floatval($acceptedQuality);
|
$acceptedQuality = floatval($acceptedQuality);
|
||||||
if ($acceptedQuality === 0.0) continue;
|
if ($acceptedQuality === 0.0) continue;
|
||||||
foreach ($availableLanguages as $availableValue)
|
foreach ($availableLanguages as $availableValue)
|
||||||
|
|
107
lib/request.php
107
lib/request.php
|
@ -17,6 +17,27 @@
|
||||||
*/
|
*/
|
||||||
class request
|
class request
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* MIME type for JSON
|
||||||
|
*
|
||||||
|
* @const string
|
||||||
|
*/
|
||||||
|
const MIME_JSON = 'application/json';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME type for HTML
|
||||||
|
*
|
||||||
|
* @const string
|
||||||
|
*/
|
||||||
|
const MIME_HTML = 'text/html';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME type for XHTML
|
||||||
|
*
|
||||||
|
* @const string
|
||||||
|
*/
|
||||||
|
const MIME_XHTML = 'application/xhtml+xml';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input stream to use for PUT parameter parsing.
|
* Input stream to use for PUT parameter parsing.
|
||||||
*
|
*
|
||||||
|
@ -66,15 +87,7 @@ class request
|
||||||
}
|
}
|
||||||
|
|
||||||
// decide if we are in JSON API or HTML context
|
// decide if we are in JSON API or HTML context
|
||||||
if (
|
$this->_isJsonApi = $this->_detectJsonRequest();
|
||||||
(array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
|
|
||||||
$_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
|
|
||||||
(array_key_exists('HTTP_ACCEPT', $_SERVER) &&
|
|
||||||
strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->_isJsonApi = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse parameters, depending on request type
|
// parse parameters, depending on request type
|
||||||
switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET')
|
switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET')
|
||||||
|
@ -168,4 +181,80 @@ class request
|
||||||
{
|
{
|
||||||
self::$_inputStream = $input;
|
self::$_inputStream = $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* detect the clients supported media type and decide if its a JSON API call or not
|
||||||
|
*
|
||||||
|
* Adapted from: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function _detectJsonRequest()
|
||||||
|
{
|
||||||
|
$hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER);
|
||||||
|
$acceptHeader = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : '';
|
||||||
|
|
||||||
|
// simple cases
|
||||||
|
if (
|
||||||
|
(array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
|
||||||
|
$_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
|
||||||
|
($hasAcceptHeader &&
|
||||||
|
strpos($acceptHeader, self::MIME_JSON) !== false &&
|
||||||
|
strpos($acceptHeader, self::MIME_HTML) === false &&
|
||||||
|
strpos($acceptHeader, self::MIME_XHTML) === false)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advanced case: media type negotiation
|
||||||
|
$mediaTypes = array();
|
||||||
|
if ($hasAcceptHeader)
|
||||||
|
{
|
||||||
|
$mediaTypeRanges = explode(',', trim($acceptHeader));
|
||||||
|
foreach ($mediaTypeRanges as $mediaTypeRange)
|
||||||
|
{
|
||||||
|
if (preg_match(
|
||||||
|
'#(\*/\*|[a-z\-]+/[a-z\-+*]+(?:\s*;\s*[^q]\S*)*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?#',
|
||||||
|
trim($mediaTypeRange), $match
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (!isset($match[2]))
|
||||||
|
{
|
||||||
|
$match[2] = '1.0';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$match[2] = (string) floatval($match[2]);
|
||||||
|
}
|
||||||
|
if (!isset($mediaTypes[$match[2]]))
|
||||||
|
{
|
||||||
|
$mediaTypes[$match[2]] = array();
|
||||||
|
}
|
||||||
|
$mediaTypes[$match[2]][] = strtolower($match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
krsort($mediaTypes);
|
||||||
|
foreach ($mediaTypes as $acceptedQuality => $acceptedValues)
|
||||||
|
{
|
||||||
|
if ($acceptedQuality === 0.0) continue;
|
||||||
|
foreach ($acceptedValues as $acceptedValue)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
strpos($acceptedValue, self::MIME_HTML) === 0 ||
|
||||||
|
strpos($acceptedValue, self::MIME_XHTML) === 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
elseif (strpos($acceptedValue, self::MIME_JSON) === 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -151,7 +151,7 @@ class zerobin
|
||||||
// output JSON or HTML
|
// output JSON or HTML
|
||||||
if ($this->_request->isJsonApiCall())
|
if ($this->_request->isJsonApiCall())
|
||||||
{
|
{
|
||||||
header('Content-type: application/json');
|
header('Content-type: ' . request::MIME_JSON);
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
|
||||||
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
|
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
|
||||||
|
|
|
@ -104,4 +104,52 @@ class requestTest extends PHPUnit_Framework_TestCase
|
||||||
$this->assertEquals('foo', $request->getParam('pasteid'));
|
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||||
$this->assertEquals('bar', $request->getParam('deletetoken'));
|
$this->assertEquals('bar', $request->getParam('deletetoken'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testReadWithNegotiation()
|
||||||
|
{
|
||||||
|
$this->reset();
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['HTTP_ACCEPT'] = 'text/html,text/html; charset=UTF-8,application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8, text/csv,application/json';
|
||||||
|
$_SERVER['QUERY_STRING'] = 'foo';
|
||||||
|
$request = new request;
|
||||||
|
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||||
|
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||||
|
$this->assertEquals('read', $request->getOperation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadWithXhtmlNegotiation()
|
||||||
|
{
|
||||||
|
$this->reset();
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['HTTP_ACCEPT'] = 'application/xhtml+xml,text/html,text/html; charset=UTF-8, application/xml;q=0.9,*/*;q=0.8, text/csv,application/json';
|
||||||
|
$_SERVER['QUERY_STRING'] = 'foo';
|
||||||
|
$request = new request;
|
||||||
|
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||||
|
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||||
|
$this->assertEquals('read', $request->getOperation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testApiReadWithNegotiation()
|
||||||
|
{
|
||||||
|
$this->reset();
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['HTTP_ACCEPT'] = 'text/plain,text/csv, application/xml;q=0.9, application/json, text/html,text/html; charset=UTF-8,application/xhtml+xml, */*;q=0.8';
|
||||||
|
$_SERVER['QUERY_STRING'] = 'foo';
|
||||||
|
$request = new request;
|
||||||
|
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
|
||||||
|
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||||
|
$this->assertEquals('read', $request->getOperation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadWithFailedNegotiation()
|
||||||
|
{
|
||||||
|
$this->reset();
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['HTTP_ACCEPT'] = 'text/plain,text/csv, application/xml;q=0.9, */*;q=0.8';
|
||||||
|
$_SERVER['QUERY_STRING'] = 'foo';
|
||||||
|
$request = new request;
|
||||||
|
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||||
|
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||||
|
$this->assertEquals('read', $request->getOperation());
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue