You are here:Home arrow Home arrow PHP arrow Enable your Zend Framework App with Conditional GET! (Make it green)
  • Narrow screen resolution
  • Wide screen resolution
  • Decrease font size
  • Default font size
  • Increase font size
  • default color
  • green color
  • blue color

Enable your Zend Framework App with Conditional GET! (Make it green)

Tuesday, 10 February 2009

In this article I'll show you a simple approach to enable your Zend Framework application saving lots of precious bandwidth, and thus, making it more end-users friendly, and save on bandwidth costs.

This technique involves HTTP conditional GET. This is basically a feature of the HTTP protocol. By sending correct HTTP headers with your application, you enable browsers of your end users to cache pages of your site.

Are you worried about users having old versions of page in cache? Then don't! This technique allows to get all the benefits of client side caching without affecting anything but 5 minutes of your time to integrate it :).

The Zend Framework is great in regards that you can easily extend it. We are going to create a front controller plugin, which will take care of handling conditional GET requests.

Let's create our front controller plugin:
  1. <?php
  2. /**
  3.  * Plugin to support conditional GET for php pages (using ETag)
  4.  * Should be loaded the very last in the plugins stack
  5.  *
  6.  * @author $Author: danila $
  7.  * @version $Id: Conditional.php 15741 2009-02-08 11:58:44Z danila $
  8.  *
  9.  */
  10. class Smartycode_Http_Conditional extends Zend_Controller_Plugin_Abstract
  11. {
  12.  
  13. public function dispatchLoopShutdown()
  14. {
  15. $send_body = true;
  16.  
  17. $etag = '"' . md5($this->getResponse()->getBody()) . '"';
  18.  
  19. $inm = split(',', getenv("HTTP_IF_NONE_MATCH"));
  20.  
  21. $inm = str_replace('-gzip', '', $inm);
  22.  
  23. // If the request would, without the If-None-Match header field,
  24. // result in anything other than a 2xx or 304 status,
  25. // then the If-None-Match header MUST be ignored
  26. $response_code = $this->getResponse()->getHttpResponseCode();
  27.  
  28. if (($response_code > 200 && $response_code < 206) || ($response_code == 304)) {
  29. foreach ($inm as $i) {
  30. if (trim($i) == $etag) {
  31. $this->getResponse()
  32. ->clearAllHeaders()
  33. ->setHttpResponseCode(304)
  34. ->clearBody();
  35. $send_body = false;
  36. break;
  37. }
  38. }
  39. }
  40.  
  41. $this->getResponse()
  42. ->setHeader('Cache-Control', 'max-age=7200, must-revalidate', true)
  43. ->setHeader('Expires', gmdate('D, d M Y H:i:s', time() + 2 * 3600) . ' GMT', true)
  44. ->clearRawHeaders();
  45.  
  46. if ($send_body) {
  47. $this->getResponse()
  48. ->setHeader('Content-Length', strlen($this->getResponse()->getBody()));
  49. }
  50.  
  51. $this->getResponse()->setHeader('ETag', $etag, true);
  52. $this->getResponse()->setHeader('Pragma', '');
  53.  
  54. }
  55. }

You may wish to adjust the "7200" value to something lot longer. This is the number of seconds that you allow to user's browser store cached page. Even 720000 is safe, because with the no-cache header browser "is told" to cache page (don't mind it says no-cache) and check with the freshness of it with your server.

Attaching this plugin to front controller is really easy. As simple as placing the following in your bootstrap file: $frontController->registerPlugin(new Smartycode_Http_Conditional(), 101);

Note the "101". You should register the plugin the very last in plugins' stack.

These simple steps just made your Zend Framework application more environment friendly:

  • Works with AJAX calls going through Zend's MVC (all request types)
  • Pages are not transmitted if they haven't changed since last time they were requested
  • You can also assume you get the benefit for SEO, as search engines supporting Etag, will effectively be able to skip pages from downloading/reanalyzing and index your pages more quickly.
  • Sends Content-Length header, enabling Keep-Alive connections
  • There are more benefits, I'm lazy to think about all of them :)

UPDATE: I have modified the code to get rid of no-cache, due to rather incorrect treatment by browsers.

According to RFC: If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy a subsequent request without successful revalidation with the origin server..
Means: everytime browser requests the page, it should check (revalidate) existing cached entity (if any).

From Firefox's HTTP caching faq, no-cache directive triggers Firefox to generate expiration time that is always in the past, thus caching doesn't really work. IE does not cache "no-cache" entities at all. Another incorrect RFC implementation is that in Firefox, "must-revalidate" triggers Firefox to always check with server for cached page validity, while according to the spec it should check with server only after page is expired.

Noted all this, set your expiration time to something low actually if you have frequently updated content. This is the period of time that browser does not (IE does not, Firefox does, others do not) check with the webserver for updates. After this period of time they will check your page for updates via If-None-Match request.

UPDATE: Added code by Giovanni for excluding certain response codes from conditional get handling (more strict as to W3C spec).





Reddit!Del.icio.us!Facebook!Slashdot!Netscape!Technorati!StumbleUpon!
Last Updated ( Friday, 26 February 2010 )

Add comment


Security code
Refresh

< Prev