Merge "Don't bother showing prev/next links navigation when there's few results"
[lhc/web/wiklou.git] / includes / libs / virtualrest / SwiftVirtualRESTService.php
1 <?php
2 /**
3 * Virtual HTTP service client for Swift
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23 /**
24 * Example virtual rest service for OpenStack Swift
25 * @TODO: caching support (APC/memcached)
26 * @since 1.23
27 */
28 class SwiftVirtualRESTService extends VirtualRESTService {
29 /** @var array */
30 protected $authCreds;
31 /** @var int UNIX timestamp */
32 protected $authSessionTimestamp = 0;
33 /** @var int UNIX timestamp */
34 protected $authErrorTimestamp = null;
35 /** @var int */
36 protected $authCachedStatus = null;
37 /** @var string */
38 protected $authCachedReason = null;
39
40 /**
41 * @param array $params Key/value map
42 * - swiftAuthUrl : Swift authentication server URL
43 * - swiftUser : Swift user used by MediaWiki (account:username)
44 * - swiftKey : Swift authentication key for the above user
45 * - swiftAuthTTL : Swift authentication TTL (seconds)
46 */
47 public function __construct( array $params ) {
48 parent::__construct( $params );
49 }
50
51 /**
52 * @return int|bool HTTP status on cached failure
53 */
54 protected function needsAuthRequest() {
55 if ( !$this->authCreds ) {
56 return true;
57 }
58 if ( $this->authErrorTimestamp !== null ) {
59 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
60 return $this->authCachedStatus; // failed last attempt; don't bother
61 } else { // actually retry this time
62 $this->authErrorTimestamp = null;
63 }
64 }
65 // Session keys expire after a while, so we renew them periodically
66 return ( ( time() - $this->authSessionTimestamp ) > $this->params['swiftAuthTTL'] );
67 }
68
69 protected function applyAuthResponse( array $req ) {
70 $this->authSessionTimestamp = 0;
71 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
72 if ( $rcode >= 200 && $rcode <= 299 ) { // OK
73 $this->authCreds = array(
74 'auth_token' => $rhdrs['x-auth-token'],
75 'storage_url' => $rhdrs['x-storage-url']
76 );
77 $this->authSessionTimestamp = time();
78 return true;
79 } elseif ( $rcode === 403 ) {
80 $this->authCachedStatus = 401;
81 $this->authCachedReason = 'Authorization Required';
82 $this->authErrorTimestamp = time();
83 return false;
84 } else {
85 $this->authCachedStatus = $rcode;
86 $this->authCachedReason = $rdesc;
87 $this->authErrorTimestamp = time();
88 return null;
89 }
90 }
91
92 public function onRequests( array $reqs, Closure $idGeneratorFunc ) {
93 $result = array();
94 $firstReq = reset( $reqs );
95 if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
96 // This was an authentication request for work requests...
97 $result = $reqs; // no change
98 } else {
99 // These are actual work requests...
100 $needsAuth = $this->needsAuthRequest();
101 if ( $needsAuth === true ) {
102 // These are work requests and we don't have any token to use.
103 // Replace the work requests with an authentication request.
104 $result = array(
105 $idGeneratorFunc() => array(
106 'method' => 'GET',
107 'url' => $this->params['swiftAuthUrl'] . "/v1.0",
108 'headers' => array(
109 'x-auth-user' => $this->params['swiftUser'],
110 'x-auth-key' => $this->params['swiftKey'] ),
111 'isAuth' => true,
112 'chain' => $reqs
113 )
114 );
115 } elseif ( $needsAuth !== false ) {
116 // These are work requests and authentication has previously failed.
117 // It is most efficient to just give failed pseudo responses back for
118 // the original work requests.
119 foreach ( $reqs as $key => $req ) {
120 $req['response'] = array(
121 'code' => $this->authCachedStatus,
122 'reason' => $this->authCachedReason,
123 'headers' => array(),
124 'body' => '',
125 'error' => ''
126 );
127 $result[$key] = $req;
128 }
129 } else {
130 // These are work requests and we have a token already.
131 // Go through and mangle each request to include a token.
132 foreach ( $reqs as $key => $req ) {
133 // The default encoding treats the URL as a REST style path that uses
134 // forward slash as a hierarchical delimiter (and never otherwise).
135 // Subclasses can override this, and should be documented in any case.
136 $parts = array_map( 'rawurlencode', explode( '/', $req['url'] ) );
137 $req['url'] = $this->authCreds['storage_url'] . '/' . implode( '/', $parts );
138 $req['headers']['x-auth-token'] = $this->authCreds['auth_token'];
139 $result[$key] = $req;
140 // @TODO: add ETag/Content-Length and such as needed
141 }
142 }
143 }
144 return $result;
145 }
146
147 public function onResponses( array $reqs, Closure $idGeneratorFunc ) {
148 $firstReq = reset( $reqs );
149 if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
150 $result = array();
151 // This was an authentication request for work requests...
152 if ( $this->applyAuthResponse( $firstReq ) ) {
153 // If it succeeded, we can subsitute the work requests back.
154 // Call this recursively in order to munge and add headers.
155 $result = $this->onRequests( $firstReq['chain'], $idGeneratorFunc );
156 } else {
157 // If it failed, it is most efficient to just give failing
158 // pseudo-responses back for the actual work requests.
159 foreach ( $firstReq['chain'] as $key => $req ) {
160 $req['response'] = array(
161 'code' => $this->authCachedStatus,
162 'reason' => $this->authCachedReason,
163 'headers' => array(),
164 'body' => '',
165 'error' => ''
166 );
167 $result[$key] = $req;
168 }
169 }
170 } else {
171 $result = $reqs; // no change
172 }
173 return $result;
174 }
175 }