3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
20 * @package thrift.transport
23 namespace Thrift\Transport
;
25 use Thrift\Exception\TTransportException
;
26 use Thrift\Factory\TStringFuncFactory
;
29 * HTTP client for Thrift
31 * @package thrift.transport
33 class TCurlClient
extends TTransport
35 private static $curlHandle;
38 * The host to connect to
45 * The port to connect on
59 * The scheme to use for the request, i.e. http, https
66 * Buffer for the HTTP request data
73 * Buffer for the HTTP response data.
94 * Make a new HTTP client.
100 public function __construct($host, $port = 80, $uri = '', $scheme = 'http')
102 if ((TStringFuncFactory
::create()->strlen($uri) > 0) && ($uri{0} != '/')) {
105 $this->scheme_
= $scheme;
106 $this->host_
= $host;
107 $this->port_
= $port;
109 $this->request_
= '';
110 $this->response_
= null;
111 $this->timeout_
= null;
112 $this->headers_
= array();
118 * @param float $timeout
120 public function setTimeoutSecs($timeout)
122 $this->timeout_
= $timeout;
126 * Whether this transport is open.
128 * @return boolean true if open
130 public function isOpen()
136 * Open the transport for reading/writing
138 * @throws TTransportException if cannot open
140 public function open()
145 * Close the transport.
147 public function close()
149 $this->request_
= '';
150 $this->response_
= null;
154 * Read some data into the array.
156 * @param int $len How much to read
157 * @return string The data that has been read
158 * @throws TTransportException if cannot read any more data
160 public function read($len)
162 if ($len >= strlen($this->response_
)) {
163 return $this->response_
;
165 $ret = substr($this->response_
, 0, $len);
166 $this->response_
= substr($this->response_
, $len);
173 * Guarantees that the full amount of data is read. Since TCurlClient gets entire payload at
174 * once, parent readAll cannot be used.
176 * @return string The data, of exact length
177 * @throws TTransportException if cannot read data
179 public function readAll($len)
181 $data = $this->read($len);
183 if (TStringFuncFactory
::create()->strlen($data) !== $len) {
184 throw new TTransportException('TCurlClient could not read '.$len.' bytes');
191 * Writes some data into the pending buffer
193 * @param string $buf The data to write
194 * @throws TTransportException if writing fails
196 public function write($buf)
198 $this->request_
.= $buf;
202 * Opens and sends the actual request over the HTTP connection
204 * @throws TTransportException if a writing error occurs
206 public function flush()
208 if (!self
::$curlHandle) {
209 register_shutdown_function(array('Thrift\\Transport\\TCurlClient', 'closeCurlHandle'));
210 self
::$curlHandle = curl_init();
211 curl_setopt(self
::$curlHandle, CURLOPT_RETURNTRANSFER
, true);
212 curl_setopt(self
::$curlHandle, CURLOPT_BINARYTRANSFER
, true);
213 curl_setopt(self
::$curlHandle, CURLOPT_USERAGENT
, 'PHP/TCurlClient');
214 curl_setopt(self
::$curlHandle, CURLOPT_CUSTOMREQUEST
, 'POST');
215 curl_setopt(self
::$curlHandle, CURLOPT_FOLLOWLOCATION
, true);
216 curl_setopt(self
::$curlHandle, CURLOPT_MAXREDIRS
, 1);
218 // God, PHP really has some esoteric ways of doing simple things.
219 $host = $this->host_
. ($this->port_
!= 80 ?
':' . $this->port_
: '');
220 $fullUrl = $this->scheme_
. "://" . $host . $this->uri_
;
223 $defaultHeaders = array('Accept' => 'application/x-thrift',
224 'Content-Type' => 'application/x-thrift',
225 'Content-Length' => TStringFuncFactory
::create()->strlen($this->request_
));
226 foreach (array_merge($defaultHeaders, $this->headers_
) as $key => $value) {
227 $headers[] = "$key: $value";
230 curl_setopt(self
::$curlHandle, CURLOPT_HTTPHEADER
, $headers);
232 if ($this->timeout_
> 0) {
233 if ($this->timeout_
< 1.0) {
234 // Timestamps smaller than 1 second are ignored when CURLOPT_TIMEOUT is used
235 curl_setopt(self
::$curlHandle, CURLOPT_TIMEOUT_MS
, 1000 * $this->timeout_
);
237 curl_setopt(self
::$curlHandle, CURLOPT_TIMEOUT
, $this->timeout_
);
240 curl_setopt(self
::$curlHandle, CURLOPT_POSTFIELDS
, $this->request_
);
241 $this->request_
= '';
243 curl_setopt(self
::$curlHandle, CURLOPT_URL
, $fullUrl);
244 $this->response_
= curl_exec(self
::$curlHandle);
245 $responseError = curl_error(self
::$curlHandle);
247 $code = curl_getinfo(self
::$curlHandle, CURLINFO_HTTP_CODE
);
249 // Handle non 200 status code / connect failure
250 if ($this->response_
=== false ||
$code !== 200) {
251 curl_close(self
::$curlHandle);
252 self
::$curlHandle = null;
253 $this->response_
= null;
254 $error = 'TCurlClient: Could not connect to ' . $fullUrl;
255 if ($responseError) {
256 $error .= ', ' . $responseError;
259 $error .= ', HTTP status code: ' . $code;
261 throw new TTransportException($error, TTransportException
::UNKNOWN
);
265 public static function closeCurlHandle()
268 if (self
::$curlHandle) {
269 curl_close(self
::$curlHandle);
270 self
::$curlHandle = null;
272 } catch (\Exception
$x) {
273 error_log('There was an error closing the curl handle: ' . $x->getMessage());
277 public function addHeaders($headers)
279 $this->headers_
= array_merge($this->headers_
, $headers);