Appendix C: Extra Material

This appendix contains code needed for some exercises.

ajax.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script type="text/javascript"
            src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js">
        </script>
        <script type="text/javascript">
            function getNonMasqueraded()
            {
                $("#result").load( "http://www.google.com/robots.txt" );
            }

            function getMasqueraded()
            {
                $("#result").load( "/masq/robots.txt" );
            }
        </script>
    </head>
    <body>
        <h1>Cross-domain Ajax</h1>
        <ul>
            <li><a href="javascript:getNonMasqueraded();">
                Test a non masqueraded cross-domain request
            </a></li>
            <li><a href="javascript:getMasqueraded();">
                Test a masqueraded cross-domain request
            </a></li>
        </ul>

        <h1>Result</h1>
        <div id="result"></div>
    </body>
</html>

article.php

<?php
header("Cache-Control: max-age=10");
$utc = new DateTimeZone("UTC");
$date = new DateTime("now", $utc);
$now = $date->format( DateTime::RFC2822 );
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head></head>
    <body>
        <h1>This article is cached for 10 seconds</h1>

        <h2>Cache timestamp: <?php echo $now; ?></h2>
        <a href="<?=$_SERVER['PHP_SELF']?>">Refresh this page</a>
    </body>
</html>

cookies.php

<?php
header( 'Content-Type: text/plain' );

print( "The following cookies have been received from the server\n" );

foreach( $_COOKIE as $name => $value )
    print( "- ${name} : ${value}\n" );
?>

esi-top.php

<?php
header('Content-Type: text/html');
header('Cache-Control: max-age=30, s-maxage=3600');
$utc = new DateTimeZone("UTC");
$date = new DateTime("now", $utc);
$now = $date->format( DateTime::RFC2822 );
$setc = "";
if( isset($_POST['k']) and $_POST['k'] !== '' and
    isset($_POST['v']) and $_POST['v'] !== '') {
        $k=$_POST['k'];
        $v=$_POST['v'];
        $setc = "Set-Cookie: $k=$v";

         header("$setc");
         ?><meta http-equiv="refresh" content="1" />
         <h1>Refreshing to set cookie <?php print $setc; ?></h1><?php
}
?>
<html><head><title>ESI top page</title></head><body><h1>ESI Test page</h1>
<p>This is content on the top-page of the ESI page.
The top page is cached for 1 hour in Varnish,
but only 30 seconds on the client.</p>
<p>The time when the top-element was created:</p><h3>

<?php echo "$now"; ?>

<h1>Set a cookie:</h1><form action="/esi-top.php" method="POST">
Key: <input type="text" name="k">
Value: <input type="text" name="v">
<input type="submit"> </form>

</h3><p>The top page received the following Cookies:</p><ul>

<?php

foreach( $_COOKIE as $name => $value )
    print( "<li>${name} : ${value}</li>\n" );
?>

<table border="1"><tr><td><esi:include src="/esi-user.php" /></td></tr>
</table></body></html>

esi-user.php

<?php
header('Content-Type: text/html');
header('Cache-Control: max-age=30, s-maxage=20');
header('Vary: Cookie');
$utc = new DateTimeZone("UTC");
$date = new DateTime("now", $utc);
$now = $date->format( DateTime::RFC2822 );
?>
<p>This is content on the user-specific ESI-include. This part of
the page is cached in Varnish separately since it emits
a "Vary: Cookie"-header. We can not affect the client-cache of
this sub-page, since that is determined by the cache-control
headers on the top-element.</p>
<p>The time when the user-specific-element was created:</p><h3>

<?php echo "$now"; ?>


</h3><p>The user-specific page received the following Cookies:
</p><ul>

<?php

foreach( $_COOKIE as $name => $value )
    print( "<li>${name} : ${value}</li>\n" );
?>

</ul>

httpheadersexample.php

<?php
date_default_timezone_set('UTC');
define( 'LAST_MODIFIED_STRING', 'Sat, 09 Sep 2000 22:00:00 GMT' );

// expires_date : 10s after page generation
$utc = new DateTimeZone("UTC");
$expires_date = new DateTime("now", $utc);
$expires_date->add(new DateInterval('PT10S'));

$headers = array(
    'Date' => date( 'D, d M Y H:i:s', time() ),
);

if( isset( $_GET['h'] ) and $_GET['h'] !== '' )
{
    switch( $_GET['h'] )
    {
        case "expires" :
            $headers['Expires'] = toUTCDate($expires_date);
        break;

        case "cache-control":
            $headers['Cache-Control'] = "public, must-revalidate,
            max-age=3600, s-maxage=3600";
        break;

        case "cache-control-override":
            $headers['Expires'] = toUTCDate($expires_date);
            $headers['Cache-Control'] = "public, must-revalidate,
            max-age=2, s-maxage=2";
        break;

        case "last-modified":
            $headers['Last-Modified'] = LAST_MODIFIED_STRING;
            $headers['Etag'] = md5( 12345 );

            if( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) and
                $_SERVER['HTTP_IF_MODIFIED_SINCE'] == LAST_MODIFIED_STRING ) {
                header( "HTTP/1.1 304 Not Modified" );
                exit(  );
            }
        break;

        case "vary":
            $headers['Expires'] = toUTCDate($expires_date);
            $headers['Vary'] = 'User-Agent';
        break;
    }

    sendHeaders( $headers );
}

function sendHeaders( array $headerList )
{
    foreach( $headerList as $name => $value )
    {
        header( "${name}: ${value}" );
    }
}

function toUTCDate( DateTime $date )
{
    $date->setTimezone( new DateTimeZone( 'UTC' ) );
    return $date->format( 'D, d M Y H:i:s \G\M\T' );
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head></head>
    <body>
        <h1>Header fields sent:</h1>
        <?php
            foreach( $headers as $name => $value ) {
                print "<strong>${name}</strong>: ${value}<br/>";
            }

            if( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
                print "<strong>If-Modified-Since</strong> has been
                sent in the";
                print "request, value : " .
                $_SERVER['HTTP_IF_MODIFIED_SINCE'];
            }
        ?>
        <hr/>
        <h1>Links for testing</h1>
        <ol>
            <li><a href="<?=$_SERVER['PHP_SELF']?>?h=expires">
            Test Expires response header field</a></li>
            <li><a href="<?=$_SERVER['PHP_SELF']?>?h=cache-control">
            Test Cache-Control response header field</a></li>
            <li><a href="<?=$_SERVER['PHP_SELF']?>?h=cache-control-override">
            Test Cache-Control and Expires</a></li>
            <li><a href="<?=$_SERVER['PHP_SELF']?>?h=last-modified">
            Test Last-Modified/If-Modified-Since response header fields</a></li>
            <li><a href="<?=$_SERVER['PHP_SELF']?>?h=vary">
            Test Vary response header field</a></li>
        <ol>
    </body>
</html>

purgearticle.php

<?php
header( 'Content-Type: text/plain' );
header( 'Cache-Control: max-age=0' );
$hostname = 'localhost';
$port     = 80;
$URL      = '/article.php';
$debug    = true;

print "Updating the article in the database ...\n";
purgeURL( $hostname, $port, $URL, $debug );

function purgeURL( $hostname, $port, $purgeURL, $debug )
{
    $finalURL = sprintf(
        "http://%s:%d%s", $hostname, $port, $purgeURL
    );

    print( "Purging ${finalURL}\n" );

    $curlOptionList = array(
        CURLOPT_RETURNTRANSFER    => true,
        CURLOPT_CUSTOMREQUEST     => 'PURGE',
        CURLOPT_HEADER            => true ,
        CURLOPT_NOBODY            => true,
        CURLOPT_URL               => $finalURL,
        CURLOPT_CONNECTTIMEOUT_MS => 2000
    );

    $fd = false;
    if( $debug == true ) {
        print "\n---- Curl debug -----\n";
        $fd = fopen("php://output", 'w+');
        $curlOptionList[CURLOPT_VERBOSE] = true;
        $curlOptionList[CURLOPT_STDERR]  = $fd;
    }

    $curlHandler = curl_init();
    curl_setopt_array( $curlHandler, $curlOptionList );
    curl_exec( $curlHandler );
    curl_close( $curlHandler );
    if( $fd !== false ) {
        fclose( $fd );
    }
}
?>

test.php

<?php
$cc = "";
if( isset($_GET['k']) and $_GET['k'] !== '' and
    isset($_GET['v']) and $_GET['v'] !== '') {
        $k=$_GET['k'];
        $v=$_GET['v'];
        $cc = "Cache-Control: $k=$v";

        header("$cc");

}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head></head>
    <body>
        <h1>Cache-Control Header:</h1>
        <?php
                print "<pre>$cc</pre>\n";
        ?>
        <hr/>
        <h1>Links for testing</h1>
        <form action="/test.php" method="GET">
        Key: <input type="text" name="k">
        Value: <input type="text" name="v">
        <input type="submit">
        </form>
    </body>
</html>

VCL Migrator from Varnish 3 to Varnish 4

The script aims to replace most of the syntactical changes in VCL code from Varnish 3 to Varnish 4, but it is not exhaustive. That said, you should use it under your own responsibility.

You can download the script from https://github.com/fgsch/varnish3to4. Usage and up-to-date details about the script is at the same web address.