MediaWiki
REL1_20
|
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 }