From c6879704abf865e25554e9bd92b185f96097985d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sat, 2 Aug 2003 10:13:27 +0000 Subject: [PATCH] Move file cache functions into CacheManager class (in CacheManager.php) and add support for showing the cached version of a page if unable to contact the database. Also add 'Vary: Accept-Encoding' to headers for every output, just to make sure. --- includes/Article.php | 110 +++---------------------- includes/CacheManager.php | 142 +++++++++++++++++++++++++++++++++ includes/DatabaseFunctions.php | 59 +++++++++++--- includes/OutputPage.php | 2 + languages/Language.php | 3 +- 5 files changed, 205 insertions(+), 111 deletions(-) create mode 100644 includes/CacheManager.php diff --git a/includes/Article.php b/includes/Article.php index 7cbc9a59b2..580d75d60a 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1620,18 +1620,20 @@ $wgLang->recodeForEdit( $wpTextbox1 ) . return $text; } - /* Caching functions */ - function tryFileCache() { + function tryFileCache() { + global $wgTitle; + if($this->isFileCacheable()) { - if($this->isFileCacheGood()) { - wfDebug( " tryFileCache() - about to load\n" ); - $this->loadFromFileCache(); + $cache = new CacheManager( $wgTitle ); + if($cache->isFileCacheGood( $this->mTouched )) { + wfDebug( " tryFileCache() - about to load\n" ); + $cache->loadFromFileCache(); exit; } else { - wfDebug( " tryFileCache() - starting buffer\n" ); - ob_start( array(&$this, 'saveToFileCache' ) ); + wfDebug( " tryFileCache() - starting buffer\n" ); + ob_start( array(&$cache, 'saveToFileCache' ) ); } } else { wfDebug( " tryFileCache() - not cacheable\n" ); @@ -1655,100 +1657,6 @@ $wgLang->recodeForEdit( $wpTextbox1 ) . } - function fileCacheName() { - global $wgTitle, $wgFileCacheDirectory, $wgLang; - if( !$this->mFileCache ) { - $hash = md5( $key = $wgTitle->getDbkey() ); - if( $wgTitle->getNamespace() ) - $key = $wgLang->getNsText( $wgTitle->getNamespace() ) . ":" . $key; - $key = str_replace( ".", "%2E", urlencode( $key ) ); - $hash1 = substr( $hash, 0, 1 ); - $hash2 = substr( $hash, 0, 2 ); - $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html"; - wfDebug( " fileCacheName() - {$this->mFileCache}\n" ); - } - return $this->mFileCache; - } - - function isFileCacheGood() { - global $wgUser, $wgCacheEpoch; - if(!file_exists( $fn = $this->fileCacheName() ) ) return false; - $cachetime = wfUnix2Timestamp( filemtime( $fn ) ); - $good = (( $this->mTouched <= $cachetime ) && - ($wgCacheEpoch <= $cachetime )); - wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$this->mTouched} epoch {$wgCacheEpoch}, good $good\n"); - return $good; - } - - function loadFromFileCache() { - global $wgUseGzip, $wgOut; - wfDebug(" loadFromFileCache()\n"); - $filename=$this->fileCacheName(); - $filenamegz = "{$filename}.gz"; - $wgOut->sendCacheControl(); - if( $wgUseGzip - && wfClientAcceptsGzip() - && file_exists( $filenamegz) - && ( filemtime( $filenamegz ) >= filemtime( $filename ) ) ) { - wfDebug(" sending gzip\n"); - header( "Content-Encoding: gzip" ); - header( "Vary: Accept-Encoding" ); - $filename = $filenamegz; - } - readfile( $filename ); - } - - function saveToFileCache( $text ) { - global $wgUseGzip, $wgCompressByDefault; - if(strcmp($text,"") == 0) return ""; - - wfDebug(" saveToFileCache()\n", false); - $filename=$this->fileCacheName(); - $mydir2=substr($filename,0,strrpos($filename,"/")); # subdirectory level 2 - $mydir1=substr($mydir2,0,strrpos($mydir2,"/")); # subdirectory level 1 - if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary - if(!file_exists($mydir2)) { mkdir($mydir2,0775); } - - $f = fopen( $filename, "w" ); - if($f) { - $now = wfTimestampNow(); - fwrite( $f, str_replace( "", - "\n", - $text ) ); - fclose( $f ); - if( $wgUseGzip and $wgCompressByDefault ) { - $start = microtime(); - wfDebug(" saving gzip\n"); - $gzout = gzencode( str_replace( "", - "\n", - $text ) ); - if( $gzout === false ) { - wfDebug(" failed to gzip compress, sending plaintext\n"); - return $text; - } - if( $f = fopen( "{$filename}.gz", "w" ) ) { - fwrite( $f, $gzout ); - fclose( $f ); - $end = microtime(); - - list($usec1, $sec1) = explode(" ",$start); - list($usec2, $sec2) = explode(" ",$end); - $interval = ((float)$usec2 + (float)$sec2) - - ((float)$usec1 + (float)$sec1); - wfDebug(" saved gzip in $interval\n"); - } else { - wfDebug(" failed to write gzip, still sending\n" ); - } - if(wfClientAcceptsGzip()) { - header( "Content-Encoding: gzip" ); - header( "Vary: Accept-Encoding" ); - wfDebug(" sending NEW gzip now...\n" ); - return $gzout; - } - } - } - return $text; - } } diff --git a/includes/CacheManager.php b/includes/CacheManager.php new file mode 100644 index 0000000000..f42c530460 --- /dev/null +++ b/includes/CacheManager.php @@ -0,0 +1,142 @@ +mTitle =& $title; + $this->mFileCache = ""; + } + + function fileCacheName() { + global $wgFileCacheDirectory, $wgLang; + if( !$this->mFileCache ) { + $hash = md5( $key = $this->mTitle->getDbkey() ); + if( $this->mTitle->getNamespace() ) + $key = $wgLang->getNsText( $this->mTitle->getNamespace() ) . ":" . $key; + $key = str_replace( ".", "%2E", urlencode( $key ) ); + + $hash1 = substr( $hash, 0, 1 ); + $hash2 = substr( $hash, 0, 2 ); + $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html"; + + if($this->useGzip()) + $this->mFileCache .= ".gz"; + + wfDebug( " fileCacheName() - {$this->mFileCache}\n" ); + } + return $this->mFileCache; + } + + function isFileCached() { + return file_exists( $this->fileCacheName() ); + } + + function fileCacheTime() { + return wfUnix2Timestamp( filemtime( $this->fileCacheName() ) ); + } + + function isFileCacheGood( $timestamp ) { + global $wgUser, $wgCacheEpoch; + + if( !$this->isFileCached() ) return false; + + $cachetime = $this->fileCacheTime(); + $good = (( $timestamp <= $cachetime ) && + ( $wgCacheEpoch <= $cachetime )); + + wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$this->mTouched} epoch {$wgCacheEpoch}, good $good\n"); + return $good; + } + + function useGzip() { + global $wgUseGzip; + return $wgUseGzip; + } + + /* In handy string packages */ + function fetchRawText() { + return file_get_contents( $this->fileCacheName() ); + } + + function fetchPageText() { + if( $this->useGzip() ) { + /* Why is there no gzfile_get_contents() or gzdecode()? */ + return implode( "", gzfile( $this->fileCacheName() ) ); + } else { + return $this->fetchRawText(); + } + } + + /* Working directory to/from output */ + function loadFromFileCache() { + global $wgOut; + wfDebug(" loadFromFileCache()\n"); + + $filename=$this->fileCacheName(); + $wgOut->sendCacheControl(); + + if( $this->useGzip() ) { + if( wfClientAcceptsGzip() ) { + header( "Content-Encoding: gzip" ); + readfile( $filename ); + } else { + /* Send uncompressed */ + readgzfile( $filename ); + } + } else { + readfile( $filename ); + } + } + + function checkCacheDirs() { + $filename = $this->fileCacheName(); + $mydir2=substr($filename,0,strrpos($filename,"/")); # subdirectory level 2 + $mydir1=substr($mydir2,0,strrpos($mydir2,"/")); # subdirectory level 1 + + if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary + if(!file_exists($mydir2)) { mkdir($mydir2,0775); } + } + + function saveToFileCache( $text ) { + if(strcmp($text,"") == 0) return ""; + + wfDebug(" saveToFileCache()\n", false); + + $this->checkCacheDirs(); + + $f = fopen( $this->fileCacheName(), "w" ); + if($f) { + $now = wfTimestampNow(); + if( $this->useGzip() ) { + $rawtext = str_replace( "", + "\n", + $text ); + $text = gzencode( $rawtext ); + } else { + $text = str_replace( "", + "\n", + $text ); + } + fwrite( $f, $text ); + fclose( $f ); + if( $this->useGzip() ) { + if( wfClientAcceptsGzip() ) { + header( "Content-Encoding: gzip" ); + header( "Vary: Accept-Encoding" ); + return $text; + } else { + return $rawtext; + } + } else { + return $text; + } + } + return $text; + } + +} + +?> diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php index bcec86d45b..191a763237 100644 --- a/includes/DatabaseFunctions.php +++ b/includes/DatabaseFunctions.php @@ -1,6 +1,7 @@ " . htmlspecialchars(mysql_error()) . "

\n" . $helpme ); + @$wgDBconnection = mysql_pconnect( $wgDBserver, $wgDBuser, $wgDBpassword ) + or wfEmergencyAbort(); + if( !mysql_select_db( $wgDBname, $wgDBconnection ) ) { + /* Persistent connections may become stuck in an unusable state */ wfDebug( "Persistent connection is broken?\n", true ); - $wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser, - $wgDBpassword ) or die( $noconn . - "\n

" . htmlspecialchars(mysql_error()) . " (tried non-p connect)

\n" . $helpme ); - mysql_select_db( $wgDBname, $wgDBconnection ) or die( $nodb . - "\n

" . htmlspecialchars(mysql_error()) . " (tried non-p connect)

\n" . $helpme ); - } + @$wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser, $wgDBpassword ) + or wfEmergencyAbort(); + + @mysql_select_db( $wgDBname, $wgDBconnection ) + or wfEmergencyAbort(); + } } # mysql_ping( $wgDBconnection ); return $wgDBconnection; } +/* Call this function if we couldn't contact the database... + We'll try to use the cache to display something in the meantime */ +function wfEmergencyAbort( $msg = "" ) { + global $wgTitle, $wgUseFileCache, $title; + + if($msg == "") $msg = wfMsg( "noconnect" ); + $text = $msg; + + if($wgUseFileCache) { + if($wgTitle) { + $t =& $wgTitle; + } else { + if($title) { + $t = Title::newFromURL( $title ); + } else { + $t = Title::newFromText( wfMsg("mainpage") ); + } + } + + $cache = new CacheManager( $t ); + if( $cache->isFileCached() ) { + $msg = "

$msg
\n" . + wfMsg( "cachederror" ) . "

\n"; + + $tag = "
"; + $text = str_replace( + $tag, + $tag . $msg, + $cache->fetchPageText() ); + } + } + + /* Don't cache error pages! They cause no end of trouble... */ + header( "Cache-control: none" ); + header( "Pragma: nocache" ); + echo $text; + exit; +} + function wfQuery( $sql, $fname = "" ) { global $wgLastDatabaseQuery, $wgOut; diff --git a/includes/OutputPage.php b/includes/OutputPage.php index a75865ff15..7f1948affd 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -323,11 +323,13 @@ class OutputPage { if( $this->mLastModified != "" ) { wfDebug( "** private caching; {$this->mLastModified} **\n", false ); header( "Cache-Control: private, must-revalidate, max-age=0" ); + header( "Vary: Accept-Encoding" ); header( "Last-modified: {$this->mLastModified}" ); } else { wfDebug( "** no caching **\n", false ); header( "Cache-Control: no-cache" ); # Experimental - see below header( "Pragma: no-cache" ); + header( "Vary: Accept-Encoding" ); header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" ); } header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page! diff --git a/languages/Language.php b/languages/Language.php index 16f4d73671..3ae261e4fe 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -401,8 +401,9 @@ The last attempted database query was: \"$1\" from within function \"$2\". MySQL returned error \"$3: $4\".\n", -"noconnect" => "Could not connect to DB on $1", +"noconnect" => "Sorry! The wiki is experiencing some technical difficulties, and cannot contact the database server.", "nodb" => "Could not select database $1", +"cachederror" => "The following is a cached copy of the requested page, and may not be up to date.", "readonly" => "Database locked", "enterlockreason" => "Enter a reason for the lock, including an estimate of when the lock will be released", -- 2.20.1