Просмотр исходного кода

implementing media type negotiation (based on language negotiation
logic) in cases both JSON and (X)HTML are being requested, resolving #68

El RIDO 10 лет назад
Родитель
Сommit
3a92c940a9
4 измененных файлов с 151 добавлено и 12 удалено
  1. 4 2
      lib/i18n.php
  2. 98 9
      lib/request.php
  3. 1 1
      lib/zerobin.php
  4. 48 0
      tst/request.php

+ 4 - 2
lib/i18n.php

@@ -198,7 +198,8 @@ class i18n
         if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
         {
             $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
-            foreach ($languageRanges as $languageRange) {
+            foreach ($languageRanges as $languageRange)
+            {
                 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})))?/',
                     trim($languageRange), $match
@@ -325,7 +326,8 @@ class i18n
     protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
         $matches = array();
         $any = false;
-        foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
+        foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues)
+        {
             $acceptedQuality = floatval($acceptedQuality);
             if ($acceptedQuality === 0.0) continue;
             foreach ($availableLanguages as $availableValue)

+ 98 - 9
lib/request.php

@@ -17,6 +17,27 @@
 */
 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.
      *
@@ -66,15 +87,7 @@ class request
         }
 
         // decide if we are in JSON API or HTML context
-        if (
-            (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;
-        }
+        $this->_isJsonApi = $this->_detectJsonRequest();
 
         // parse parameters, depending on request type
         switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET')
@@ -168,4 +181,80 @@ class request
     {
         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;
+    }
 }

+ 1 - 1
lib/zerobin.php

@@ -151,7 +151,7 @@ class zerobin
         // output JSON or HTML
         if ($this->_request->isJsonApiCall())
         {
-            header('Content-type: application/json');
+            header('Content-type: ' . request::MIME_JSON);
             header('Access-Control-Allow-Origin: *');
             header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
             header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');

+ 48 - 0
tst/request.php

@@ -104,4 +104,52 @@ class requestTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('foo', $request->getParam('pasteid'));
         $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());
+    }
 }