MediaWiki  REL1_20
EhcacheBagOStuff.php
Go to the documentation of this file.
00001 <?php
00030 class EhcacheBagOStuff extends BagOStuff {
00031         var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions, 
00032                 $requestData, $requestDataPos;
00033         
00034         var $curls = array();
00035 
00039         function __construct( $params ) {
00040                 if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
00041                         throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' );
00042                 }
00043                 if ( !extension_loaded( 'zlib' ) ) {
00044                         throw new MWException( __CLASS__.' requires the zlib extension' );
00045                 }
00046                 if ( !isset( $params['servers'] ) ) {
00047                         throw new MWException( __METHOD__.': servers parameter is required' );
00048                 }
00049                 $this->servers = $params['servers'];
00050                 $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
00051                 $this->connectTimeout = isset( $params['connectTimeout'] ) 
00052                         ? $params['connectTimeout'] : 1;
00053                 $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
00054                 $this->curlOptions = array(
00055                         CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
00056                         CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
00057                         CURLOPT_RETURNTRANSFER => 1,
00058                         CURLOPT_CUSTOMREQUEST => 'GET',
00059                         CURLOPT_POST => 0,
00060                         CURLOPT_POSTFIELDS => '',
00061                         CURLOPT_HTTPHEADER => array(),
00062                 );
00063         }
00064 
00069         public function get( $key ) {
00070                 wfProfileIn( __METHOD__ );
00071                 $response = $this->doItemRequest( $key );
00072                 if ( !$response || $response['http_code'] == 404 ) {
00073                         wfProfileOut( __METHOD__ );
00074                         return false;
00075                 }
00076                 if ( $response['http_code'] >= 300 ) {
00077                         wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" );
00078                         wfProfileOut( __METHOD__ );
00079                         return false;                   
00080                 }
00081                 $body = $response['body'];
00082                 $type = $response['content_type'];
00083                 if ( $type == 'application/vnd.php.serialized+deflate' ) {
00084                         $body = gzinflate( $body );
00085                         if ( !$body ) {
00086                                 wfDebug( __METHOD__.": error inflating $key\n" );
00087                                 wfProfileOut( __METHOD__ );
00088                                 return false;
00089                         }
00090                         $data = unserialize( $body );
00091                 } elseif ( $type == 'application/vnd.php.serialized' ) {
00092                         $data = unserialize( $body );
00093                 } else {
00094                         wfDebug( __METHOD__.": unknown content type \"$type\"\n" );
00095                         wfProfileOut( __METHOD__ );
00096                         return false;
00097                 }
00098 
00099                 wfProfileOut( __METHOD__ );
00100                 return $data;
00101         }
00102 
00109         public function set( $key, $value, $expiry = 0 ) {
00110                 wfProfileIn( __METHOD__ );
00111                 $expiry = $this->convertExpiry( $expiry );
00112                 $ttl = $expiry ? $expiry - time() : 2147483647;
00113                 $blob = serialize( $value );
00114                 if ( strlen( $blob ) > 100 ) {
00115                         $blob = gzdeflate( $blob );
00116                         $contentType = 'application/vnd.php.serialized+deflate';
00117                 } else {
00118                         $contentType = 'application/vnd.php.serialized';
00119                 }
00120 
00121                 $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00122 
00123                 if ( $code == 404 ) {
00124                         // Maybe the cache does not exist yet, let's try creating it
00125                         if ( !$this->createCache( $key ) ) {
00126                                 wfDebug( __METHOD__.": cache creation failed\n" );
00127                                 wfProfileOut( __METHOD__ );
00128                                 return false;
00129                         }
00130                         $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00131                 }
00132 
00133                 $result = false;
00134                 if ( !$code ) {
00135                         wfDebug( __METHOD__.": PUT failure for key $key\n" );
00136                 } elseif ( $code >= 300 ) {
00137                         wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" );
00138                 } else {
00139                         $result = true;
00140                 }
00141 
00142                 wfProfileOut( __METHOD__ );
00143                 return $result;
00144         }
00145 
00151         public function delete( $key, $time = 0 ) {
00152                 wfProfileIn( __METHOD__ );
00153                 $response = $this->doItemRequest( $key,
00154                         array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
00155                 $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
00156                 if ( !$response || ( $code != 404 && $code >= 300 ) ) {
00157                         wfDebug( __METHOD__.": DELETE failure for key $key\n" );
00158                         $result = false;
00159                 } else {
00160                         $result = true;
00161                 }
00162                 wfProfileOut( __METHOD__ );
00163                 return $result;
00164         }
00165 
00170         protected function getCacheUrl( $key ) {
00171                 if ( count( $this->servers ) == 1 ) {
00172                         $server = reset( $this->servers );
00173                 } else {
00174                         // Use consistent hashing
00175                         $hashes = array();
00176                         foreach ( $this->servers as $server ) {
00177                                 $hashes[$server] = md5( $server . '/' . $key );
00178                         }
00179                         asort( $hashes );
00180                         reset( $hashes );
00181                         $server = key( $hashes );
00182                 }
00183                 return "http://$server/ehcache/rest/{$this->cacheName}";
00184         }
00185 
00190         protected function getCurl( $cacheUrl ) {
00191                 if ( !isset( $this->curls[$cacheUrl] ) ) {
00192                         $this->curls[$cacheUrl] = curl_init();
00193                 }
00194                 return $this->curls[$cacheUrl];
00195         }
00196 
00204         protected function attemptPut( $key, $data, $type, $ttl ) {
00205                 // In initial benchmarking, it was 30 times faster to use CURLOPT_POST 
00206                 // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
00207                 // CURLOPT_UPLOAD was pushing the request headers first, then waiting 
00208                 // for an ACK packet, then sending the data, whereas CURLOPT_POST just
00209                 // sends the headers and the data in a single send().
00210                 $response = $this->doItemRequest( $key,
00211                         array(
00212                                 CURLOPT_POST => 1,
00213                                 CURLOPT_CUSTOMREQUEST => 'PUT',
00214                                 CURLOPT_POSTFIELDS => $data,
00215                                 CURLOPT_HTTPHEADER => array(
00216                                         'Content-Type: ' . $type,
00217                                         'ehcacheTimeToLiveSeconds: ' . $ttl
00218                                 )
00219                         )
00220                 );
00221                 if ( !$response ) {
00222                         return 0;
00223                 } else {
00224                         return $response['http_code'];
00225                 }
00226         }
00227 
00232         protected function createCache( $key ) {
00233                 wfDebug( __METHOD__.": creating cache for $key\n" );
00234                 $response = $this->doCacheRequest( $key, 
00235                         array(
00236                                 CURLOPT_POST => 1,
00237                                 CURLOPT_CUSTOMREQUEST => 'PUT',
00238                                 CURLOPT_POSTFIELDS => '',
00239                         ) );
00240                 if ( !$response ) {
00241                         wfDebug( __CLASS__.": failed to create cache for $key\n" );
00242                         return false;
00243                 }
00244                 return ( $response['http_code'] == 201 /* created */
00245                         || $response['http_code'] == 409 /* already there */ );
00246         }
00247 
00253         protected function doCacheRequest( $key, $curlOptions = array() ) {
00254                 $cacheUrl = $this->getCacheUrl( $key );
00255                 $curl = $this->getCurl( $cacheUrl );
00256                 return $this->doRequest( $curl, $cacheUrl, $curlOptions );
00257         }
00258 
00264         protected function doItemRequest( $key, $curlOptions = array() ) {
00265                 $cacheUrl = $this->getCacheUrl( $key );
00266                 $curl = $this->getCurl( $cacheUrl );
00267                 $url = $cacheUrl . '/' . rawurlencode( $key );
00268                 return $this->doRequest( $curl, $url, $curlOptions );
00269         }
00270 
00278         protected function doRequest( $curl, $url, $curlOptions = array() ) {
00279                 if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
00280                         // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
00281                         throw new MWException( __METHOD__.": to prevent options set in one doRequest() " .
00282                                 "call from affecting subsequent doRequest() calls, only options listed " . 
00283                                 "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
00284                 }
00285                 $curlOptions += $this->curlOptions;
00286                 $curlOptions[CURLOPT_URL] = $url;
00287 
00288                 curl_setopt_array( $curl, $curlOptions );
00289                 $result = curl_exec( $curl );
00290                 if ( $result === false ) {
00291                         wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" );
00292                         return false;
00293                 }
00294                 $info = curl_getinfo( $curl );
00295                 $info['body'] = $result;
00296                 return $info;
00297         }
00298 }