Merge "Special:Upload should not crash on failing previews"
[mediawiki.git] / includes / libs / virtualrest / SwiftVirtualRESTService.php
blob679d51c5ac32fc378392c7352dba3c7471d66c7b
1 <?php
2 /**
3 * Virtual HTTP service client for Swift
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.
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.
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
20 * @file
23 /**
24 * Example virtual rest service for OpenStack Swift
25 * @TODO: caching support (APC/memcached)
26 * @since 1.23
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;
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)
47 public function __construct( array $params ) {
48 // set up defaults and merge them with the given params
49 $mparams = array_merge( [
50 'name' => 'swift'
51 ], $params );
52 parent::__construct( $mparams );
55 /**
56 * @return int|bool HTTP status on cached failure
58 protected function needsAuthRequest() {
59 if ( !$this->authCreds ) {
60 return true;
62 if ( $this->authErrorTimestamp !== null ) {
63 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
64 return $this->authCachedStatus; // failed last attempt; don't bother
65 } else { // actually retry this time
66 $this->authErrorTimestamp = null;
69 // Session keys expire after a while, so we renew them periodically
70 return ( ( time() - $this->authSessionTimestamp ) > $this->params['swiftAuthTTL'] );
73 protected function applyAuthResponse( array $req ) {
74 $this->authSessionTimestamp = 0;
75 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
76 if ( $rcode >= 200 && $rcode <= 299 ) { // OK
77 $this->authCreds = [
78 'auth_token' => $rhdrs['x-auth-token'],
79 'storage_url' => $rhdrs['x-storage-url']
81 $this->authSessionTimestamp = time();
82 return true;
83 } elseif ( $rcode === 403 ) {
84 $this->authCachedStatus = 401;
85 $this->authCachedReason = 'Authorization Required';
86 $this->authErrorTimestamp = time();
87 return false;
88 } else {
89 $this->authCachedStatus = $rcode;
90 $this->authCachedReason = $rdesc;
91 $this->authErrorTimestamp = time();
92 return null;
96 public function onRequests( array $reqs, Closure $idGeneratorFunc ) {
97 $result = [];
98 $firstReq = reset( $reqs );
99 if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
100 // This was an authentication request for work requests...
101 $result = $reqs; // no change
102 } else {
103 // These are actual work requests...
104 $needsAuth = $this->needsAuthRequest();
105 if ( $needsAuth === true ) {
106 // These are work requests and we don't have any token to use.
107 // Replace the work requests with an authentication request.
108 $result = [
109 $idGeneratorFunc() => [
110 'method' => 'GET',
111 'url' => $this->params['swiftAuthUrl'] . "/v1.0",
112 'headers' => [
113 'x-auth-user' => $this->params['swiftUser'],
114 'x-auth-key' => $this->params['swiftKey'] ],
115 'isAuth' => true,
116 'chain' => $reqs
119 } elseif ( $needsAuth !== false ) {
120 // These are work requests and authentication has previously failed.
121 // It is most efficient to just give failed pseudo responses back for
122 // the original work requests.
123 foreach ( $reqs as $key => $req ) {
124 $req['response'] = [
125 'code' => $this->authCachedStatus,
126 'reason' => $this->authCachedReason,
127 'headers' => [],
128 'body' => '',
129 'error' => ''
131 $result[$key] = $req;
133 } else {
134 // These are work requests and we have a token already.
135 // Go through and mangle each request to include a token.
136 foreach ( $reqs as $key => $req ) {
137 // The default encoding treats the URL as a REST style path that uses
138 // forward slash as a hierarchical delimiter (and never otherwise).
139 // Subclasses can override this, and should be documented in any case.
140 $parts = array_map( 'rawurlencode', explode( '/', $req['url'] ) );
141 $req['url'] = $this->authCreds['storage_url'] . '/' . implode( '/', $parts );
142 $req['headers']['x-auth-token'] = $this->authCreds['auth_token'];
143 $result[$key] = $req;
144 // @TODO: add ETag/Content-Length and such as needed
148 return $result;
151 public function onResponses( array $reqs, Closure $idGeneratorFunc ) {
152 $firstReq = reset( $reqs );
153 if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
154 $result = [];
155 // This was an authentication request for work requests...
156 if ( $this->applyAuthResponse( $firstReq ) ) {
157 // If it succeeded, we can subsitute the work requests back.
158 // Call this recursively in order to munge and add headers.
159 $result = $this->onRequests( $firstReq['chain'], $idGeneratorFunc );
160 } else {
161 // If it failed, it is most efficient to just give failing
162 // pseudo-responses back for the actual work requests.
163 foreach ( $firstReq['chain'] as $key => $req ) {
164 $req['response'] = [
165 'code' => $this->authCachedStatus,
166 'reason' => $this->authCachedReason,
167 'headers' => [],
168 'body' => '',
169 'error' => ''
171 $result[$key] = $req;
174 } else {
175 $result = $reqs; // no change
177 return $result;