by Alexandre Alapetite on 2004-07-04; updated on 2016-08-07

HTTP conditional requests in PHP

HTTP has a very efficient mechanism to benefit from the client’s Web browser’s cache and other capacities. This allows the reduction of the required bandwidth, processor time, and improve response time.

When a client asks for a document the first time, this one is transmitted. But when this client ask for the same document again, because the document might have been modified, the client sends at the same time a date and an identifier of the last version received. The server will send back the document only if it has been modified since the client’s version, otherwise a not-modified response will be sent. In all the cases, the client sends also its capacities, and the communication is optimised according to that, with compression and persistent connections.

Summary: I propose a free library — only one function — to handle the different kinds of conditional requests (304, 412), HEAD requests, cache management at client and proxy level and compression of data. In RSS/Atom mode, allows filtering by date the articles server side, to transfer to the client only the new articles. Includes basic support of sessions. No modification in the PHP or HTTP server configuration is needed. There is no need to add any software client or server side. In order to use it, the library has to be included at the top of the PHP script with a require() and one function has to be called.

Download the module

français

Table of contents

Exit

How HTTP conditional requests work

The main HTTP headers involved

Last-Modified: Thu, 08 Jul 2004 17:33:54 GMT
The server returns the document’s last modification date. HTTP/1.0 compatible. It is interesting to have an explicit date for users and search engines. See reference for date format.
Etag: "82e81980-27f3-4a6ae480"
The server returns a unique identifier for the document. This Etag is modified if the address or the content of the document have changed. This header can be used at the same time as Last-Modified or alone (HTTP/1.1). The fact that the Etag is coded allows hiding if needed of the last modification date. See reference for ETag format.
If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT
Sent by the client to ask for an update if the document has been modified since this date. The date has to be the same string as the one received in the Last-Modified server header.
If-None-Match: "82e81980-27f3-4a6ae480"
Sent by the client to ask for an update if no document can match this Etag. The Etag has to be the same string as the one received in the Etag server header.
Cache-Control: private, max-age=0, must-revalidate
Used by the server, intermediate caches and the client mainly to manage the client cache and the way the document can be shared by several users without having to query the server each time.
Accept-Encoding: gzip,deflate
Sent by the client to inform the server that the document can be transferred in one of the listed modified (compressed) formats.
Content-Encoding: gzip
Sent by the server to inform the client that the document is transferred in a modified (compressed) format. The format used should be one that has been proposed by the client in Accept-Encoding.
Content-Length: 3495
If the length of the document is known (static document, or buffered dynamic document), this field is used by the server to tell the client how long is the file. This allows persistent connections; otherwise, if the length is not known, the transfer is made in “chunked” mode and the end of the document is when the connection is closed by the server.
Connection: keep-alive
Used by the server and the client to use the same persistent connection to transfer several files. In order to do that, most of the servers and clients need Content-Length to be set.

See my documentation about HTTP and HTML redirections for some comments about HTTP headers.


Classic communication schema

In the following example, only headers relevant for conditional requests are shown.

1. The client ask for the document the first time

GET /test.php HTTP/1.1

2. The server sends the document

HTTP/1.x 200 OK
Date: Thu, 08 Jul 2004 17:42:26 GMT
Last-Modified: Thu, 08 Jul 2004 17:33:54 GMT
Etag: "82e81980-27f3-4a6ae480"

<html>
...
</html>

3. The client asks for the same document a second time and gives the references of the version he has (in his cache)

GET /test.php HTTP/1.1
If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT
If-None-Match: "82e81980-27f3-4a6ae480"

4. The server replies with a not-modified header since the document has not been modified since the client’s version

HTTP/1.x 304 Not Modified
Date: Thu, 08 Jul 2004 17:46:31 GMT
Etag: "82e81980-27f3-4a6ae480"

5. The client asks for the same document a third time

GET /test.php HTTP/1.1
If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT
If-None-Match: "82e81980-27f3-4a6ae480"

6. The server provides the newest version because the document has been modified since the client’s version

HTTP/1.x 200 OK
Date: Thu, 08 Jul 2004 17:48:54 GMT
Last-Modified: Thu, 08 Jul 2004 17:48:52 GMT
Etag: "82e81980-2bf2-7ff14900"

<html>
...
</html>

Response to a HTTP conditional request

This mechanism is normally automatically handled by HTTP servers (Apache, IIS, ...) for static documents such as HTML pages, JPEG pictures, etc. but it is the programmer’s responsibility to manage it for dynamic documents like PHP, CGI, etc.

No additional software is needed, server or client side. Most of the current servers and browsers are natively compatible with this technique. In case the client is not HTTP/1.1 compliant, this optimisation is not working anymore but the communication is working normally.

I propose a module, to be included at the top of your PHP pages to automatically manage those conditional requests, in order to save processor time, bandwidth, and allows a faster navigation for the client. It can control cache mechanism in the client and proxy, and can compress data. It has also a special RSS/ATOM feeds feature to send only the new articles to the client. Basic support for sessions is included.

This module takes care of the different conditional requests, uses the last modification date of the script itself, and handle HEAD requests.


How to use this library?

Before sending any text to the client, you just have to call the function httpConditional() with:

$UnixTimeStamp (required)
The date of the last modification of the data, UNIX timestamp format (seconds since the 1st of January 1970 at 00:00:00 GMT).
The function httpConditional() takes care of the modification date of the calling script itself.
$cacheSeconds=0 (implied)
Lifetime in seconds of the cached version of the document. If <0, caching is disabled. Set to 0, the document will be revalidated each time it is accessed.
$cachePrivacy=0 (implied)
Sharing policy between users of a cached version. 0=private, 1=normal (public), 2=forced public (also when the accessed zone requires a password, or in order to activate some caching with HTTPS).
$feedMode=false (implied)
Special RSS/ATOM feeds: allows filtering articles server side and sending to the client only the articles that are newer than the client’s last update.
When this parameter is set to true, the global variable $clientCacheDate will contain the date of the client’s cache version, the cache policy is forced to private, the connection is closed quickly since the client usually takes only one file, and the last modification of the script is not taken into account.
$compression=false (implied)
Enable data compression according to the capacities of the client.
Allows persistent connections, according to the server policy (e.g.: Apache uses them, but IIS close them everytime).
You can override the default level of compression (6) using zlib.output_compression_level in php.ini or with ini_set('zlib.output_compression_level',7) for example (1..9).
$session=false (implied)
To be turned on when sessions are used.
Automatically checks if the data contained in $_SESSION has been modified since the last generation of the document.
To be used together with session_cache_limiter(''); and/or session.cache_limiter='' in php.ini

Basic usage:

example.php

<?php
 require_once('http-conditional.php');
 //Date of the last modification of the content (Unix Timestamp format)
 //Example: request form a database
 $dateLastModification=...;
 if (httpConditional($dateLastModification))
 {//No modification since the client’s version
  ... //Close the database, and other cleaning
  exit(); //No need to send anything else
 }
 //!\ Do not send any text to the client before this line
 ... //The rest of the script, just like if this first part was not used
?>
http-conditional.php

<?php
/*Optimisation: Enable support for HTTP/1.x conditional requests in PHP.*/

//In RSS/ATOM feedMode, contains the date of the clients last update.
$clientCacheDate=0; //Global variable because PHP4 does not allow conditional arguments by reference
$_sessionMode=false; //Global private variable

function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false)
{//Credits: https://alexandre.alapetite.fr/doc-alex/php-http-304/
 //RFC2616 HTTP/1.1: http://www.w3.org/Protocols/rfc2616/rfc2616.html
 //RFC1945 HTTP/1.0: http://www.w3.org/Protocols/rfc1945/rfc1945.txt

 //If HTTP headers are already sent, too late, nothing to do.
 if (headers_sent()) return false;

 if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME'];
 elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED'];
 else return false;

 if ((!$feedMode)&&(($modifScript=filemtime($scriptName))>$UnixTimeStamp))
  $UnixTimeStamp=$modifScript; //Last modification date, of the data and of the script itself
 //The modification date must to be newer than the current time on the server
 $UnixTimeStamp=min($UnixTimeStamp,time());

 //If the conditional request allows to use the client’s cache
 $is304=true;
 //If the conditions are refused
 $is412=false;
 //There is a need for at least one condition to allow a 304 Not Modified response
 $nbCond=0;

 /*
 Date format: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
  To smallest common divisor between the different standards that have been used is like: Mon, 28 Jun 2004 18:31:54 GMT
  It is compatible HTTP/1.1 (RFC2616,RFC822,RFC1123,RFC733) and HTTP/1.0 (Usenet getdate(3),RFC850,RFC1036).
 */
 $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp);
 $dateCacheClient='Tue, 10 Jan 1980 20:30:40 GMT';

 //Entity tag (Etag) of the returned document.
 //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
 //Must be modified if the filename or the content have been changed
 if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING'];
 else $myQuery='';
 if ($session&&isset($_SESSION))
 {//In the case of sessions, integrate the variables of $_SESSION in the ETag calculation
  global $_sessionMode;
  $_sessionMode=$session;
  $myQuery.=print_r($_SESSION,true).session_name().'='.session_id();
 }
 $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; //='"0123456789abcdef0123456789abcdef"'

 if ((!$is412)&&isset($_SERVER['HTTP_IF_MATCH']))
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24
  $etagsClient=stripslashes($_SERVER['HTTP_IF_MATCH']);
  //Compare the current Etag with the ones provided by the client
  $etagsClient=str_ireplace('-gzip','',$etagsClient);
  $is412=(($etagsClient!=='*')&&(strpos($etagsClient,$etagServer)===false));
 }
 if ($is304&&isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
  //http://www.w3.org/Protocols/rfc1945/rfc1945.txt
  //Get the date of the client’s cache version
  //No need to check for consistency, since a string comparison will be made.
  $nbCond++;
  $dateCacheClient=$_SERVER['HTTP_IF_MODIFIED_SINCE'];
  $p=strpos($dateCacheClient,';'); //Internet Explorer is not standard compliant
  if ($p!==false) //IE6 might give "Sat, 26 Feb 2005 20:57:12 GMT; length=134"
   $dateCacheClient=substr($dateCacheClient,0,$p); //Removes the information after the date added by IE
  //Compare the current document’s date with the date provided by the client.
  //Must be identical to return a 304 Not Modified
  $is304=($dateCacheClient==$dateLastModif);
 }
 if ($is304&&isset($_SERVER['HTTP_IF_NONE_MATCH']))
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
  //Compare Etags to check if the client has already the current version
  $nbCond++;
  $etagClient=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
  $etagClient=str_ireplace('-gzip','',$etagClient);
  $is304=(($etagClient===$etagServer)||($etagClient==='*'));
 }

 //$_SERVER['HTTP_IF_RANGE']
 //This library does not handle this condition. Returns a normal 200 in all the cases.
 //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27

 if ((!$is412)&&isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE']))
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.28
  $dateCacheClient=$_SERVER['HTTP_IF_UNMODIFIED_SINCE'];
  $p=strpos($dateCacheClient,';');
  if ($p!==false)
   $dateCacheClient=substr($dateCacheClient,0,$p);
  $is412=($dateCacheClient!==$dateLastModif);
 }
 if ($feedMode)
 {//Special RSS
  global $clientCacheDate;
  $clientCacheDate=@strtotime($dateCacheClient);
  $cachePrivacy=0;
 }

 if ($is412)
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.13
  header('HTTP/1.1 412 Precondition Failed');
  header('Content-Type: text/plain');
  header('Cache-Control: private, max-age=0, must-revalidate');
  echo "HTTP/1.1 Error 412 Precondition Failed: Precondition request failed positive evaluation\n";
  //The response is finished; the request has been aborted
  //because the document has been modified since the client has decided to do an action
  //(avoid edition conflicts for example)
  return true;
 }
 elseif ($is304&&($nbCond>0))
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
  header('HTTP/1.0 304 Not Modified');
  header('Etag: '.$etagServer);
  if ($feedMode) header('Connection: close'); //You should comment this line when running IIS
  return true; //The response is over, the client will use the version in his cache
 }
 else //The request will be handled normally, without condition
 {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1
  //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
  if ($compression&&isset($_SERVER['HTTP_ACCEPT_ENCODING'])&&
      extension_loaded('zlib')&&(!ini_get('zlib.output_compression')))
   ob_start('_httpConditionalCallBack'); //Use compression.
   //ob_gzhandler() will check HTTP_ACCEPT_ENCODING and put correct headers
  //header('HTTP/1.0 200 OK'); //By default in PHP
  //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
  if ($cacheSeconds<0)
  {
   $cache='private, no-cache, no-store, must-revalidate';
   header('Pragma: no-cache');
  }
  else
  {
   if ($cacheSeconds==0) $cache='private, must-revalidate, ';
   elseif ($cachePrivacy==0) $cache='private, ';
   elseif ($cachePrivacy==2) $cache='public, ';
   else $cache='';
   $cache.='max-age='.floor($cacheSeconds);
  }
  header('Cache-Control: '.$cache);
  header('Last-Modified: '.$dateLastModif);
  header('Etag: '.$etagServer);
  //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
  //No need to keep a connection opened for RSS/ATOM feeds
  //since most of the time clients take only one file
  if ($feedMode) header('Connection: close');  //You should comment this line when running IIS
  //http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
  //In the case of a HEAD request,
  //the same headers as a GET request must be returned,
  //but the script does not need to calculate any content
  return $_SERVER['REQUEST_METHOD']=='HEAD';
 }
}

function _httpConditionalCallBack(&$buffer,$mode=5)
{//Private function automatically called at the end of the script when compression is enabled
 //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
 //You can adjust the level of compression with zlib.output_compression_level in php.ini
 if (extension_loaded('zlib')&&(!ini_get('zlib.output_compression')))
 {
  $buffer2=ob_gzhandler($buffer,$mode); //Will check HTTP_ACCEPT_ENCODING and put correct headers
  if (strlen($buffer2)>1) //When ob_gzhandler succeeded
   $buffer=$buffer2;
 }
 header('Content-Length: '.strlen($buffer)); //Allows persistent connections
 //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13
 return $buffer;
}

function httpConditionalRefresh($UnixTimeStamp)
{//Update HTTP headers if the content has just been modified by the client’s request
 //See an example on https://alexandre.alapetite.fr/doc-alex/compteur/
 if (headers_sent()) return false;

 if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME'];
 elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED'];
 else return false;

 $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp);

 if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING'];
 else $myQuery='';
 global $_sessionMode;
 if ($_sessionMode&&isset($_SESSION))
  $myQuery.=print_r($_SESSION,true).session_name().'='.session_id();
 $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"';

 header('Last-Modified: '.$dateLastModif);
 header('Etag: '.$etagServer);
}
?>

Recommended usage

With $dateLastModification being you variable containing the date of the last modification of the data in the UNIX format, here are the recommended arguments to pass to the function in different cases:

Classic public document
httpConditional($dateLastModification,18000,1,false,true)
Public cache for 5 hours, with compression and persistent connexions.
Classic private document
httpConditional($dateLastModification,3600,0,false,true)
Private cache for 1 hour, with compression and persistent connexions.
Private document that can be modified by the user
httpConditional($dateLastModification,5,0,false,true)
Private cache for 5 seconds, with compression and persistent connexions.
RSS/ATOM feed
httpConditional($dateLastModification,3600,0,true,true)
Private cache for 1 hour, RSS/ATOM mode with fast closed connexions, with compression.
Use the global variable $clientCacheDate to filter the articles by date in your SQL query.

Useful date functions

Some useful functions to manage dates and give them to httpConditional() at the UNIX format:

time()
Return the current Unix timestamp
strtotime()
Parse a textual datetime description into a Unix timestamp
getlastmod()
Gets time of last page modification
filemtime()
Gets file modification time
MySQL UNIX_TIMESTAMP()
In a MySQL query using SQL, converts a date field into a UNIX timestamp

Utilisation cases

This module has been tested for example with PHP/4/5/7 under Apache/1.3/2.0 and IIS/5.1. Not all the clients are able to handle all the optimisations, but the communication has been perfect with all the tested clients, like InternetExplorer/5.0/5.5/6.0, Netscape/1.22/2.02/3.04/4.8/Mozilla, Opera/7, SharpReader/0.9.5, RSSreader/1.0.88...

Now, here are some examples using this library.

Article with MySQL

In order to get the full power from conditional requests, the last modification date must be quickly and easily accessible. Also, an optimisation of the database is sometimes a good idea. For example, a table containing the main needed modification dates can be very efficient.

This simple case uses PHP to display an article that is stored into a MySQL database.
The table that contains articles has a field called “modified”, which contains a date at MySQL format. We want to retrieve by SQL the date of the last modification of this article in a UNIX timestamp.

article.php

<?php
if isset($_GET['id']) $num=$_GET['id']; //Reference of the article
else $num=0;
if (($connect=mysql_connect('server','user','password'))&&mysql_select_db('mybase'))
{
 $query='SELECT UNIX_TIMESTAMP(ar.modified) AS lastmod FROM articles ar WHERE ar.id='.$num;
 if (($result=mysql_query($query))&&($row=mysql_fetch_assoc($result)))
 {
  $dateLastModification=$row['lastmod'];
  mysql_free_result($result);
  if (httpConditional($dateLastModification,0,0,false,true)) //Private policy, compression
  {//No modification since the client’s last update
   mysql_close($connect);
   exit();
  }
 }
}
else $connect=false;
?>
<html>
...
<?php
if ($connect)
{
 ... //Other requests to the database
 mysql_close($connect);
}
?>
...
</html>

In this example, we have used compression, and a private cache policy, with a lifetime of 0. If this article is public, with no access condition, it is possible to save resources by activating the cache and with a public policy, as we will see in the next example about the dynamic PNG picture.


Dynamic PNG picture

In this case, we want to generate a dynamic PNG picture. The picture is just a label, and the title comes from the label.txt text file. So the date of the last modification of the picture is the date of the last modification of this text file, which contains the real data.

This public picture is accessed very frequently. We wish to keep copies of it in clients’ Web browsers and in intermediate caches, such as proxies, Internet providers, etc. We choose a lifetime for these copies of 180 seconds. This is a balance to estimate between resources and freshness; it depends on how often this picture is modified.

image.png.php

<?php
require_once('http-conditional.php');
header('Content-type: image/png');
//Modification date of the file that contains the title
$dateLastModification=filemtime('label.txt');
if (httpConditional($dateLastModification,180,2)) //Public cache, 180 seconds
 exit(); //No modification since the client’s last update
if ($handle=fopen('label.txt','r')) //This file contains “Hello World!”
{
 $label=fread($handle,255); //Read the title for the label
 fclose($handle);
}
else $label='Error';
$im=@imagecreate(120,30) or die('GD library error');
header('Content-type: image/png');
$bgcolor=imagecolorallocate($im,160,150,255);
$color=imagecolorallocate($im,5,5,5);
imagestring($im,5,7,7,$label,$color);
imagepng($im);
imagedestroy($im);
?>

RSS feed with ODBC

In this case, we want to know with one SQL query the most recent modification date of elements stored in several tables.
This RSS1.0 feed is composed of data coming from 3 tables: “articles”, “news”, “documents”. Each table has a field called “modified” containing a UNIX timestamp.

In order to avoid retransmitting articles again and again each time the client ask for an update of the RSS feed, we will filter server side the articles by date, and send to the client only the articles that are newer than his version ($clientCacheDate). This provides an interesting optimisation of bandwidth. But this implies that the response is client dependent, and the cache policy must be private ($cachePrivacy=0). This is ensured by the function when $feedMode is set to true.
Moreoever, we will enable data compression if the client can handle it ($compression=true), in order to decrease the size of the text to send.
If no new article is available, a “304 Not Modified” response will be sent to the client, like in previous examples.

HTTP/1.1 and compression support are not as good for RSS readers (clients) than they are with Internet browsers, but it is getting better and better. Some clients such as SharpReader are already compliant enough. Here again, if the client does not handle HTTP/1.1, the optimisation cannot be done, but the communication goes on normally, without side effect.

rss.php

<?php
require_once('http-conditional.php');
if ($odbc=odbc_connect('basetest','user','password'))
{
 $sql='SELECT MAX(modif) AS lastmod FROM ('.
  'SELECT MAX(ar.date) AS modif FROM articles ar UNION '.
  'SELECT MAX(br.date) AS modif FROM news br UNION '.
  'SELECT MAX(dc.date) AS modif FROM documents dc)';
 if (($query=odbc_exec($odbc,$sql))&&odbc_fetch_row($query))
 {
  $dateLastModification=odbc_result($query,'lastmod');
  odbc_free_result($query);
  if (httpConditional($dateLastModification,1800,0,true,true)) //Private cache, 30 minutes and compression enabled
  {//No modification since the client’s last update
   odbc_close($odbc);
   header("Content-Type: application/rss+xml");
   exit();
  }
  //The global variable $clientCacheDate now contains the client’s last update date
 }
}
header('Content-Type: application/rss+xml');
echo '<','?xml version="1.0" encoding="UTF-8"?',">\n";
?>
<rdf:RDF xmlns="http://purl.org/rss/1.0/" xml:lang="en-GB"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
  <channel rdf:about="https://alexandre.alapetite.fr/blog/actu.en.html">
    <title>My RSS</title>
    <description>Description of my RSS</description>
    <link>https://alexandre.alapetite.fr/</link>
    <dc:language>en-GB</dc:language>
    <dc:date>2004-07-22</dc:date>
    <dc:creator>Alexandre Alapetite</dc:creator>
    <items>
      <rdf:Seq>
<?php
if ($odbc)
{
 /*
  Filter the articles:
  - Only the articles newer than $clientCacheDate (client’s last update)
  - Articles that are 30 days old at the maximum
  - Only the 20 most recent articles.
 */
 $clientDate=max($clientDate-60,time()-(30*86400));
 $sql='SELECT TOP 20 * FROM ('.
  'SELECT title,link,date,description FROM articles WHERE date>'.$clientDate.
  ' UNION SELECT title,link,date,description FROM news WHERE date>'.$clientDate.
  ' UNION SELECT title,link,date,description FROM documents WHERE date>'.$clientDate.
  ') ORDER BY date DESC';
 if (($query=odbc_exec($odbc,$sql))&&odbc_fetch_row($query,1))
 {
  odbc_fetch_row($query,0);
  while (odbc_fetch_row($query))
   echo "\t\t\t\t".'<rdf:li rdf:resource="https://alexandre.alapetite.fr'.odbc_result($query,'link').'"/>'."\n";
 }
}
?>
     </rdf:Seq>
    </items>
  </channel>

<?php
if ($odbc&&$query)
{
 odbc_fetch_row($query,0);
 while (odbc_fetch_row($query))
   echo "\t\t".'<item rdf:about="https://alexandre.alapetite.fr'.odbc_result($query,'link').'">'."\n",
    "\t\t\t".'<title>'.odbc_result($query,'title').'</title>'."\n",
    "\t\t\t".'<link>'.odbc_result($query,'link').'</link>'."\n",
    "\t\t\t".'<date>'.substr(date('Y-m-d\TH:i:sO',$myDate=odbc_result($query,'date')),0,22).':'.substr(date('O',$myDate),3).'</date>'."\n",
    "\t\t\t".'<description><![CDATA['.odbc_result($query,'description').']]></description>'."\n",
    "\t\t".'</item>'."\n";
 odbc_close($odbc);
}
?>
</rdf:RDF>

Utilisation with sessions

This library can be used together with sessions, by activating the session parameter. It then automatically checks if the data contained in $_SESSION has been modified since the call to this function, since the last generation of the document, using a MD5 hash code stored in the ETag HTTP header. The three modification cases that are checked detected:

  1. if $_SESSION has been modified after calling httpConditional() during the last generation of the document
  2. if $_SESSION has been modified from another document
  3. if $_SESSION has been modified before calling httpConditional() in the current execution

It is necessary to disable automatic generation of headers with session_cache_limiter(''); and/or session.cache_limiter='' in php.ini

example.php

<?php
 session_cache_limiter(''); //Disable automatic generation of header
 session_start(); //Start the session
 ...
 require_once('http-conditional.php');
 //Date of the last modification of the content (Unix Timestamp format)
 //Example: request form a database
 $dateLastModification=...;
 if (httpConditional($dateLastModification))
 {//No modification since the client’s version
  ... //Close the database, and other cleaning
  exit(); //No need to send anything else
 }
 //!\ Do not send any text to the client before this line
 ... //The rest of the script, just like if this first part was not used
?>

Meter of visitors

See the example of meter of visitors in PHP/HTTP on its dedicated page.


History

1.8.0 2016-08-07
Tolerate a `-gzip` suffix in Etag
More minor changes
1.6.2 2008-03-06
Correction and modification of the behaviour: when $cacheSeconds==0 (default value) a conditional revalidation against the server is requested at each access to the document; when $cacheSeconds<0 caching is disabled.
1.6.1 2005-04-03
Basic support of sessions
1.5 2005-04-01
Generation of the Content-Length header, when the compression paramater is activated, and that even for browsers not handling compression.
Correction: handling of the case when the browser sends an Accept-Encoding header not containing gzip nor deflate.
1.4 2005-02-27
added: function httpConditionalRefresh() to update HTTP headers if the content is modified by the client’s request
correction: Support of Internet Explorer, which does not follow standards regarding the value of If-Modified-Since
1.3.1 2004-11-23
Creative Commons Licence, French version "CC BY-SA (FR)"
1.3 2004-08-05
added: Mode RSS/Atom, compression and persistent connections
1.0 2004-07-26
Initial release

Licence

This content is protected by a licence Creative Commons Attribution-ShareAlike 2.0 France “BY-SA (FR)” [Creative Commons License]


Acknowledgements


Comments

object: View comments

https://alexandre.alapetite.fr

Back