Permalink
Browse files

Improved cache handling when sessions and cookies are used. Bug fix i…

…n cache handling for the new API setting 'www-cache-load-timestamp'.
  • Loading branch information...
1 parent abe5d19 commit c8ab4d1837ccd4cba5b6b5eb9c577926ac1b767a @kristovaher committed Oct 24, 2012
View
@@ -1,2 +1,2 @@
-www:3.4.2
+www:3.4.3
system:1.0.0
@@ -31,7 +31,7 @@ class WWW_controller_view extends WWW_Factory {
public function load($input){
// Unsetting input data that are only used by API and are accessible elsewhere by the user
- unset($input['www-command'],$input['www-return-type'],$input['www-cache-timeout'],$input['www-request'],$input['www-session'],$input['www-cache-tags']);
+ unset($input['www-command'],$input['www-return-type'],$input['www-cache-timeout'],$input['www-cache-load-timeout'],$input['www-request'],$input['www-session'],$input['www-cache-tags']);
// Getting view information
$view=$this->viewData();
@@ -139,7 +139,7 @@
<h3>www-session</h3>
- <p>This variable holds data from session variable, but not the whole $_SESSION array. Wave Framework namespaces its session data and only the namespaced session data is stored in this 'www-session' array. This variable is automatically defined for API and should not be sent through input variables.</p>
+ <p>This variable tells Wave Framework API to load session data from active session and apply it as input data for any API call. This is set to 1 by default, so session data gets always included, with the exception of Factory api() calls, which disable it by default.</p>
<h3>www-crypt-input</h3>
View
@@ -125,5 +125,12 @@
<p>Usually this compression error comes as a result of empty space existing after your PHP scripts, so make sure that the scripts you have edited do not have empty space after closing PHP tags. Alternative is to not use closing tags on PHP scripts at all.</p>
+
+ <h3>I am using page caching and I have a problem caching pages with and without active sessions, what can I do?</h3>
+
+ <p>There is a little-known bug in both Safari and Opera web browsers that ignore 'Vary' header from response HTTP request headers. 'Vary' header tells caching engines what header components to take into account when writing cache and validating cache in the storage. While Firefox, Chrome and Internet Explorer browsers all listen to this 'Vary' header as expected, Safari and Opera don't which means that sometimes these browsers may display cached content from time when there was no session cookie.</p>
+
+ <p>What is recommended is to not cache any pages (by setting www-cache-timestamp to 0 in Sitemap file or not defining it at all) that have user-specific information or simply wait until user reloads the page manually.</p>
+
</body>
</html>
@@ -25,6 +25,7 @@
<h2>3.x.x</h2>
<ul>
+ <li><b>3.4.3</b> - Improved cache handling when sessions and cookies are used. Bug fix in cache handling for the new API setting 'www-cache-load-timestamp'.</li>
<li><b>3.4.2</b> - Integrated a new method callTool() to Factory and State, which allows you to call system specific functions such as filesystem cleaner or to get an index of files. More tools will be added in the future. The additional JavaScript and PHP loading now targets /scripts/ folder and not /libraries/ folder. Added the option to load external JS and CSS based on sitemap URL. File handler can also use overrides now, if a file is in resources folder. Moved '/resources/autoload.php' to '/resources/scripts/script.php'.</li>
<li><b>3.4.1</b> - Added options for serving Appcache manifests. Fixed some dynamic URL routing bugs in URL Controller.</li>
<li><b>3.4.0</b> - A new Factory JavaScript class is now available, which allows you to dynamically load classes from /resources/classes/ subfolder (or another folder, since it is configurable within Factory). It is now also possible to dynamically load class.www-wrapper.js and class.www-factory.js together with other main JavaScript files, Resource Handler tests for this internally and loads them from /engine/ folder instead. Also JavaScript and CSS files have a separate folder now under /resources/ subfolder. Added the ability to also dynamically load function libraries from both PHP and JavaScript. Documentation updated. Added options that allow to load a set of libraries as defined in Sitemap as well as allowing View Controller to also load view-specific PHP autoload-style scripts. Updated state messenger and its documentation to work so that it 'makes more sense'.</li>
View
@@ -181,6 +181,7 @@
<li><b>robots-cache-timeout</b> - Cache lifetime of robots.txt files.</li>
<li><b>server-ip</b> - Server IP address.</li>
<li><b>session-data</b> - This holds data from session storage or temporary data before it is stored in session storage.</li>
+ <li><b>session-original-data</b> - This is the session data at the start of the request, used for comparing.</li>
<li><b>session-domain</b> - Session cookie domain.</li>
<li><b>session-fingerprint</b> - True or false flag for whether sessions are tied to IP and browser fingerprint.</li>
<li><b>session-fingerprint-key</b> - Key value that the fingerprint will be stored under in sessions.</li>
@@ -236,10 +237,6 @@
<p>This is an internal variable that State uses to check if it has already validated sessions or not.</p>
- <h3>private $originalSessionData=array()</h3>
-
- <p>This carries the session data that was active at the moment sessions were created. It is used to compare later on if sessions need to be commited or not.</p>
-
<h3>private $messenger=false</h3>
<p>This holds the 'keyword' or 'passkey' of currently used <a href="guide_messenger.htm">State Messenger</a> .</p>
@@ -17,7 +17,7 @@
* @license GNU Lesser General Public License Version 3
* @tutorial /doc/pages/api.htm
* @since 1.0.0
- * @version 3.4.2
+ * @version 3.4.3
*/
final class WWW_API {
@@ -324,7 +324,7 @@
'disable-callbacks'=>(isset($apiInputData['www-disable-callbacks']))?$apiInputData['www-disable-callbacks']:false,
'crypt-output'=>false,
'profile'=>$this->state->data['api-public-profile'],
- 'push-output'=>(isset($apiInputData['www-output']))?$apiInputData['www-output']:true,
+ 'push-output'=>(isset($apiInputData['www-output']))?$apiInputData['www-output']:1,
'return-hash'=>(isset($apiInputData['www-return-hash']))?$apiInputData['www-return-hash']:false,
'return-timestamp'=>(isset($apiInputData['www-return-timestamp']))?$apiInputData['www-return-timestamp']:false,
'return-type'=>(isset($apiInputData['www-return-type']))?$apiInputData['www-return-type']:'json',
@@ -342,7 +342,7 @@
// Turning output off if the HTTP HEAD request is made
if($this->state->data['http-request-method']=='HEAD'){
- $apiState['push-output']=false;
+ $apiState['push-output']=0;
}
// If API command is not set and is not set in input array either, the system will return an error
@@ -360,9 +360,13 @@
}
// If session data is set
- if(!empty($this->state->data['session-data'])){
- $apiInputData['www-session']=$this->state->data['session-data'];
- unset($apiInputData['www-session'][$this->state->data['session-fingerprint-key']]);
+ if(!isset($apiInputData['www-session']) || $apiInputData['www-session']==1){
+ if(!empty($this->state->data['session-data'])){
+ // Grabbing session data for input
+ $apiInputData['www-session']=$this->state->data['session-data'];
+ // Unsetting the session cookie and other variables from the input data
+ unset($apiInputData['www-session'][$this->state->data['session-fingerprint-key']],$apiInputData['www-session'][$this->state->data['session-timestamp-key']],$apiInputData['www-session'][$this->state->data['session-token-key']]);
+ }
}
// Sorting the input array
@@ -393,13 +397,14 @@
// This tests if cache value sent through input is valid
if($apiState['command']!='www-create-session'){
+ // If cache timeout is set then it is also applied as default to load timeout
if(isset($apiInputData['www-cache-timeout']) && $apiInputData['www-cache-timeout']>=0){
$apiState['cache-timeout']=$apiInputData['www-cache-timeout'];
$apiState['cache-load-timeout']=$apiInputData['www-cache-timeout'];
}
// Loading cache timestamp must be less than the actual cache timeout setting that is set
if(isset($apiInputData['www-cache-load-timeout']) && $apiInputData['www-cache-load-timeout']>=0 && $apiInputData['www-cache-load-timeout']<=$apiState['cache-timeout']){
- $apiState['cache-load-timeout']=$apiState['cache-timeout'];
+ $apiState['cache-load-timeout']=$apiInputData['www-cache-load-timeout'];
}
}
@@ -574,7 +579,7 @@
// Validation hash is calculated from input data
$validationData=$apiInputData;
// Session input is not considered for validation hash and is unset
- unset($validationData['www-hash'],$validationData['www-session']);
+ unset($validationData['www-hash'],$validationData['www-session'],$validationData[$this->state->data['session-name']]);
// Unsetting any possible exceptions (such as file uploads and cookie input)
if(is_array($apiValidation) && !empty($apiValidation)){
@@ -640,10 +645,11 @@
} else {
return $this->output(array('www-message'=>'Problem decrypting encrypted data: No tools to decrypt data','www-response-code'=>114),$apiState);
}
+ // This variable is not used anymore
+ unset($apiInputData['www-crypt-input']);
}
// If this is set, then the value of this is used to crypt the output
- // Please note that while www-crypt-output key can be set outside www-crypt-input data, it is recommended to keep that key within crypted input when making a request
if(isset($apiInputData['www-crypt-output'])){
$apiState['crypt-output']=$apiInputData['www-crypt-output'];
}
@@ -706,15 +712,14 @@
// This stores a flag about whether cache is used or not
$this->apiLoggerData['cache-used']=false;
$this->apiLoggerData['www-command']=$apiState['command'];
-
- // If cache timeout is set
- // If this value is 0, then no cache is used for command
- if($apiState['cache-load-timeout']){
+
+ // Calculating cache folder locations, if either load or write cache is used
+ if($apiState['cache-load-timeout'] || $apiState['cache-timeout']){
// Calculating cache validation string
$cacheValidator=$apiInputData;
// If session namespace is defined, it is removed from cookies for cache validation
- unset($cacheValidator[$this->state->data['session-name']],$cacheValidator['www-headers'],$cacheValidator['www-cache-tags'],$cacheValidator['www-hash'],$cacheValidator['www-state'],$cacheValidator['www-timestamp'],$cacheValidator['www-crypt-output'],$cacheValidator['www-cache-timeout'],$cacheValidator['www-cache-load-timeout'],$cacheValidator['www-return-type'],$cacheValidator['www-output'],$cacheValidator['www-return-hash'],$cacheValidator['www-return-timestamp'],$cacheValidator['www-content-type'],$cacheValidator['www-minify'],$cacheValidator['www-crypt-input'],$cacheValidator['www-xml'],$cacheValidator['www-json'],$cacheValidator['www-ip-session'],$cacheValidator['www-disable-callbacks'],$cacheValidator[$this->state->data['session-token-key']]);
+ unset($cacheValidator[$this->state->data['session-name']],$cacheValidator['www-headers'],$cacheValidator['www-cache-tags'],$cacheValidator['www-hash'],$cacheValidator['www-state'],$cacheValidator['www-timestamp'],$cacheValidator['www-crypt-output'],$cacheValidator['www-cache-timeout'],$cacheValidator['www-cache-load-timeout'],$cacheValidator['www-return-type'],$cacheValidator['www-output'],$cacheValidator['www-return-hash'],$cacheValidator['www-return-timestamp'],$cacheValidator['www-content-type'],$cacheValidator['www-minify'],$cacheValidator['www-ip-session'],$cacheValidator['www-disable-callbacks'],$cacheValidator[$this->state->data['session-token-key']]);
// MD5 is used for slight performance benefits over sha1() when calculating cache validation hash string
$cacheValidator=md5($apiState['command'].serialize($cacheValidator).$apiState['return-type'].$apiState['push-output'].$this->state->data['version']);
@@ -724,6 +729,11 @@
// Setting cache folder
$cacheFolder=$this->state->data['system-root'].'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'output'.DIRECTORY_SEPARATOR.substr($cacheFile,0,2).DIRECTORY_SEPARATOR;
+ }
+
+ // If cache is actually loaded
+ if($apiState['cache-load-timeout']){
+
// If cache file exists, it will be parsed and set as API value
if(file_exists($cacheFolder.$cacheFile)){
@@ -735,19 +745,23 @@
// If server detects its cache to still within cache limit
if($apiState['last-modified']>=($this->state->data['request-time']-$apiState['cache-load-timeout'])){
+
// If this request has already been made and the last-modified timestamp is exactly the same
- if($apiState['push-output'] && $this->state->data['http-if-modified-since'] && $this->state->data['http-if-modified-since']>=$apiState['last-modified']){
+ if($apiState['push-output'] && $this->state->data['http-if-modified-since'] && $this->state->data['http-if-modified-since']==$apiState['last-modified']){
// Adding log data
if($useLogger){
$this->apiLoggerData['cache-used']=true;
$this->apiLoggerData['response-code']=304;
}
// Cache headers (Last modified is never sent with 304 header)
- if($this->state->data['http-authentication']==true){
+ if($this->state->data['http-authentication']==true || isset($this->state->data['session-data'][$this->state->data['session-user-key']]) || isset($this->state->data['session-data'][$this->state->data['session-permissions-key']])){
header('Cache-Control: private,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
} else {
header('Cache-Control: public,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
}
+ // This tells caching engine to take cookies and content encoding into account
+ header('Vary: Accept-Encoding,Cookie');
+ // Expires header based on timeout
header('Expires: '.gmdate('D, d M Y H:i:s',($apiState['last-modified']+$apiState['cache-timeout'])).' GMT');
// Returning 304 header
header('HTTP/1.1 304 Not Modified');
@@ -756,23 +770,16 @@
// System loads the result from cache file based on return data type
$apiResult=$this->getCache($cacheFolder.$cacheFile);
-
// Since cache was used
$this->apiLoggerData['cache-used']=true;
} else {
- // Since cache seems to be outdated, a new one will be generated with new request time
+ // Since cache seems to be outdated, last modified time is reset to request time
$apiState['last-modified']=$this->state->data['request-time'];
}
- } else {
- // Current cache timeout is used to return to browser information about how long browser should store this result
- $apiState['last-modified']=$this->state->data['request-time'];
}
- } else {
- // Since cache is not used, last modified time is the time of the request
- $apiState['last-modified']=$this->state->data['request-time'];
}
// SOLVING API RESULT IF RESULT WAS NOT FOUND IN CACHE
@@ -1042,7 +1049,7 @@
break;
case 'php':
// If PHP is used, then it can not be 'echoed' out due to being a PHP variable, so this is turned off
- $apiState['push-output']=false;
+ $apiState['push-output']=0;
break;
}
@@ -1115,7 +1122,7 @@
// Cache control settings sent to the user agent depend on cache timeout settings
if($apiState['cache-timeout']!=0){
// Cache control depends whether HTTP authentication is used or not
- if($this->state->data['http-authentication']==true){
+ if($this->state->data['http-authentication']==true || isset($this->state->data['session-data'][$this->state->data['session-user-key']]) || isset($this->state->data['session-data'][$this->state->data['session-permissions-key']])){
header('Cache-Control: private,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
} else {
header('Cache-Control: public,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
@@ -1124,11 +1131,14 @@
header('Last-Modified: '.gmdate('D, d M Y H:i:s',$apiState['last-modified']).' GMT');
} else {
// When no cache is used, request tells specifically that
- header('Cache-Control: no-store;');
+ header('Cache-Control: no-cache,no-store;');
header('Expires: '.gmdate('D, d M Y H:i:s',$this->state->data['request-time']).' GMT');
- header('Last-Modified: '.$apiState['last-modified'].' GMT');
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s',$apiState['last-modified']).' GMT');
}
+ // This tells caching engine to take cookies and content encoding into account
+ header('Vary: Accept-Encoding,Cookie');
+
// If custom header was assigned, it is added
if($apiState['custom-header']){
header($apiState['custom-header']);
@@ -1197,8 +1207,6 @@
case 'gzip':
// Notifying user agent of gzipped output
header('Content-Encoding: gzip');
- // This tells proxies to store both compressed and uncompressed version
- header('Vary: Accept-Encoding');
$apiResult=gzencode(ob_get_clean(),9);
break;
default:
@@ -15,7 +15,7 @@
* @license GNU Lesser General Public License Version 3
* @tutorial /doc/pages/factory.htm
* @since 1.0.0
- * @version 3.4.2
+ * @version 3.4.3
*/
class WWW_Factory {
@@ -96,6 +96,10 @@ class WWW_Factory {
if(!isset($inputData['www-output'])){
$inputData['www-output']=0;
}
+ // This tells API not to include session as input data for the API call
+ if(!isset($inputData['www-session'])){
+ $inputData['www-session']=0;
+ }
// Returning the result from API
return $this->WWW_API->command($inputData,$useBuffer,false);
@@ -17,7 +17,7 @@
* @license GNU Lesser General Public License Version 3
* @tutorial /doc/pages/sessions.htm
* @since 3.2.0
- * @version 3.2.7
+ * @version 3.4.3
*/
final class WWW_Sessions {
@@ -52,7 +52,7 @@
'http-only'=>true
);
- /*
+ /**
* This is a true-false flag about whether session ID should be regenerated. This can be sent by
* the system that uses the Session Handler at any time before sessions are commited. Wave
* Framework uses this to regenerate cookies when it detects the cookie lifetime is about to end.
Oops, something went wrong.

0 comments on commit c8ab4d1

Please sign in to comment.