1<?php
2
3/**
4 * Copyright (c) 2006- Facebook
5 * Distributed under the Thrift Software License
6 *
7 * See accompanying file LICENSE or visit the Thrift site at:
8 * http://developers.facebook.com/thrift/
9 *
10 * @package thrift.transport
11 * @author Mark Slee <mcslee@facebook.com>
12 */
13
14/**
15 * Sockets implementation of the TTransport interface.
16 *
17 * @package thrift.transport
18 * @author Mark Slee <mcslee@facebook.com>
19 */
20class TSocket extends TTransport {
21
22  /**
23   * Handle to PHP socket
24   *
25   * @var resource
26   */
27  private $handle_ = null;
28
29  /**
30   * Remote hostname
31   *
32   * @var string
33   */
34  protected $host_ = 'localhost';
35
36  /**
37   * Remote port
38   *
39   * @var int
40   */
41  protected $port_ = '9090';
42
43  /**
44   * Send timeout in milliseconds
45   *
46   * @var int
47   */
48  private $sendTimeout_ = 100;
49
50  /**
51   * Recv timeout in milliseconds
52   *
53   * @var int
54   */
55  private $recvTimeout_ = 750;
56
57  /**
58   * Is send timeout set?
59   *
60   * @var bool
61   */
62  private $sendTimeoutSet_ = FALSE;
63
64  /**
65   * Persistent socket or plain?
66   *
67   * @var bool
68   */
69  private $persist_ = FALSE;
70
71  /**
72   * Debugging on?
73   *
74   * @var bool
75   */
76  protected $debug_ = FALSE;
77
78  /**
79   * Debug handler
80   *
81   * @var mixed
82   */
83  protected $debugHandler_ = null;
84
85  /**
86   * Socket constructor
87   *
88   * @param string $host         Remote hostname
89   * @param int    $port         Remote port
90   * @param bool   $persist      Whether to use a persistent socket
91   * @param string $debugHandler Function to call for error logging
92   */
93  public function __construct($host='localhost',
94                              $port=9090,
95                              $persist=FALSE,
96                              $debugHandler=null) {
97    $this->host_ = $host;
98    $this->port_ = $port;
99    $this->persist_ = $persist;
100    $this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log';
101  }
102
103  /**
104   * Sets the send timeout.
105   *
106   * @param int $timeout
107   */
108  public function setSendTimeout($timeout) {
109    $this->sendTimeout_ = $timeout;
110  }
111
112  /**
113   * Sets the receive timeout.
114   *
115   * @param int $timeout
116   */
117  public function setRecvTimeout($timeout) {
118    $this->recvTimeout_ = $timeout;
119  }
120
121  /**
122   * Sets debugging output on or off
123   *
124   * @param bool $debug
125   */
126  public function setDebug($debug) {
127    $this->debug_ = $debug;
128  }
129
130  /**
131   * Get the host that this socket is connected to
132   *
133   * @return string host
134   */
135  public function getHost() {
136    return $this->host_;
137  }
138
139  /**
140   * Get the remote port that this socket is connected to
141   *
142   * @return int port
143   */
144  public function getPort() {
145    return $this->port_;
146  }
147
148  /**
149   * Tests whether this is open
150   *
151   * @return bool true if the socket is open
152   */
153  public function isOpen() {
154    return is_resource($this->handle_);
155  }
156
157  /**
158   * Connects the socket.
159   */
160  public function open() {
161
162    if ($this->persist_) {
163      $this->handle_ = @pfsockopen($this->host_,
164                                   $this->port_,
165                                   $errno,
166                                   $errstr,
167                                   $this->sendTimeout_/1000.0);
168    } else {
169      $this->handle_ = @fsockopen($this->host_,
170                                  $this->port_,
171                                  $errno,
172                                  $errstr,
173                                  $this->sendTimeout_/1000.0);
174    }
175
176    // Connect failed?
177    if ($this->handle_ === FALSE) {
178      $error = 'TSocket: Could not connect to '.$this->host_.':'.$this->port_.' ('.$errstr.' ['.$errno.'])';
179      if ($this->debug_) {
180        call_user_func($this->debugHandler_, $error);
181      }
182      throw new TException($error);
183    }
184
185    stream_set_timeout($this->handle_, 0, $this->sendTimeout_*1000);
186    $this->sendTimeoutSet_ = TRUE;
187  }
188
189  /**
190   * Closes the socket.
191   */
192  public function close() {
193    if (!$this->persist_) {
194      @fclose($this->handle_);
195      $this->handle_ = null;
196    }
197  }
198
199  /**
200   * Uses stream get contents to do the reading
201   *
202   * @param int $len How many bytes
203   * @return string Binary data
204   */
205  public function readAll($len) {
206    if ($this->sendTimeoutSet_) {
207      stream_set_timeout($this->handle_, 0, $this->recvTimeout_*1000);
208      $this->sendTimeoutSet_ = FALSE;
209    }
210    // This call does not obey stream_set_timeout values!
211    // $buf = @stream_get_contents($this->handle_, $len);
212
213    $pre = null;
214    while (TRUE) {
215      $buf = @fread($this->handle_, $len);
216      if ($buf === FALSE || $buf === '') {
217        $md = stream_get_meta_data($this->handle_);
218        if ($md['timed_out']) {
219          throw new TException('TSocket: timed out reading '.$len.' bytes from '.
220                               $this->host_.':'.$this->port_);
221        } else {
222          throw new TException('TSocket: Could not read '.$len.' bytes from '.
223                               $this->host_.':'.$this->port_);
224        }
225      } else if (($sz = strlen($buf)) < $len) {
226        $md = stream_get_meta_data($this->handle_);
227        if ($md['timed_out']) {
228          throw new TException('TSocket: timed out reading '.$len.' bytes from '.
229                               $this->host_.':'.$this->port_);
230        } else {
231          $pre .= $buf;
232          $len -= $sz;
233        }
234      } else {
235        return $pre.$buf;
236      }
237    }
238  }
239
240  /**
241   * Read from the socket
242   *
243   * @param int $len How many bytes
244   * @return string Binary data
245   */
246  public function read($len) {
247    if ($this->sendTimeoutSet_) {
248      stream_set_timeout($this->handle_, 0, $this->recvTimeout_*1000);
249      $this->sendTimeoutSet_ = FALSE;
250    }
251    $data = @fread($this->handle_, $len);
252    if ($data === FALSE || $data === '') {
253      $md = stream_get_meta_data($this->handle_);
254      if ($md['timed_out']) {
255        throw new TException('TSocket: timed out reading '.$len.' bytes from '.
256                             $this->host_.':'.$this->port_);
257      } else {
258        throw new TException('TSocket: Could not read '.$len.' bytes from '.
259                             $this->host_.':'.$this->port_);
260      }
261    }
262    return $data;
263  }
264
265  /**
266   * Write to the socket.
267   *
268   * @param string $buf The data to write
269   */
270  public function write($buf) {
271    if (!$this->sendTimeoutSet_) {
272      stream_set_timeout($this->handle_, 0, $this->sendTimeout_*1000);
273      $this->sendTimeoutSet_ = TRUE;
274    }
275    while (strlen($buf) > 0) {
276      $got = @fwrite($this->handle_, $buf);
277      if ($got === 0 || $got === FALSE) {
278        $md = stream_get_meta_data($this->handle_);
279        if ($md['timed_out']) {
280          throw new TException('TSocket: timed out writing '.strlen($buf).' bytes from '.
281                               $this->host_.':'.$this->port_);
282        } else {
283            throw new TException('TSocket: Could not write '.strlen($buf).' bytes '.
284                                 $this->host_.':'.$this->port_);
285        }
286      }
287      $buf = substr($buf, $got);
288    }
289  }
290
291  /**
292   * Flush output to the socket.
293   */
294  public function flush() {
295    $ret = fflush($this->handle_);
296    if ($ret === FALSE) {
297      throw new TException('TSocket: Could not flush: '.
298                           $this->host_.':'.$this->port_);
299    }
300  }
301}
302
303?>
304