THRIFT-2394 TCurlClient: an HTTP transport implementation using libcurl
Client: PHP
Patch: Oran Kelly
diff --git a/lib/php/Makefile.am b/lib/php/Makefile.am
index d1a560a..e9d673c 100755
--- a/lib/php/Makefile.am
+++ b/lib/php/Makefile.am
@@ -95,6 +95,7 @@
phptransportdir = $(phpdir)/Transport
phptransport_DATA = \
lib/Thrift/Transport/TBufferedTransport.php \
+ lib/Thrift/Transport/TCurlClient.php \
lib/Thrift/Transport/TFramedTransport.php \
lib/Thrift/Transport/THttpClient.php \
lib/Thrift/Transport/TMemoryBuffer.php \
diff --git a/lib/php/lib/Thrift/Transport/TCurlClient.php b/lib/php/lib/Thrift/Transport/TCurlClient.php
new file mode 100644
index 0000000..9eeefe1
--- /dev/null
+++ b/lib/php/lib/Thrift/Transport/TCurlClient.php
@@ -0,0 +1,222 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.transport
+ */
+
+namespace Thrift\Transport;
+
+use Thrift\Transport\TTransport;
+use Thrift\Exception\TTransportException;
+use Thrift\Factory\TStringFuncFactory;
+
+/**
+ * HTTP client for Thrift
+ *
+ * @package thrift.transport
+ */
+class TCurlClient extends TTransport {
+
+ private static $curlHandle;
+
+ /**
+ * The host to connect to
+ *
+ * @var string
+ */
+ protected $host_;
+
+ /**
+ * The port to connect on
+ *
+ * @var int
+ */
+ protected $port_;
+
+ /**
+ * The URI to request
+ *
+ * @var string
+ */
+ protected $uri_;
+
+ /**
+ * The scheme to use for the request, i.e. http, https
+ *
+ * @var string
+ */
+ protected $scheme_;
+
+ /**
+ * Buffer for the HTTP request data
+ *
+ * @var string
+ */
+ protected $request_;
+
+ /**
+ * Buffer for the HTTP response data.
+ *
+ * @var binary string
+ */
+ protected $response_;
+
+ /**
+ * Read timeout
+ *
+ * @var float
+ */
+ protected $timeout_;
+
+ /**
+ * Make a new HTTP client.
+ *
+ * @param string $host
+ * @param int $port
+ * @param string $uri
+ */
+ public function __construct($host, $port=80, $uri='', $scheme = 'http') {
+ if ((TStringFuncFactory::create()->strlen($uri) > 0) && ($uri{0} != '/')) {
+ $uri = '/'.$uri;
+ }
+ $this->scheme_ = $scheme;
+ $this->host_ = $host;
+ $this->port_ = $port;
+ $this->uri_ = $uri;
+ $this->request_ = '';
+ $this->response_ = null;
+ $this->timeout_ = null;
+ }
+
+ /**
+ * Set read timeout
+ *
+ * @param float $timeout
+ */
+ public function setTimeoutSecs($timeout) {
+ $this->timeout_ = $timeout;
+ }
+
+ /**
+ * Whether this transport is open.
+ *
+ * @return boolean true if open
+ */
+ public function isOpen() {
+ return true;
+ }
+
+ /**
+ * Open the transport for reading/writing
+ *
+ * @throws TTransportException if cannot open
+ */
+ public function open() {
+ }
+
+ /**
+ * Close the transport.
+ */
+ public function close() {
+ $this->request_ = '';
+ $this->response_ = null;
+ }
+
+ /**
+ * Read some data into the array.
+ *
+ * @param int $len How much to read
+ * @return string The data that has been read
+ * @throws TTransportException if cannot read any more data
+ */
+ public function read($len) {
+ if ($len >= strlen($this->response_)) {
+ return $this->response_;
+ } else {
+ $ret = substr($this->response_, 0, $len);
+ $this->response_ = substr($this->response_, $len);
+ return $ret;
+ }
+ }
+
+ /**
+ * Writes some data into the pending buffer
+ *
+ * @param string $buf The data to write
+ * @throws TTransportException if writing fails
+ */
+ public function write($buf) {
+ $this->request_ .= $buf;
+ }
+
+ /**
+ * Opens and sends the actual request over the HTTP connection
+ *
+ * @throws TTransportException if a writing error occurs
+ */
+ public function flush() {
+ if (!self::$curlHandle) {
+ register_shutdown_function(array('Thrift\\Transport\\TCurlClient', 'closeCurlHandle'));
+ self::$curlHandle = curl_init();
+ curl_setopt(self::$curlHandle, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt(self::$curlHandle, CURLOPT_BINARYTRANSFER, true);
+ curl_setopt(self::$curlHandle, CURLOPT_USERAGENT, 'PHP/TCurlClient');
+ curl_setopt(self::$curlHandle, CURLOPT_CUSTOMREQUEST, 'POST');
+ curl_setopt(self::$curlHandle, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt(self::$curlHandle, CURLOPT_MAXREDIRS, 1);
+ }
+ // God, PHP really has some esoteric ways of doing simple things.
+ $host = $this->host_.($this->port_ != 80 ? ':'.$this->port_ : '');
+ $fullUrl = $this->scheme_."://".$host.$this->uri_;
+
+ $headers = array('Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: '.TStringFuncFactory::create()->strlen($this->request_));
+ curl_setopt(self::$curlHandle, CURLOPT_HTTPHEADER, $headers);
+
+ if ($this->timeout_ > 0) {
+ curl_setopt(self::$curlHandle, CURLOPT_TIMEOUT, $this->timeout_);
+ }
+ curl_setopt(self::$curlHandle, CURLOPT_POSTFIELDS, $this->request_);
+ $this->request_ = '';
+
+ curl_setopt(self::$curlHandle, CURLOPT_URL, $fullUrl);
+ $this->response_ = curl_exec(self::$curlHandle);
+
+ // Connect failed?
+ if (!$this->response_) {
+ curl_close(self::$curlHandle);
+ self::$curlHandle = null;
+ $error = 'TCurlClient: Could not connect to '.$fullUrl;
+ throw new TTransportException($error, TTransportException::NOT_OPEN);
+ }
+ }
+
+ static function closeCurlHandle() {
+ try {
+ if (self::$curlHandle) {
+ curl_close(self::$curlHandle);
+ self::$curlHandle = null;
+ }
+ } catch (\Exception $x) {
+ error_log('There was an error closing the curl handle: ' . $x->getMessage());
+ }
+ }
+
+}