Thrift now works in PHP, hot stuff

Summary: End to end communication working in Thrift with PHP

Problem: It's a bit slower than pillar still. Need to find out why.

Reviewed By: aditya

Test Plan: Unit tests are in the test directory. Get lucas on the PHP case...




git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@664720 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/php/src/transport/TBufferedTransport.php b/lib/php/src/transport/TBufferedTransport.php
new file mode 100644
index 0000000..dad96ff
--- /dev/null
+++ b/lib/php/src/transport/TBufferedTransport.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * Buffered transport. Stores data to an internal buffer that it doesn't
+ * actually write out until flush is called. For reading, we do a greedy
+ * read and then serve data out of the internal buffer.
+ *
+ * @package thrift.transport
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+class TBufferedTransport extends TTransport {
+
+  /**
+   * Constructor. Creates a buffered transport around an underlying transport
+   */
+  public function __construct($transport=null, $rBufSize=512, $wBufSize=512) {
+    $this->transport_ = $transport;
+    $this->rBufSize_ = $rBufSize;
+    $this->wBufSize_ = $wBufSize;
+  }
+
+  /**
+   * The underlying transport
+   *
+   * @var TTransport
+   */
+  protected $transport_ = null;
+
+  /**
+   * The receive buffer size
+   *
+   * @var int
+   */
+  protected $rBufSize_ = 512;
+
+  /**
+   * The write buffer size
+   *
+   * @var int
+   */
+  protected $wBufSize_ = 512;
+
+  /**
+   * The write buffer.
+   *
+   * @var string
+   */
+  protected $wBuf_ = '';
+
+  /**
+   * The read buffer.
+   *
+   * @var string
+   */
+  protected $rBuf_ = '';
+
+  public function isOpen() {
+    return $this->transport_->isOpen();
+  }
+
+  public function open() {
+    $this->transport_->open();
+  }
+
+  public function close() {
+    $this->transport_->close();
+  }
+
+  public function readAll($len) {
+    return $this->transport_->readAll($len);
+  }
+  
+  public function read($len) {
+    // Methinks PHP is already buffering these for us
+    return $this->transport_->read($len);
+
+    if (strlen($this->rBuf_) >= $len) {
+      $ret = substr($this->rBuf_, 0, $len);
+      $this->rBuf_ = substr($this->rBuf_, $len);
+      return $ret;
+    }
+
+    $this->rBuf_ .= $this->transport_->read($this->rBufSize_);
+    $give = min(strlen($this->rBuf_), $len);
+    $ret = substr($this->rBuf_, 0, $give);
+    $this->rBuf_ = substr($this->rBuf_, $give);
+    return $ret;
+  }
+
+  public function write($buf) {
+    $this->wBuf_ .= $buf;
+    if (strlen($this->wBuf_) >= $this->wBufSize_) {
+      $this->transport_->write($this->wBuf_);
+      $this->wBuf_ = '';
+    }
+  }
+
+  public function flush() {
+    if (!empty($this->wBuf_)) {
+      $this->transport_->write($this->wBuf_);
+      $this->wBuf_ = '';
+    }
+  }
+
+}
+
+?>
+
diff --git a/lib/php/src/transport/TChunkedTransport.php b/lib/php/src/transport/TChunkedTransport.php
new file mode 100644
index 0000000..04fba1f
--- /dev/null
+++ b/lib/php/src/transport/TChunkedTransport.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Chunked transport. Writes and reads data in chunks that are stamped with
+ * their length.
+ *
+ * @package thrift.transport
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+class TChunkedTransport extends TTransport {
+
+  /**
+   * Underlying transport object.
+   *
+   * @var TTransport
+   */
+  private $transport_;
+
+  /**
+   * Buffer for read data.
+   *
+   * @var string
+   */
+  private $rBuf_;
+
+  /**
+   * Buffer for queued output data
+   *
+   * @var string
+   */
+  private $wBuf_;
+
+  /**
+   * Constructor.
+   *
+   * @param TTransport $transport Underlying transport
+   */
+  public __construct($transport=null) {
+    $this->transport_ = $transport;
+  }
+
+  /**
+   * Reads from the buffer. When more data is required reads another entire
+   * chunk and serves future reads out of that.
+   *
+   * @param int $len How much data
+   */
+  public function read($len) {
+    $out = '';
+    $need = $len;
+    $have = strlen($this->rBuf_);
+    if ($need > $have) {
+      $out = $this->rBuf_;
+      $need -= $have;
+      $this->readChunk();
+    }
+
+    $give = $need;
+    if (strlen($this->rBuf_) < $give) {
+      $out .= $this->rBuf_;
+      $this->rBuf_ = '';
+    } else {
+      $out .= substr($this->rBuf_, 0, $give);
+      $this->rBuf_ = substr($this->rBuf_, $give);
+    }
+
+    return $out;
+  }
+
+  /**
+   * Reads a chunk of data into the internal read buffer.
+   */
+  private function readChunk() {
+    $buf = $this->transport_->readAll(4);
+    $val = unpack('N', $buf);
+    $sz = $val[1];
+
+    $this->rBuf_ = $this->transport_->readAll($sz);
+  }
+
+  /**
+   * Writes some data to the pending output buffer.
+   *
+   * @param string $buf The data
+   * @param int    $len Limit of bytes to write
+   */
+  public function write($buf, $len=null) {
+    if ($len !== null && $len < strlen($buf)) {
+      $buf = substr($buf, 0, $len);
+    }
+    $this->wBuf_ .= $buf;
+  }
+
+  /**
+   * Writes the output buffer to the stream in the format of a 4-byte length
+   * followed by the actual data.
+   */
+  public function flush() {
+    $out = pack('N', strlen($this->wBuf_));
+    $out .= $this->wBuf_;
+    $this->transport_->write($out);
+    $this->transport_->flush();
+    $this->wBuf_ = '';
+  }
+
+}
\ No newline at end of file
diff --git a/lib/php/src/transport/TSocket.php b/lib/php/src/transport/TSocket.php
new file mode 100644
index 0000000..0a3b090
--- /dev/null
+++ b/lib/php/src/transport/TSocket.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * Sockets implementation of the TTransport interface.
+ *
+ * @package thrift.transport
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+class TSocket extends TTransport {
+
+  /**
+   * Handle to PHP socket
+   *
+   * @var resource
+   */
+  private $handle_ = null;
+
+  /**
+   * Remote hostname
+   * 
+   * @var string
+   */
+  private $host_ = 'localhost';
+
+  /**
+   * Remote port
+   *
+   * @var int
+   */
+  private $port_ = '9090';
+
+  /**
+   * Persistent socket or plain?
+   *
+   * @var bool
+   */
+  private $persist_ = false;
+
+  /**
+   * Socket constructor
+   *
+   * @param string $host    Remote hostname
+   * @param int    $port    Remote port
+   * @param bool   $persist Whether to use a persistent socket
+   */
+  public function __construct($host='localhost', $port=9090, $persist=false) {
+    $this->host_ = $host;
+    $this->port_ = $port;
+    $this->persist_ = $persist;
+  }
+
+  /**
+   * Tests whether this is open
+   *
+   * @return bool true if the socket is open
+   */
+  public function isOpen() {
+    return is_resource($this->handle_);
+  }
+
+  /**
+   * Connects the socket.
+   */
+  public function open() {
+    if ($this->persist_) {
+      $this->handle_ = pfsockopen($this->host_, $this->port_);
+    } else {
+      $this->handle_ = fsockopen($this->host_, $this->port_);
+    }
+    if ($this->handle_ === FALSE) {      
+      throw new Exception('TSocket: Could not connect to '.
+                          $this->host_.':'.$this->port_);
+    }
+  }
+
+  /**
+   * Closes the socket
+   */
+  public function close() {
+    if (!$this->persist_) {
+      fclose($this->handle_);
+    }
+  }
+  
+  /**
+   * Uses stream get contents to do the reading
+   */
+  public function readAll($len) {
+    return stream_get_contents($this->handle_, $len);
+  }
+
+  /**
+   * Read from the socket
+   */
+  public function read($len) {
+    $data = fread($this->handle_, 1);
+    if ($data === FALSE) {
+      throw new Exception('TSocket: Could not read '.$len.' bytes from '.
+                          $this->host_.':'.$this->port_);
+    }
+    return $data;
+  }
+
+  /**
+   * Write to the socket.
+   */
+  public function write($buf) {
+    while (!empty($buf)) {
+      $got = fwrite($this->handle_, $buf);
+      if ($got == false) {
+        throw new Exception('TSocket: Could not write '.strlen($buf).' bytes '.
+                            $this->host_.':'.$this->port_);
+      }
+      $buf = substr($buf, $got);
+    }
+  }
+
+  /**
+   * Flush output to the socket.
+   */
+  public function flush() {
+    fflush($this->handle_);
+  }
+}
+
+?>
diff --git a/lib/php/src/transport/TTransport.php b/lib/php/src/transport/TTransport.php
new file mode 100644
index 0000000..8d57d2a
--- /dev/null
+++ b/lib/php/src/transport/TTransport.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Base interface for a transport agent.
+ *
+ * @package thrift.transport
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+abstract class TTransport {
+
+  /**
+   * Whether this transport is open.
+   *
+   * @return boolean true if open
+   */
+  public abstract function isOpen();
+
+  /**
+   * Open the transport for reading/writing
+   *
+   * @throws TTransportException if cannot open
+   */
+  public abstract function open();
+
+  /**
+   * Close the transport.
+   */
+  public abstract function close();
+
+  /**
+   * 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 abstract function read($len);
+
+  /**
+   * Guarantees that the full amount of data is read.
+   *
+   * @return string The data, of exact length
+   * @throws TTransportException if cannot read data
+   */
+  public function readAll($len) {
+    // return $this->read($len);
+
+    $data = '';
+    $got = 0;
+    while (($got = strlen($data)) < $len) {
+      $data .= $this->read($len - $got);
+    }
+    return $data;
+  }
+
+  /**
+   * Writes the given data out.
+   *
+   * @param string $buf  The data to write
+   * @throws TTransportException if writing fails
+   */
+  public abstract function write($buf);
+
+  /**
+   * Flushes any pending data out of a buffer
+   *
+   * @throws TTransportException if a writing error occurs
+   */
+  public function flush() {}
+}
+
+?>