Localisation updates for core and extension messages from translatewiki.net
[mediawiki.git] / includes / objectcache / EhcacheBagOStuff.php
blob75aad27acb9b75c32aa1d1bdbdcdd2a6c8eb085e
1 <?php
3 /**
4 * Client for the Ehcache RESTful web service - http://ehcache.org/documentation/cache_server.html
5 * TODO: Simplify configuration and add to the installer.
6 */
7 class EhcacheBagOStuff extends BagOStuff {
8 var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
9 $requestData, $requestDataPos;
11 var $curls = array();
13 function __construct( $params ) {
14 if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
15 throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' );
17 if ( !extension_loaded( 'zlib' ) ) {
18 throw new MWException( __CLASS__.' requires the zlib extension' );
20 if ( !isset( $params['servers'] ) ) {
21 throw new MWException( __METHOD__.': servers parameter is required' );
23 $this->servers = $params['servers'];
24 $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
25 $this->connectTimeout = isset( $params['connectTimeout'] )
26 ? $params['connectTimeout'] : 1;
27 $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
28 $this->curlOptions = array(
29 CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
30 CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
31 CURLOPT_RETURNTRANSFER => 1,
32 CURLOPT_CUSTOMREQUEST => 'GET',
33 CURLOPT_POST => 0,
34 CURLOPT_POSTFIELDS => '',
35 CURLOPT_HTTPHEADER => array(),
39 public function get( $key ) {
40 wfProfileIn( __METHOD__ );
41 $response = $this->doItemRequest( $key );
42 if ( !$response || $response['http_code'] == 404 ) {
43 wfProfileOut( __METHOD__ );
44 return false;
46 if ( $response['http_code'] >= 300 ) {
47 wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" );
48 wfProfileOut( __METHOD__ );
49 return false;
51 $body = $response['body'];
52 $type = $response['content_type'];
53 if ( $type == 'application/vnd.php.serialized+deflate' ) {
54 $body = gzinflate( $body );
55 if ( !$body ) {
56 wfDebug( __METHOD__.": error inflating $key\n" );
57 wfProfileOut( __METHOD__ );
58 return false;
60 $data = unserialize( $body );
61 } elseif ( $type == 'application/vnd.php.serialized' ) {
62 $data = unserialize( $body );
63 } else {
64 wfDebug( __METHOD__.": unknown content type \"$type\"\n" );
65 wfProfileOut( __METHOD__ );
66 return false;
69 wfProfileOut( __METHOD__ );
70 return $data;
73 public function set( $key, $value, $expiry = 0 ) {
74 wfProfileIn( __METHOD__ );
75 $expiry = $this->convertExpiry( $expiry );
76 $ttl = $expiry ? $expiry - time() : 2147483647;
77 $blob = serialize( $value );
78 if ( strlen( $blob ) > 100 ) {
79 $blob = gzdeflate( $blob );
80 $contentType = 'application/vnd.php.serialized+deflate';
81 } else {
82 $contentType = 'application/vnd.php.serialized';
85 $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
87 if ( $code == 404 ) {
88 // Maybe the cache does not exist yet, let's try creating it
89 if ( !$this->createCache( $key ) ) {
90 wfDebug( __METHOD__.": cache creation failed\n" );
91 wfProfileOut( __METHOD__ );
92 return false;
94 $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
97 $result = false;
98 if ( !$code ) {
99 wfDebug( __METHOD__.": PUT failure for key $key\n" );
100 } elseif ( $code >= 300 ) {
101 wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" );
102 } else {
103 $result = true;
106 wfProfileOut( __METHOD__ );
107 return $result;
110 public function delete( $key, $time = 0 ) {
111 wfProfileIn( __METHOD__ );
112 $response = $this->doItemRequest( $key,
113 array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
114 $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
115 if ( !$response || ( $code != 404 && $code >= 300 ) ) {
116 wfDebug( __METHOD__.": DELETE failure for key $key\n" );
117 $result = false;
118 } else {
119 $result = true;
121 wfProfileOut( __METHOD__ );
122 return $result;
125 protected function getCacheUrl( $key ) {
126 if ( count( $this->servers ) == 1 ) {
127 $server = reset( $this->servers );
128 } else {
129 // Use consistent hashing
130 $hashes = array();
131 foreach ( $this->servers as $server ) {
132 $hashes[$server] = md5( $server . '/' . $key );
134 asort( $hashes );
135 reset( $hashes );
136 $server = key( $hashes );
138 return "http://$server/ehcache/rest/{$this->cacheName}";
142 * Get a cURL handle for the given cache URL.
143 * We cache the handles to allow keepalive.
145 protected function getCurl( $cacheUrl ) {
146 if ( !isset( $this->curls[$cacheUrl] ) ) {
147 $this->curls[$cacheUrl] = curl_init();
149 return $this->curls[$cacheUrl];
152 protected function attemptPut( $key, $data, $type, $ttl ) {
153 // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
154 // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
155 // CURLOPT_UPLOAD was pushing the request headers first, then waiting
156 // for an ACK packet, then sending the data, whereas CURLOPT_POST just
157 // sends the headers and the data in a single send().
158 $response = $this->doItemRequest( $key,
159 array(
160 CURLOPT_POST => 1,
161 CURLOPT_CUSTOMREQUEST => 'PUT',
162 CURLOPT_POSTFIELDS => $data,
163 CURLOPT_HTTPHEADER => array(
164 'Content-Type: ' . $type,
165 'ehcacheTimeToLiveSeconds: ' . $ttl
169 if ( !$response ) {
170 return 0;
171 } else {
172 return $response['http_code'];
176 protected function createCache( $key ) {
177 wfDebug( __METHOD__.": creating cache for $key\n" );
178 $response = $this->doCacheRequest( $key,
179 array(
180 CURLOPT_POST => 1,
181 CURLOPT_CUSTOMREQUEST => 'PUT',
182 CURLOPT_POSTFIELDS => '',
183 ) );
184 if ( !$response ) {
185 wfDebug( __CLASS__.": failed to create cache for $key\n" );
186 return false;
188 if ( $response['http_code'] == 201 /* created */
189 || $response['http_code'] == 409 /* already there */ )
191 return true;
192 } else {
193 return false;
197 protected function doCacheRequest( $key, $curlOptions = array() ) {
198 $cacheUrl = $this->getCacheUrl( $key );
199 $curl = $this->getCurl( $cacheUrl );
200 return $this->doRequest( $curl, $cacheUrl, $curlOptions );
203 protected function doItemRequest( $key, $curlOptions = array() ) {
204 $cacheUrl = $this->getCacheUrl( $key );
205 $curl = $this->getCurl( $cacheUrl );
206 $url = $cacheUrl . '/' . rawurlencode( $key );
207 return $this->doRequest( $curl, $url, $curlOptions );
210 protected function doRequest( $curl, $url, $curlOptions = array() ) {
211 if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
212 // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
213 throw new MWException( __METHOD__.": to prevent options set in one doRequest() " .
214 "call from affecting subsequent doRequest() calls, only options listed " .
215 "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
217 $curlOptions += $this->curlOptions;
218 $curlOptions[CURLOPT_URL] = $url;
220 curl_setopt_array( $curl, $curlOptions );
221 $result = curl_exec( $curl );
222 if ( $result === false ) {
223 wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" );
224 return false;
226 $info = curl_getinfo( $curl );
227 $info['body'] = $result;
228 return $info;