MediaWiki
REL1_22
|
00001 <?php 00030 class EhcacheBagOStuff extends BagOStuff { 00031 var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions, 00032 $requestData, $requestDataPos; 00033 00034 var $curls = array(); 00035 00040 function __construct( $params ) { 00041 if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) { 00042 throw new MWException( __CLASS__ . ' requires curl version 7.16.2 or later.' ); 00043 } 00044 if ( !extension_loaded( 'zlib' ) ) { 00045 throw new MWException( __CLASS__ . ' requires the zlib extension' ); 00046 } 00047 if ( !isset( $params['servers'] ) ) { 00048 throw new MWException( __METHOD__ . ': servers parameter is required' ); 00049 } 00050 $this->servers = $params['servers']; 00051 $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw'; 00052 $this->connectTimeout = isset( $params['connectTimeout'] ) 00053 ? $params['connectTimeout'] : 1; 00054 $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1; 00055 $this->curlOptions = array( 00056 CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ), 00057 CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ), 00058 CURLOPT_RETURNTRANSFER => 1, 00059 CURLOPT_CUSTOMREQUEST => 'GET', 00060 CURLOPT_POST => 0, 00061 CURLOPT_POSTFIELDS => '', 00062 CURLOPT_HTTPHEADER => array(), 00063 ); 00064 } 00065 00071 public function get( $key, &$casToken = null ) { 00072 wfProfileIn( __METHOD__ ); 00073 $response = $this->doItemRequest( $key ); 00074 if ( !$response || $response['http_code'] == 404 ) { 00075 wfProfileOut( __METHOD__ ); 00076 return false; 00077 } 00078 if ( $response['http_code'] >= 300 ) { 00079 wfDebug( __METHOD__ . ": GET failure, got HTTP {$response['http_code']}\n" ); 00080 wfProfileOut( __METHOD__ ); 00081 return false; 00082 } 00083 $body = $response['body']; 00084 $type = $response['content_type']; 00085 if ( $type == 'application/vnd.php.serialized+deflate' ) { 00086 $body = gzinflate( $body ); 00087 if ( !$body ) { 00088 wfDebug( __METHOD__ . ": error inflating $key\n" ); 00089 wfProfileOut( __METHOD__ ); 00090 return false; 00091 } 00092 $data = unserialize( $body ); 00093 } elseif ( $type == 'application/vnd.php.serialized' ) { 00094 $data = unserialize( $body ); 00095 } else { 00096 wfDebug( __METHOD__ . ": unknown content type \"$type\"\n" ); 00097 wfProfileOut( __METHOD__ ); 00098 return false; 00099 } 00100 00101 $casToken = $body; 00102 00103 wfProfileOut( __METHOD__ ); 00104 return $data; 00105 } 00106 00113 public function set( $key, $value, $expiry = 0 ) { 00114 wfProfileIn( __METHOD__ ); 00115 $expiry = $this->convertExpiry( $expiry ); 00116 $ttl = $expiry ? $expiry - time() : 2147483647; 00117 $blob = serialize( $value ); 00118 if ( strlen( $blob ) > 100 ) { 00119 $blob = gzdeflate( $blob ); 00120 $contentType = 'application/vnd.php.serialized+deflate'; 00121 } else { 00122 $contentType = 'application/vnd.php.serialized'; 00123 } 00124 00125 $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); 00126 00127 if ( $code == 404 ) { 00128 // Maybe the cache does not exist yet, let's try creating it 00129 if ( !$this->createCache( $key ) ) { 00130 wfDebug( __METHOD__ . ": cache creation failed\n" ); 00131 wfProfileOut( __METHOD__ ); 00132 return false; 00133 } 00134 $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); 00135 } 00136 00137 $result = false; 00138 if ( !$code ) { 00139 wfDebug( __METHOD__ . ": PUT failure for key $key\n" ); 00140 } elseif ( $code >= 300 ) { 00141 wfDebug( __METHOD__ . ": PUT failure for key $key: HTTP $code\n" ); 00142 } else { 00143 $result = true; 00144 } 00145 00146 wfProfileOut( __METHOD__ ); 00147 return $result; 00148 } 00149 00157 public function cas( $casToken, $key, $value, $exptime = 0 ) { 00158 // Not sure if we can implement CAS for ehcache. There appears to be CAS-support per 00159 // http://ehcache.org/documentation/get-started/consistency-options#cas-cache-operations, 00160 // but I can't find any docs for our current implementation. 00161 throw new MWException( "CAS is not implemented in " . __CLASS__ ); 00162 } 00163 00169 public function delete( $key, $time = 0 ) { 00170 wfProfileIn( __METHOD__ ); 00171 $response = $this->doItemRequest( $key, 00172 array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) ); 00173 $code = isset( $response['http_code'] ) ? $response['http_code'] : 0; 00174 if ( !$response || ( $code != 404 && $code >= 300 ) ) { 00175 wfDebug( __METHOD__ . ": DELETE failure for key $key\n" ); 00176 $result = false; 00177 } else { 00178 $result = true; 00179 } 00180 wfProfileOut( __METHOD__ ); 00181 return $result; 00182 } 00183 00188 public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) { 00189 return $this->mergeViaLock( $key, $callback, $exptime, $attempts ); 00190 } 00191 00196 protected function getCacheUrl( $key ) { 00197 if ( count( $this->servers ) == 1 ) { 00198 $server = reset( $this->servers ); 00199 } else { 00200 // Use consistent hashing 00201 $hashes = array(); 00202 foreach ( $this->servers as $server ) { 00203 $hashes[$server] = md5( $server . '/' . $key ); 00204 } 00205 asort( $hashes ); 00206 reset( $hashes ); 00207 $server = key( $hashes ); 00208 } 00209 return "http://$server/ehcache/rest/{$this->cacheName}"; 00210 } 00211 00216 protected function getCurl( $cacheUrl ) { 00217 if ( !isset( $this->curls[$cacheUrl] ) ) { 00218 $this->curls[$cacheUrl] = curl_init(); 00219 } 00220 return $this->curls[$cacheUrl]; 00221 } 00222 00230 protected function attemptPut( $key, $data, $type, $ttl ) { 00231 // In initial benchmarking, it was 30 times faster to use CURLOPT_POST 00232 // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because 00233 // CURLOPT_UPLOAD was pushing the request headers first, then waiting 00234 // for an ACK packet, then sending the data, whereas CURLOPT_POST just 00235 // sends the headers and the data in a single send(). 00236 $response = $this->doItemRequest( $key, 00237 array( 00238 CURLOPT_POST => 1, 00239 CURLOPT_CUSTOMREQUEST => 'PUT', 00240 CURLOPT_POSTFIELDS => $data, 00241 CURLOPT_HTTPHEADER => array( 00242 'Content-Type: ' . $type, 00243 'ehcacheTimeToLiveSeconds: ' . $ttl 00244 ) 00245 ) 00246 ); 00247 if ( !$response ) { 00248 return 0; 00249 } else { 00250 return $response['http_code']; 00251 } 00252 } 00253 00258 protected function createCache( $key ) { 00259 wfDebug( __METHOD__ . ": creating cache for $key\n" ); 00260 $response = $this->doCacheRequest( $key, 00261 array( 00262 CURLOPT_POST => 1, 00263 CURLOPT_CUSTOMREQUEST => 'PUT', 00264 CURLOPT_POSTFIELDS => '', 00265 ) ); 00266 if ( !$response ) { 00267 wfDebug( __CLASS__ . ": failed to create cache for $key\n" ); 00268 return false; 00269 } 00270 return ( $response['http_code'] == 201 /* created */ 00271 || $response['http_code'] == 409 /* already there */ ); 00272 } 00273 00279 protected function doCacheRequest( $key, $curlOptions = array() ) { 00280 $cacheUrl = $this->getCacheUrl( $key ); 00281 $curl = $this->getCurl( $cacheUrl ); 00282 return $this->doRequest( $curl, $cacheUrl, $curlOptions ); 00283 } 00284 00290 protected function doItemRequest( $key, $curlOptions = array() ) { 00291 $cacheUrl = $this->getCacheUrl( $key ); 00292 $curl = $this->getCurl( $cacheUrl ); 00293 $url = $cacheUrl . '/' . rawurlencode( $key ); 00294 return $this->doRequest( $curl, $url, $curlOptions ); 00295 } 00296 00304 protected function doRequest( $curl, $url, $curlOptions = array() ) { 00305 if ( array_diff_key( $curlOptions, $this->curlOptions ) ) { 00306 // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) ); 00307 throw new MWException( __METHOD__ . ": to prevent options set in one doRequest() " . 00308 "call from affecting subsequent doRequest() calls, only options listed " . 00309 "in \$this->curlOptions may be specified in the \$curlOptions parameter." ); 00310 } 00311 $curlOptions += $this->curlOptions; 00312 $curlOptions[CURLOPT_URL] = $url; 00313 00314 curl_setopt_array( $curl, $curlOptions ); 00315 $result = curl_exec( $curl ); 00316 if ( $result === false ) { 00317 wfDebug( __CLASS__ . ": curl error: " . curl_error( $curl ) . "\n" ); 00318 return false; 00319 } 00320 $info = curl_getinfo( $curl ); 00321 $info['body'] = $result; 00322 return $info; 00323 } 00324 }