1<?php
2/*
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
10 *
11 *   http://www.apache.org/licenses/LICENSE-2.0
12 *
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
18 * under the License.
19 *
20 * @package thrift.transport
21 */
22
23namespace Thrift\Transport;
24
25use Thrift\Exception\TTransportException;
26use Thrift\Factory\TStringFuncFactory;
27
28/**
29 * HTTP client for Thrift
30 *
31 * @package thrift.transport
32 */
33class THttpClient extends TTransport
34{
35    /**
36     * The host to connect to
37     *
38     * @var string
39     */
40    protected $host_;
41
42    /**
43     * The port to connect on
44     *
45     * @var int
46     */
47    protected $port_;
48
49    /**
50     * The URI to request
51     *
52     * @var string
53     */
54    protected $uri_;
55
56    /**
57     * The scheme to use for the request, i.e. http, https
58     *
59     * @var string
60     */
61    protected $scheme_;
62
63    /**
64     * Buffer for the HTTP request data
65     *
66     * @var string
67     */
68    protected $buf_;
69
70    /**
71     * Input socket stream.
72     *
73     * @var resource
74     */
75    protected $handle_;
76
77    /**
78     * Read timeout
79     *
80     * @var float
81     */
82    protected $timeout_;
83
84    /**
85     * http headers
86     *
87     * @var array
88     */
89    protected $headers_;
90
91    /**
92     * Context additional options
93     *
94     * @var array
95     */
96    protected $context_;
97
98    /**
99     * Make a new HTTP client.
100     *
101     * @param string $host
102     * @param int    $port
103     * @param string $uri
104     * @param string $scheme
105     * @param array  $context
106     */
107    public function __construct($host, $port = 80, $uri = '', $scheme = 'http', array $context = array())
108    {
109        if ((TStringFuncFactory::create()->strlen($uri) > 0) && ($uri[0] != '/')) {
110            $uri = '/' . $uri;
111        }
112        $this->scheme_ = $scheme;
113        $this->host_ = $host;
114        $this->port_ = $port;
115        $this->uri_ = $uri;
116        $this->buf_ = '';
117        $this->handle_ = null;
118        $this->timeout_ = null;
119        $this->headers_ = array();
120        $this->context_ = $context;
121    }
122
123    /**
124     * Set read timeout
125     *
126     * @param float $timeout
127     */
128    public function setTimeoutSecs($timeout)
129    {
130        $this->timeout_ = $timeout;
131    }
132
133    /**
134     * Whether this transport is open.
135     *
136     * @return boolean true if open
137     */
138    public function isOpen()
139    {
140        return true;
141    }
142
143    /**
144     * Open the transport for reading/writing
145     *
146     * @throws TTransportException if cannot open
147     */
148    public function open()
149    {
150    }
151
152    /**
153     * Close the transport.
154     */
155    public function close()
156    {
157        if ($this->handle_) {
158            @fclose($this->handle_);
159            $this->handle_ = null;
160        }
161    }
162
163    /**
164     * Read some data into the array.
165     *
166     * @param int $len How much to read
167     * @return string The data that has been read
168     * @throws TTransportException if cannot read any more data
169     */
170    public function read($len)
171    {
172        $data = @fread($this->handle_, $len);
173        if ($data === false || $data === '') {
174            $md = stream_get_meta_data($this->handle_);
175            if ($md['timed_out']) {
176                throw new TTransportException(
177                    'THttpClient: timed out reading ' . $len . ' bytes from ' .
178                    $this->host_ . ':' . $this->port_ . $this->uri_,
179                    TTransportException::TIMED_OUT
180                );
181            } else {
182                throw new TTransportException(
183                    'THttpClient: Could not read ' . $len . ' bytes from ' .
184                    $this->host_ . ':' . $this->port_ . $this->uri_,
185                    TTransportException::UNKNOWN
186                );
187            }
188        }
189
190        return $data;
191    }
192
193    /**
194     * Writes some data into the pending buffer
195     *
196     * @param string $buf The data to write
197     * @throws TTransportException if writing fails
198     */
199    public function write($buf)
200    {
201        $this->buf_ .= $buf;
202    }
203
204    /**
205     * Opens and sends the actual request over the HTTP connection
206     *
207     * @throws TTransportException if a writing error occurs
208     */
209    public function flush()
210    {
211        // God, PHP really has some esoteric ways of doing simple things.
212        $host = $this->host_ . ($this->port_ != 80 ? ':' . $this->port_ : '');
213
214        $headers = array();
215        $defaultHeaders = array('Host' => $host,
216            'Accept' => 'application/x-thrift',
217            'User-Agent' => 'PHP/THttpClient',
218            'Content-Type' => 'application/x-thrift',
219            'Content-Length' => TStringFuncFactory::create()->strlen($this->buf_));
220        foreach (array_merge($defaultHeaders, $this->headers_) as $key => $value) {
221            $headers[] = "$key: $value";
222        }
223
224        $options = $this->context_;
225
226        $baseHttpOptions = isset($options["http"]) ? $options["http"] : array();
227
228        $httpOptions = $baseHttpOptions + array('method' => 'POST',
229            'header' => implode("\r\n", $headers),
230            'max_redirects' => 1,
231            'content' => $this->buf_);
232        if ($this->timeout_ > 0) {
233            $httpOptions['timeout'] = $this->timeout_;
234        }
235        $this->buf_ = '';
236
237        $options["http"] = $httpOptions;
238        $contextid = stream_context_create($options);
239        $this->handle_ = @fopen(
240            $this->scheme_ . '://' . $host . $this->uri_,
241            'r',
242            false,
243            $contextid
244        );
245
246        // Connect failed?
247        if ($this->handle_ === false) {
248            $this->handle_ = null;
249            $error = 'THttpClient: Could not connect to ' . $host . $this->uri_;
250            throw new TTransportException($error, TTransportException::NOT_OPEN);
251        }
252    }
253
254    public function addHeaders($headers)
255    {
256        $this->headers_ = array_merge($this->headers_, $headers);
257    }
258}
259