MediaWiki  master
GuzzleHttpRequest.php
Go to the documentation of this file.
1 <?php
23 
40  const SUPPORTS_FILE_POSTS = true;
41 
42  protected $handler = null;
43  protected $sink = null;
45  protected $guzzleOptions = [ 'http_errors' => false ];
46 
54  public function __construct(
55  $url, array $options = [], $caller = __METHOD__, Profiler $profiler = null
56  ) {
57  parent::__construct( $url, $options, $caller, $profiler );
58 
59  if ( isset( $options['handler'] ) ) {
60  $this->handler = $options['handler'];
61  }
62  if ( isset( $options['sink'] ) ) {
63  $this->sink = $options['sink'];
64  }
65  }
66 
87  public function setCallback( $callback ) {
88  $this->sink = null;
89  $this->doSetCallback( $callback );
90  }
91 
102  protected function doSetCallback( $callback ) {
103  if ( !$this->sink ) {
104  parent::doSetCallback( $callback );
105  $this->sink = new MWCallbackStream( $this->callback );
106  }
107  }
108 
114  public function execute() {
115  $this->prepare();
116 
117  if ( !$this->status->isOK() ) {
118  return Status::wrap( $this->status ); // TODO B/C; move this to callers
119  }
120 
121  if ( $this->proxy ) {
122  $this->guzzleOptions['proxy'] = $this->proxy;
123  }
124 
125  $this->guzzleOptions['timeout'] = $this->timeout;
126  $this->guzzleOptions['connect_timeout'] = $this->connectTimeout;
127  $this->guzzleOptions['version'] = '1.1';
128 
129  if ( !$this->followRedirects ) {
130  $this->guzzleOptions['allow_redirects'] = false;
131  } else {
132  $this->guzzleOptions['allow_redirects'] = [
133  'max' => $this->maxRedirects
134  ];
135  }
136 
137  if ( $this->method == 'POST' ) {
139  if ( is_array( $postData ) ) {
140  $this->guzzleOptions['form_params'] = $postData;
141  } else {
142  $this->guzzleOptions['body'] = $postData;
143  }
144 
145  // Suppress 'Expect: 100-continue' header, as some servers
146  // will reject it with a 417 and Curl won't auto retry
147  // with HTTP 1.0 fallback
148  $this->guzzleOptions['expect'] = false;
149  }
150 
151  $this->guzzleOptions['headers'] = $this->reqHeaders;
152 
153  if ( $this->handler ) {
154  $this->guzzleOptions['handler'] = $this->handler;
155  }
156 
157  if ( $this->sink ) {
158  $this->guzzleOptions['sink'] = $this->sink;
159  }
160 
161  if ( $this->caInfo ) {
162  $this->guzzleOptions['verify'] = $this->caInfo;
163  } elseif ( !$this->sslVerifyHost && !$this->sslVerifyCert ) {
164  $this->guzzleOptions['verify'] = false;
165  }
166 
167  try {
168  $client = new Client( $this->guzzleOptions );
169  $request = new Request( $this->method, $this->url );
170  $response = $client->send( $request );
171  $this->headerList = $response->getHeaders();
172 
173  $this->respVersion = $response->getProtocolVersion();
174  $this->respStatus = $response->getStatusCode() . ' ' . $response->getReasonPhrase();
175  } catch ( GuzzleHttp\Exception\ConnectException $e ) {
176  // ConnectException is thrown for several reasons besides generic "timeout":
177  // Connection refused
178  // couldn't connect to host
179  // connection attempt failed
180  // Could not resolve IPv4 address for host
181  // Could not resolve IPv6 address for host
182  if ( $this->usingCurl() ) {
183  $handlerContext = $e->getHandlerContext();
184  if ( $handlerContext['errno'] == CURLE_OPERATION_TIMEOUTED ) {
185  $this->status->fatal( 'http-timed-out', $this->url );
186  } else {
187  $this->status->fatal( 'http-curl-error', $handlerContext['error'] );
188  }
189  } else {
190  $this->status->fatal( 'http-request-error' );
191  }
192  } catch ( GuzzleHttp\Exception\RequestException $e ) {
193  if ( $this->usingCurl() ) {
194  $handlerContext = $e->getHandlerContext();
195  $this->status->fatal( 'http-curl-error', $handlerContext['error'] );
196  } else {
197  // Non-ideal, but the only way to identify connection timeout vs other conditions
198  $needle = 'Connection timed out';
199  if ( strpos( $e->getMessage(), $needle ) !== false ) {
200  $this->status->fatal( 'http-timed-out', $this->url );
201  } else {
202  $this->status->fatal( 'http-request-error' );
203  }
204  }
205  } catch ( GuzzleHttp\Exception\GuzzleException $e ) {
206  $this->status->fatal( 'http-internal-error' );
207  }
208 
209  if ( $this->profiler ) {
210  $profileSection = $this->profiler->scopedProfileIn(
211  __METHOD__ . '-' . $this->profileName
212  );
213  }
214 
215  if ( $this->profiler ) {
216  $this->profiler->scopedProfileOut( $profileSection );
217  }
218 
219  $this->parseHeader();
220  $this->setStatus();
221 
222  return Status::wrap( $this->status ); // TODO B/C; move this to callers
223  }
224 
225  protected function prepare() {
226  $this->doSetCallback( $this->callback );
227  parent::prepare();
228  }
229 
233  protected function usingCurl() {
234  return ( $this->handler && is_a( $this->handler, 'GuzzleHttp\Handler\CurlHandler' ) ) ||
235  ( !$this->handler && extension_loaded( 'curl' ) );
236  }
237 
242  protected function parseHeader() {
243  // Failure without (valid) headers gets a response status of zero
244  if ( !$this->status->isOK() ) {
245  $this->respStatus = '0 Error';
246  }
247 
248  foreach ( $this->headerList as $name => $values ) {
249  $this->respHeaders[strtolower( $name )] = $values;
250  }
251 
252  $this->parseCookies();
253  }
254 }
$response
setCallback( $callback)
Set a read callback to accept data read from the HTTP request.
callable $callback
parseHeader()
Guzzle provides headers as an array.
parseCookies()
Parse the cookies in the response headers and store them in the cookie jar.
Profiler $profiler
setStatus()
Sets HTTPRequest status member to a fatal value with the error message if the returned integer value ...
doSetCallback( $callback)
Worker function for setting callbacks.
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:55
int string $timeout
__construct( $url, array $options=[], $caller=__METHOD__, Profiler $profiler=null)