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