Author: chabotc
Date: Wed Jul  2 11:42:58 2008
New Revision: 673461

URL: http://svn.apache.org/viewvc?rev=673461&view=rev
Log:
New http caching header functionality for the proxy service. This is still
somewhat experimental, and while it has been tried in all the major browsers
it hasn't been tested with every gadget yet.

What it does is add propper http header handling for e-tags and 
if-modified-since
headers, where it:

- always ignores the remote party's etag and generate our own (md5(content))
- use the remote party's last-modified if it's available, otherwise
  use the time that the request was last made.
- If the e-tag matches, 304 is returned and if-modified-since is not evaluated
  else it checks the if-modified-since

The idea is that most modern browsers support e-tags just fine, so that's our
prefered mechanism for returning 304 Not Modified to requests. However there
are proxy servers (and old browsers) that do not support e-tags, however they
do support last-modified / if-modified-since, so that's our fallback mechanism.

To further try to improve the cachability of the proxied requests, the code
also sets a Cache-Control:public, maxAge={refreshInterval} header, and a
Expires : (time + refreshInterval) header, as to save the round trip on things
that should be cachable.

The refreshInterval used is either the one specified in the request 
(&refresh=<seconds>)
and if non is set, the global caching time policy is used. (One day in the 
default
config).

Any and all feedback is very welcome!


Modified:
    incubator/shindig/trunk/php/src/common/HttpServlet.php
    incubator/shindig/trunk/php/src/common/RemoteContentRequest.php
    incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
    incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php

Modified: incubator/shindig/trunk/php/src/common/HttpServlet.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/HttpServlet.php?rev=673461&r1=673460&r2=673461&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/common/HttpServlet.php (original)
+++ incubator/shindig/trunk/php/src/common/HttpServlet.php Wed Jul  2 11:42:58 
2008
@@ -75,7 +75,7 @@
                                        if 
(isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == 
$etag) {
                                                header("ETag: \"$etag\"");
                                                if ($this->lastModified) {
-                                                       header('Last-Modified: 
' . gmdate('D, d M Y H:i:s', $this->lastModified), true);
+                                                       header('Last-Modified: 
' . gmdate('D, d M Y H:i:s', $this->lastModified) . ' GMT', true);
                                                }
                                                header("HTTP/1.1 304 Not 
Modified", true);
                                                header('Content-Length: 0', 
true);
@@ -88,14 +88,14 @@
                                        if 
(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $this->lastModified && ! 
isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
                                                $if_modified_since = 
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
                                                if ($this->lastModified <= 
$if_modified_since) {
-                                                       header('Last-Modified: 
' . gmdate('D, d M Y H:i:s', $this->lastModified), true);
+                                                       header('Last-Modified: 
' . gmdate('D, d M Y H:i:s', $this->lastModified) . ' GMT', true);
                                                        header("HTTP/1.1 304 
Not Modified", true);
                                                        header('Content-Length: 
0', true);
                                                        ob_end_clean();
                                                        die();
                                                }
                                        }
-                                       header('Last-Modified: ' . gmdate('D, d 
M Y H:i:s', ($this->lastModified ? $this->lastModified : time())), true);
+                                       header('Last-Modified: ' . gmdate('D, d 
M Y H:i:s', ($this->lastModified ? $this->lastModified : time())) . ' GMT', 
true);
                                }
                        }
                }

Modified: incubator/shindig/trunk/php/src/common/RemoteContentRequest.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/RemoteContentRequest.php?rev=673461&r1=673460&r2=673461&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/common/RemoteContentRequest.php (original)
+++ incubator/shindig/trunk/php/src/common/RemoteContentRequest.php Wed Jul  2 
11:42:58 2008
@@ -1,5 +1,4 @@
 <?php
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -32,6 +31,7 @@
        private $httpCode = false;
        private $contentType = null;
        private $options;
+       private $created;
        public $handle = false;
        public static $DEFAULT_CONTENT_TYPE = 
"application/x-www-form-urlencoded; charset=utf-8";
 
@@ -40,6 +40,7 @@
                $this->uri = $uri;
                $this->headers = $headers;
                $this->postBody = $postBody;
+               $this->created = time();
        }
 
        public function createRemoteContentRequest($method, $uri, $headers, 
$postBody, $options)
@@ -222,7 +223,7 @@
        {
                return $this->responseContent;
        }
-       
+
        public function getResponseHeaders()
        {
                return $this->responseHeaders;
@@ -307,26 +308,31 @@
        {
                $headers = explode("\n", $this->headers);
                foreach ($headers as $header) {
-                       $key = explode(":", $header);
+                       $key = explode(":", $header, 2);
                        if ($key[0] == $headerName)
-                               return $key[1];
+                               return trim($key[1]);
                }
                return null;
        }
-       
+
        //FIXME: Find a better way to do this
        // The headers can be an array of elements.
        public function getResponseHeader($headerName)
        {
                $headers = explode("\n", $this->responseHeaders);
                foreach ($headers as $header) {
-                       $key = explode(":", $header);
+                       $key = explode(":", $header, 2);
                        if ($key[0] == $headerName) {
                                return trim($key[1]);
                        }
                }
                return null;
        }
+       
+       public function getCreated()
+       {
+               return $this->created;
+       }
 
        public function setPostBody($postBody)
        {
@@ -337,7 +343,6 @@
        {
                $this->uri = $uri;
        }
-
 }
 
 /**

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetContext.php?rev=673461&r1=673460&r2=673461&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetContext.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetContext.php Wed Jul  2 
11:42:58 2008
@@ -61,7 +61,7 @@
 
        private function getRefreshIntervalParam()
        {
-               $this->refreshInterval = isset($_GET['refresh']) ? 
$_GET['refresh'] : Config::get('cache_time');
+               return isset($_GET['refresh']) ? $_GET['refresh'] : 
Config::get('cache_time');
        }
 
        private function getContainerParam()

Modified: incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php?rev=673461&r1=673460&r2=673461&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php Wed Jul  2 
11:42:58 2008
@@ -26,6 +26,7 @@
  * GET and POST based input, and peforms a request based on the input, headers 
and 
  * httpmethod params. It also deals with request signing and verification thru 
the
  * authz and st (security token) params. 
+ *
  */
 class ProxyHandler {
        private $context;
@@ -162,12 +163,6 @@
         */
        public function fetch($url, $signer, $method)
        {
-               try {
-                       $token = 
$this->context->extractAndValidateToken($signer);
-               } catch (Exception $e) {
-                       $token = '';
-                       // no token given, safe to ignore
-               }
                $url = $this->validateUrl($url);
                //TODO: Fetcher needs to handle variety of HTTP methods.
                $result = $this->fetchContent($url, $method);
@@ -180,14 +175,37 @@
                                        $key = trim(substr($header, 0, 
strpos($header, ':')));
                                        $val = trim(substr($header, 
strpos($header, ':') + 1));
                                        // filter out headers that would 
otherwise mess up our output
-                                       if (strcasecmp($key, 
"Transfer-Encoding") != 0 && strcasecmp($key, "Cache-Control") != 0 && 
strcasecmp($key, "Expires") != 0 && strcasecmp($key, "Content-Length") != 0) {
+                                       if (strcasecmp($key, 
"Transfer-Encoding") != 0 && strcasecmp($key, "Cache-Control") != 0 && 
strcasecmp($key, "Expires") != 0 && strcasecmp($key, "Content-Length") != 0 && 
strcasecmp($key, "ETag") != 0) {
                                                header("$key: $val");
                                        }
                                }
                        }
-                       $this->setCachingHeaders();
-                       // then echo the content
-                       echo $result->getResponseContent();
+                       $etag = md5($result->getResponseContent());
+                       $lastModified = 
$result->getResponseHeader('Last-Modified') != null ? 
$result->getResponseHeader('Last-Modified') : gmdate('D, d M Y H:i:s', 
$result->getCreated().' GMT');
+                       $notModified = false;
+                       // If HTTP_PRAGMA | HTTP_CACHE_CONTROL == no-cache, the 
browser wants to do a 'forced reload' 
+                       if (! isset($_SERVER['HTTP_PRAGMA']) || ! 
strstr(strtolower($_SERVER['HTTP_PRAGMA']), 'no-cache') && (! 
isset($_SERVER['HTTP_CACHE_CONTROL']) || ! 
strstr(strtolower($_SERVER['HTTP_CACHE_CONTROL']), 'no-cache'))) {
+                               if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && 
$_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
+                                       // if e-tag's match, set not modified, 
and no need to check the if-modified-since headers
+                                       $notModified = true;
+                               } elseif 
(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $this->lastModified && ! 
isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
+                                       $if_modified_since = 
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+                                       // Use the request's Last-Modified, 
otherwise fall back on our internal time keeping (the time the request was 
created)
+                                       $lastModified = 
strtotime($lastModified);
+                                       if ($lastModified <= 
$if_modified_since) {
+                                               $notModified = true;
+                                       }
+                               }
+                       }
+                       $this->setCachingHeaders($etag, 
$this->context->getRefreshInterval(), $lastModified);
+                       // If the cached file time is within the 
refreshInterval params value and the ETag match, return not-modified
+                       if ($notModified) {
+                               header('HTTP/1.0 304 Not Modified', true);
+                               header('Content-Length: 0', true);
+                       } else {
+                               // then echo the content
+                               echo $result->getResponseContent();
+                       }
                } else {
                        @ob_end_clean();
                        header("HTTP/1.0 404 Not Found", true);
@@ -284,11 +302,19 @@
         * the browser not to cache this. 
         *
         */
-       private function setCachingHeaders()
+       private function setCachingHeaders($etag = false, $maxAge = false, 
$lastModified = false)
        {
-               // TODO: Re-implement caching behavior if appropriate.
-               header("Cache-Control: private; max-age=0", true);
-               header("Expires: " . gmdate("D, d M Y H:i:s", time() - 3000) . 
" GMT", true);
+               if ($etag) {
+                       header("ETag: $etag");
+               }
+               if ($lastModified) {
+                       header("Last-Modified: $lastModified");
+               }
+               $expires = $maxAge !== false ? time() + $maxAge : time() - 3000;
+               $public = $maxAge ? 'public' : 'private';
+               $maxAge = $maxAge === false ? '0' : $maxAge;
+               header("Cache-Control: {$public}; max-age={$maxAge}", true);
+               header("Expires: " . gmdate("D, d M Y H:i:s", $expires) . " 
GMT", true);
        }
 
        /**


Reply via email to