MediaWiki  REL1_19
EhcacheBagOStuff.php
Go to the documentation of this file.
00001 <?php
00002 
00007 class EhcacheBagOStuff extends BagOStuff {
00008         var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions, 
00009                 $requestData, $requestDataPos;
00010         
00011         var $curls = array();
00012 
00013         function __construct( $params ) {
00014                 if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
00015                         throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' );
00016                 }
00017                 if ( !extension_loaded( 'zlib' ) ) {
00018                         throw new MWException( __CLASS__.' requires the zlib extension' );
00019                 }
00020                 if ( !isset( $params['servers'] ) ) {
00021                         throw new MWException( __METHOD__.': servers parameter is required' );
00022                 }
00023                 $this->servers = $params['servers'];
00024                 $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
00025                 $this->connectTimeout = isset( $params['connectTimeout'] ) 
00026                         ? $params['connectTimeout'] : 1;
00027                 $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
00028                 $this->curlOptions = array(
00029                         CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
00030                         CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
00031                         CURLOPT_RETURNTRANSFER => 1,
00032                         CURLOPT_CUSTOMREQUEST => 'GET',
00033                         CURLOPT_POST => 0,
00034                         CURLOPT_POSTFIELDS => '',
00035                         CURLOPT_HTTPHEADER => array(),
00036                 );
00037         }
00038 
00039         public function get( $key ) {
00040                 wfProfileIn( __METHOD__ );
00041                 $response = $this->doItemRequest( $key );
00042                 if ( !$response || $response['http_code'] == 404 ) {
00043                         wfProfileOut( __METHOD__ );
00044                         return false;
00045                 }
00046                 if ( $response['http_code'] >= 300 ) {
00047                         wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" );
00048                         wfProfileOut( __METHOD__ );
00049                         return false;                   
00050                 }
00051                 $body = $response['body'];
00052                 $type = $response['content_type'];
00053                 if ( $type == 'application/vnd.php.serialized+deflate' ) {
00054                         $body = gzinflate( $body );
00055                         if ( !$body ) {
00056                                 wfDebug( __METHOD__.": error inflating $key\n" );
00057                                 wfProfileOut( __METHOD__ );
00058                                 return false;
00059                         }
00060                         $data = unserialize( $body );
00061                 } elseif ( $type == 'application/vnd.php.serialized' ) {
00062                         $data = unserialize( $body );
00063                 } else {
00064                         wfDebug( __METHOD__.": unknown content type \"$type\"\n" );
00065                         wfProfileOut( __METHOD__ );
00066                         return false;
00067                 }
00068 
00069                 wfProfileOut( __METHOD__ );
00070                 return $data;
00071         }
00072 
00073         public function set( $key, $value, $expiry = 0 ) {
00074                 wfProfileIn( __METHOD__ );
00075                 $expiry = $this->convertExpiry( $expiry );
00076                 $ttl = $expiry ? $expiry - time() : 2147483647;
00077                 $blob = serialize( $value );
00078                 if ( strlen( $blob ) > 100 ) {
00079                         $blob = gzdeflate( $blob );
00080                         $contentType = 'application/vnd.php.serialized+deflate';
00081                 } else {
00082                         $contentType = 'application/vnd.php.serialized';
00083                 }
00084 
00085                 $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00086 
00087                 if ( $code == 404 ) {
00088                         // Maybe the cache does not exist yet, let's try creating it
00089                         if ( !$this->createCache( $key ) ) {
00090                                 wfDebug( __METHOD__.": cache creation failed\n" );
00091                                 wfProfileOut( __METHOD__ );
00092                                 return false;
00093                         }
00094                         $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00095                 }
00096 
00097                 $result = false;
00098                 if ( !$code ) {
00099                         wfDebug( __METHOD__.": PUT failure for key $key\n" );
00100                 } elseif ( $code >= 300 ) {
00101                         wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" );
00102                 } else {
00103                         $result = true;
00104                 }
00105 
00106                 wfProfileOut( __METHOD__ );
00107                 return $result;
00108         }
00109 
00110         public function delete( $key, $time = 0 ) {
00111                 wfProfileIn( __METHOD__ );
00112                 $response = $this->doItemRequest( $key,
00113                         array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
00114                 $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
00115                 if ( !$response || ( $code != 404 && $code >= 300 ) ) {
00116                         wfDebug( __METHOD__.": DELETE failure for key $key\n" );
00117                         $result = false;
00118                 } else {
00119                         $result = true;
00120                 }
00121                 wfProfileOut( __METHOD__ );
00122                 return $result;
00123         }
00124 
00125         protected function getCacheUrl( $key ) {
00126                 if ( count( $this->servers ) == 1 ) {
00127                         $server = reset( $this->servers );
00128                 } else {
00129                         // Use consistent hashing
00130                         $hashes = array();
00131                         foreach ( $this->servers as $server ) {
00132                                 $hashes[$server] = md5( $server . '/' . $key );
00133                         }
00134                         asort( $hashes );
00135                         reset( $hashes );
00136                         $server = key( $hashes );
00137                 }
00138                 return "http://$server/ehcache/rest/{$this->cacheName}";
00139         }
00140 
00145         protected function getCurl( $cacheUrl ) {
00146                 if ( !isset( $this->curls[$cacheUrl] ) ) {
00147                         $this->curls[$cacheUrl] = curl_init();
00148                 }
00149                 return $this->curls[$cacheUrl];
00150         }
00151 
00152         protected function attemptPut( $key, $data, $type, $ttl ) {
00153                 // In initial benchmarking, it was 30 times faster to use CURLOPT_POST 
00154                 // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
00155                 // CURLOPT_UPLOAD was pushing the request headers first, then waiting 
00156                 // for an ACK packet, then sending the data, whereas CURLOPT_POST just
00157                 // sends the headers and the data in a single send().
00158                 $response = $this->doItemRequest( $key,
00159                         array(
00160                                 CURLOPT_POST => 1,
00161                                 CURLOPT_CUSTOMREQUEST => 'PUT',
00162                                 CURLOPT_POSTFIELDS => $data,
00163                                 CURLOPT_HTTPHEADER => array(
00164                                         'Content-Type: ' . $type,
00165                                         'ehcacheTimeToLiveSeconds: ' . $ttl
00166                                 )
00167                         )
00168                 );
00169                 if ( !$response ) {
00170                         return 0;
00171                 } else {
00172                         return $response['http_code'];
00173                 }
00174         }
00175 
00176         protected function createCache( $key ) {
00177                 wfDebug( __METHOD__.": creating cache for $key\n" );
00178                 $response = $this->doCacheRequest( $key, 
00179                         array(
00180                                 CURLOPT_POST => 1,
00181                                 CURLOPT_CUSTOMREQUEST => 'PUT',
00182                                 CURLOPT_POSTFIELDS => '',
00183                         ) );
00184                 if ( !$response ) {
00185                         wfDebug( __CLASS__.": failed to create cache for $key\n" );
00186                         return false;
00187                 }
00188                 if ( $response['http_code'] == 201 /* created */ 
00189                         || $response['http_code'] == 409 /* already there */ ) 
00190                 {
00191                         return true;
00192                 } else {
00193                         return false;
00194                 }                       
00195         }
00196 
00197         protected function doCacheRequest( $key, $curlOptions = array() ) {
00198                 $cacheUrl = $this->getCacheUrl( $key );
00199                 $curl = $this->getCurl( $cacheUrl );
00200                 return $this->doRequest( $curl, $cacheUrl, $curlOptions );
00201         }
00202 
00203         protected function doItemRequest( $key, $curlOptions = array() ) {
00204                 $cacheUrl = $this->getCacheUrl( $key );
00205                 $curl = $this->getCurl( $cacheUrl );
00206                 $url = $cacheUrl . '/' . rawurlencode( $key );
00207                 return $this->doRequest( $curl, $url, $curlOptions );
00208         }
00209 
00210         protected function doRequest( $curl, $url, $curlOptions = array() ) {
00211                 if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
00212                         // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
00213                         throw new MWException( __METHOD__.": to prevent options set in one doRequest() " .
00214                                 "call from affecting subsequent doRequest() calls, only options listed " . 
00215                                 "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
00216                 }
00217                 $curlOptions += $this->curlOptions;
00218                 $curlOptions[CURLOPT_URL] = $url;
00219 
00220                 curl_setopt_array( $curl, $curlOptions );
00221                 $result = curl_exec( $curl );
00222                 if ( $result === false ) {
00223                         wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" );
00224                         return false;
00225                 }
00226                 $info = curl_getinfo( $curl );
00227                 $info['body'] = $result;
00228                 return $info;
00229         }
00230 }