App: regression/cache_file - Page: a_304_carries_the_etag_its_200_carries
PHP
a_304_carries_the_etag_its_200_carries.php
|
HTML
a_304_carries_the_etag_its_200_carries.pad
|
Result
|
<?php
// A 304 carries the ETag the 200 to the same request carries. The stable page, fetched at // a fresh address: the miss goes out as it was made, the hit gzipped as the cache keeps it, // with the tag of the gzip body ("...-gzip"), and asked again with that tag the answer is // a 304 with the same one - it said the identity tag, which a cache holding the gzip copy // may not take for the one it holds.
function tagOf ( $answer ) {
foreach ( $answer ['headers'] ?? [] as $name => $value ) if ( strtolower ( $name ) == 'etag' ) return trim ( $value );
return '';
}
$tagUrl = $padHost . 'regression/cache_file/?stable&fresh=' . bin2hex ( random_bytes ( 6 ) ); $tagMiss = padCurl ( $tagUrl ); $tagHit = padCurl ( $tagUrl ); $tagAsk = padCurl ( [ 'url' => $tagUrl, 'headers' => [ 'If-None-Match' => tagOf ( $tagHit ) ] ] );
$tagResult = 'miss: ' . ( str_ends_with ( tagOf ( $tagMiss ), '-gzip"' ) ? 'gzip tag' : 'plain tag' ) . ', hit: ' . ( str_ends_with ( tagOf ( $tagHit ), '-gzip"' ) ? 'gzip tag' : 'plain tag' ) . ', asked with it: ' . $tagAsk ['result'] . ' ' . ( tagOf ( $tagAsk ) === tagOf ( $tagHit ) ? 'the same tag' : 'another tag' );
?>
|
{meta cache=0}{$tagResult}
|
|
App: regression/cache_file - Page: a_public_answer_among_several_locales_varies_on_the_cookie
PHP
a_public_answer_among_several_locales_varies_on_the_cookie.php
|
HTML
a_public_answer_among_several_locales_varies_on_the_cookie.pad
|
Result
|
<?php
// A page a shared cache may keep, of an application with several $padLocales, says Vary: // Cookie on its public answer as well as Accept-Language: a padLang cookie chooses the // locale (lib/locale.php), and the answer to that visitor is private - but a shared cache // that kept the guest's English answer, stored under the address and Accept-Language // alone, handed it to the next visitor whose padLang=nl cookie chose Dutch, the server // never asked (RFC 9111 4.1: only the headers Vary names are matched). Apache joins the // Vary names with a comma, php -S with a comma and a blank: the blank is taken out before // they are compared.
$varyAnswer = padCurl ( [ 'url' => $padHost . 'regression/cache_file/?proxied&padInclude&fresh=' . bin2hex ( random_bytes ( 6 ) ), 'headers' => [ 'Accept-Language' => 'en' ] ] );
$varyControl = ''; $varyList = '';
foreach ( $varyAnswer ['headers'] ?? [] as $varyName => $varyValue ) if ( strtolower ( $varyName ) == 'cache-control' ) $varyControl = $varyValue; elseif ( strtolower ( $varyName ) == 'vary' ) $varyList = preg_replace ( '/\s*,\s*/', ',', $varyValue );
$varyResult = trim ( (string) ( $varyAnswer ['data'] ?? '' ) ) . ' ' . ( preg_match ( '/^(public|private)\b/', $varyControl, $varyMatch ) ? $varyMatch [1] : "'$varyControl'" ) . ', Vary: ' . $varyList;
?>
|
{meta cache=0}{$varyResult}
|
|
App: regression/cache_file - Page: a_public_answer_of_one_locale_varies_on_neither_the_language_nor_the_cookie
PHP
a_public_answer_of_one_locale_varies_on_neither_the_language_nor_the_cookie.php
|
HTML
a_public_answer_of_one_locale_varies_on_neither_the_language_nor_the_cookie.pad
|
Result
|
<?php
// A page a shared cache may keep, of an application with one locale in $padLocales: neither // Accept-Language nor a padLang cookie can choose another, so the answer to a visitor with // that cookie is public and Vary names neither - where Vary said Accept-Language, Cookie // and a shared cache kept a copy per Cookie header, and the padLang visitor was answered // private. The blank after Vary's commas is taken out, as Apache and php -S join them // differently.
$varyAnswer = padCurl ( [ 'url' => $padHost . 'regression/cache_file/?proxiedone&padInclude&fresh=' . bin2hex ( random_bytes ( 6 ) ), 'headers' => [ 'Accept-Language' => 'en', 'Cookie' => 'padLang=en' ] ] );
$varyControl = ''; $varyList = '';
foreach ( $varyAnswer ['headers'] ?? [] as $varyName => $varyValue ) if ( strtolower ( $varyName ) == 'cache-control' ) $varyControl = $varyValue; elseif ( strtolower ( $varyName ) == 'vary' ) $varyList = preg_replace ( '/\s*,\s*/', ',', $varyValue );
$varyResult = trim ( (string) ( $varyAnswer ['data'] ?? '' ) ) . ' ' . ( preg_match ( '/^(public|private)\b/', $varyControl, $varyMatch ) ? $varyMatch [1] : "'$varyControl'" ) . ', Vary: ' . $varyList;
?>
|
{meta cache=0}{$varyResult}
|
|
App: regression/cache_file - Page: a_push_in_a_nocache_part_is_made_on_every_fetch
PHP
a_push_in_a_nocache_part_is_made_on_every_fetch.php
|
HTML
a_push_in_a_nocache_part_is_made_on_every_fetch.pad
|
Result
|
<?php
// A {push} inside a {nocache} part of a page the cache would keep pushes what this fetch // renders: nocachepush, fetched twice at a fresh address, prints a new part and a new push // both times. The page was kept with its stack filled in - the part rendered anew on every // hit, its push showed the first fetch's text for the entry's whole life.
$pushUrl = $padHost . 'regression/cache_file/?nocachepush&padInclude&fresh=' . bin2hex ( random_bytes ( 6 ) );
preg_match ( '/<b>(\w+)<\/b>.*\[(\w+)\]/s', padCurl ( $pushUrl ) ['data'], $pushOne ); preg_match ( '/<b>(\w+)<\/b>.*\[(\w+)\]/s', padCurl ( $pushUrl ) ['data'], $pushTwo );
$pushResult = 'part new: ' . ( ( $pushOne [2] ?? 1 ) !== ( $pushTwo [2] ?? 1 ) ? 'yes' : 'no' ) . ', push new: ' . ( ( $pushOne [1] ?? 1 ) !== ( $pushTwo [1] ?? 1 ) ? 'yes' : 'no' );
?>
|
{meta cache=0}{$pushResult}
|
|
App: regression/cache_file - Page: a_visitor_with_an_identity_is_never_answered_public
PHP
a_visitor_with_an_identity_is_never_answered_public.php
|
HTML
a_visitor_with_an_identity_is_never_answered_public.pad
|
Result
|
<?php
// A page a shared cache may keep ($padCacheProxyAge) is public to a guest only. Asked with // a cookie of the visitor's own, or with an Authorization header, it is that visitor's - // private, as the page cache already treats such a request: it went out public with an // s-maxage, and a proxy kept "Hello Alice" for everyone after her (RFC 9111 3.5 lets a // shared cache reuse an answer to an Authorization request that says public). So is the // answer whose locale a padLang cookie chose: a shared cache keys it on the address and the // Accept-Language alone, and handed the Dutch page to the next English visitor.
function proxyType ( $extra ) {
global $padHost;
$answer = padCurl ( [ 'url' => $padHost . 'regression/cache_file/?proxied&padInclude&fresh=' . bin2hex ( random_bytes ( 6 ) ) ] + $extra );
$control = '';
foreach ( $answer ['headers'] ?? [] as $name => $value ) if ( strtolower ( $name ) == 'cache-control' ) $control = $value;
return trim ( $answer ['data'] ) . ' ' . ( preg_match ( '/^(public|private)\b/', $control, $match ) ? $match [1] : "'$control'" );
}
$proxyResult = 'guest: ' . proxyType ( [] ) . ', cookie: ' . proxyType ( [ 'cookies' => [ 'viewer' => 'Alice' ] ] ) . ', authorization: ' . proxyType ( [ 'headers' => [ 'Authorization' => 'Basic ' . base64_encode ( 'Alice:secret' ) ] ] ) . ', language cookie: ' . proxyType ( [ 'cookies' => [ 'padLang' => 'nl' ], 'headers' => [ 'Accept-Language' => 'en' ] ] );
?>
|
{meta cache=0}{$proxyResult}
|
|
App: regression/cache_file - Page: an_exposing_page_says_vary_accept_on_every_fetch
PHP
an_exposing_page_says_vary_accept_on_every_fetch.php
|
HTML
an_exposing_page_says_vary_accept_on_every_fetch.pad
|
Result
|
<?php
// A page that exposes its data answers JSON to an Accept header that asks for it, so every // answer of it says Vary: Accept - the second fetch too. The page cache kept it, and a hit, // which runs no page PHP, said Vary: Accept-Encoding only: a browser holding the HTML // answered a fetch of the page's JSON with it. Apache joins the Vary names with a comma, // php -S with a comma and a blank: the blank is taken out before they are compared.
$exposedUrl = $padHost . 'regression/cache_file/?exposed&padInclude&fresh=' . bin2hex ( random_bytes ( 6 ) );
function exposedVary ( $url ) {
foreach ( padCurl ( $url ) ['headers'] ?? [] as $name => $value ) if ( strtolower ( $name ) == 'vary' ) return preg_replace ( '/\s*,\s*/', ',', $value );
return '-';
}
$exposedResult = 'first: ' . exposedVary ( $exposedUrl ) . ', second: ' . exposedVary ( $exposedUrl );
?>
|
{meta cache=0}{$exposedResult}
|
|
App: regression/cache_file - Page: debug
PHP
debug.php
|
HTML
debug.pad
|
Result
|
<?php
// A page that shows this machine something it shows nobody else - a {debug} - is not // stored: built for a local request it went into the cache, and every visitor after it // was answered with the value {debug} keeps from them. The second fetch says it was // forwarded, as a visitor's request through a proxy on this machine does. The address is // new on every run: the visitor's own copy, rightly stored, would answer the next run's // local fetch.
$url = $padHost . 'regression/cache_file/?debugged&padInclude&debug=' . padRandomString ();
$local = padCurl ( $url ); $remote = padCurl ( [ 'url' => $url, 'headers' => [ 'X-Forwarded-For' => '203.0.113.5' ] ] );
$debugResult = 'local sees it: ' . ( str_contains ( $local ['data'], 'for this machine only' ) ? 'yes' : 'no' ) . ', visitor sees it: ' . ( str_contains ( $remote ['data'], 'for this machine only' ) ? 'YES' : 'no' );
?>
|
{$debugResult}
|
|
App: regression/cache_file - Page: debugged
PHP
debugged.php
|
HTML
debugged.pad
|
Result
|
<?php
// The fixture of debug: a page with a {debug} in it, which shows its value to this // machine's own requests only.
$debugValue = 'for this machine only';
?>
|
<p>page</p>{debug $debugValue}
|
|
App: regression/cache_file - Page: etaglist
PHP
etaglist.php
|
HTML
etaglist.pad
|
Result
|
<?php
// On a cache miss the 304 decision reads the whole If-None-Match list: the stable page, // fetched at a fresh address each time so the cache never holds it, answers 304 to a list // holding its tag second, and to *, and 200 to a tag it does not have. Only the first tag // of the list was compared there - the cache's own path already read them all.
function etagAsk ( $tags ) {
global $padHost;
$input = [ 'url' => $padHost . 'regression/cache_file/?stable&fresh=' . bin2hex ( random_bytes ( 6 ) ) ];
if ( $tags !== '' ) $input ['headers'] = [ 'If-None-Match' => $tags ];
return padCurl ( $input );
}
$etagFirst = etagAsk ( '' ); $etagTag = '';
foreach ( $etagFirst ['headers'] ?? [] as $etagName => $etagValue ) if ( strtolower ( $etagName ) == 'etag' ) $etagTag = trim ( $etagValue );
$etagResult = 'tag: ' . ( $etagTag !== '' ? 'sent' : 'none' ) . ' | list: ' . etagAsk ( '"AAAAAAAAAAAAAAAAAAAAAA", ' . $etagTag ) ['result'] . ' | star: ' . etagAsk ( '*' ) ['result'] . ' | other: ' . etagAsk ( '"AAAAAAAAAAAAAAAAAAAAAA"' ) ['result'];
?>
|
{$etagResult}
|
|
App: regression/cache_file - Page: exposed
PHP
exposed.php
|
HTML
exposed.pad
|
Result
|
<?php
// A page that exposes its data (lib/expose.php): its answer depends on the Accept header.
$rows = [ [ 'n' => 1 ], [ 'n' => 2 ] ]; $padExpose = [ 'rows' ];
?>
|
{rows}{$n} {/rows}
|
|
App: regression/cache_file - Page: flush
PHP
flush.php
|
HTML
flush.pad
|
Result
|
<?php
// A flushed page goes without an ETag - {flush} gives the ETag up with the whole body it // describes - where the headers that went out with the first part carried Etag: "", one // tag for every flushed page of the application.
$flushAnswer = padCurl ( $padHost . 'regression/cache_file/?flushed&padInclude&flush=' . padRandomString () );
$flushResult = 'body: ' . trim ( $flushAnswer ['data'] ) . ', etag: ' . ( array_key_exists ( 'Etag', $flushAnswer ['headers'] ) || array_key_exists ( 'ETag', $flushAnswer ['headers'] ) ? 'sent' : 'none' );
?>
|
{!flushResult}
|
|
App: regression/cache_file - Page: flushed
PHP
|
HTML
flushed.pad
|
Result
|
|
<p>head</p>{flush}<p>rest</p>
|
|
App: regression/cache_file - Page: fragment
PHP
fragment.php
|
HTML
fragment.pad
|
Result
|
<?php
// A page whose PHP picks a response fragment - from an HX-Request header, which the page // cache's key does not hold - is answered fresh for such a request and its fragment is // never stored: the HTMX swap went into the cache under the page's address, and every // browser after it got the bare fragment for the whole page; or the stored whole page // went into the swap. Two runs in a row each see both answers right; the address is new // on every run.
$url = $padHost . 'regression/cache_file/?fragmented&padInclude&fragment=' . padRandomString ();
$swap = padCurl ( [ 'url' => $url, 'headers' => [ 'HX-Request' => 'true' ] ] ); $page = padCurl ( $url ); $swap2 = padCurl ( [ 'url' => $url, 'headers' => [ 'HX-Request' => 'true' ] ] );
$fragmentResult = 'swap: ' . trim ( $swap ['data'] ) . ' | page: ' . trim ( $page ['data'] ) . ' | swap again: ' . trim ( $swap2 ['data'] );
?>
|
{!fragmentResult}
|
|
App: regression/cache_file - Page: fragmented
PHP
fragmented.php
|
HTML
fragmented.pad
|
Result
|
<?php
// The fixture of fragment: a page that answers its fragment alone to an HTMX request - the // HX-Request header - and the whole page to everyone else, as lib/respond.php suggests.
if ( isset ( $_SERVER ['HTTP_HX_REQUEST'] ) ) $padFragmentOnly = 'part';
?>
|
<p>full</p>{fragment 'part'}<p>part</p>{/fragment}
|
|
App: regression/cache_file - Page: guarded
PHP
guarded.php
|
HTML
guarded.pad
|
Result
|
<?php
// A page under a directory guard stays out of the page cache: a hit is answered before // any guard runs, so a cached copy would go to whoever asked, whatever the guard says. The // guard here wants a header, and a request with it gets a fresh build each time while one // without it is refused - it was given the copy the first request left in the cache.
$url = $padHost . 'regression/cache_file/?locked/probe&padInclude&guarded';
$keyOne = padCurl ( [ 'url' => $url, 'headers' => [ 'X-Key' => 'open' ] ] ); $keyTwo = padCurl ( [ 'url' => $url, 'headers' => [ 'X-Key' => 'open' ] ] ); $noKey = padCurl ( $url );
$guardedResult = 'built fresh: ' . ( $keyOne ['data'] !== $keyTwo ['data'] ? 'yes' : 'NO' ) . ', without the key: ' . $noKey ['result'];
?>
|
{$guardedResult}
|
|
|