2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
20 package org
.apache
.thrift
.transport
;
22 import java
.io
.ByteArrayInputStream
;
23 import java
.io
.ByteArrayOutputStream
;
24 import java
.io
.InputStream
;
25 import java
.io
.IOException
;
28 import java
.net
.HttpURLConnection
;
29 import java
.util
.HashMap
;
32 import org
.apache
.http
.HttpEntity
;
33 import org
.apache
.http
.HttpHost
;
34 import org
.apache
.http
.HttpResponse
;
35 import org
.apache
.http
.HttpStatus
;
36 import org
.apache
.http
.client
.HttpClient
;
37 import org
.apache
.http
.client
.methods
.HttpPost
;
38 import org
.apache
.http
.entity
.ByteArrayEntity
;
39 import org
.apache
.http
.params
.CoreConnectionPNames
;
42 * HTTP implementation of the TTransport interface. Used for working with a
43 * Thrift web services implementation (using for example TServlet).
45 * This class offers two implementations of the HTTP transport.
46 * One uses HttpURLConnection instances, the other HttpClient from Apache
48 * The chosen implementation depends on the constructor used to
49 * create the THttpClient instance.
50 * Using the THttpClient(String url) constructor or passing null as the
51 * HttpClient to THttpClient(String url, HttpClient client) will create an
52 * instance which will use HttpURLConnection.
54 * When using HttpClient, the following configuration leads to 5-15%
55 * better performance than the HttpURLConnection implementation:
57 * http.protocol.version=HttpVersion.HTTP_1_1
58 * http.protocol.content-charset=UTF-8
59 * http.protocol.expect-continue=false
60 * http.connection.stalecheck=false
62 * Also note that under high load, the HttpURLConnection implementation
63 * may exhaust the open file descriptor limit.
65 * @see <a href="https://issues.apache.org/jira/browse/THRIFT-970">THRIFT-970</a>
68 public class THttpClient
extends TTransport
{
70 private URL url_
= null;
72 private final ByteArrayOutputStream requestBuffer_
= new ByteArrayOutputStream();
74 private InputStream inputStream_
= null;
76 private int connectTimeout_
= 0;
78 private int readTimeout_
= 0;
80 private Map
<String
,String
> customHeaders_
= null;
82 private final HttpHost host
;
84 private final HttpClient client
;
86 public static class Factory
extends TTransportFactory
{
88 private final String url
;
89 private final HttpClient client
;
91 public Factory(String url
) {
96 public Factory(String url
, HttpClient client
) {
102 public TTransport
getTransport(TTransport trans
) {
104 if (null != client
) {
105 return new THttpClient(url
, client
);
107 return new THttpClient(url
);
109 } catch (TTransportException tte
) {
115 public THttpClient(String url
) throws TTransportException
{
120 } catch (IOException iox
) {
121 throw new TTransportException(iox
);
125 public THttpClient(String url
, HttpClient client
) throws TTransportException
{
128 this.client
= client
;
129 this.host
= new HttpHost(url_
.getHost(), -1 == url_
.getPort() ? url_
.getDefaultPort() : url_
.getPort(), url_
.getProtocol());
130 } catch (IOException iox
) {
131 throw new TTransportException(iox
);
135 public void setConnectTimeout(int timeout
) {
136 connectTimeout_
= timeout
;
137 if (null != this.client
) {
138 // WARNING, this modifies the HttpClient params, this might have an impact elsewhere if the
139 // same HttpClient is used for something else.
140 client
.getParams().setParameter(CoreConnectionPNames
.CONNECTION_TIMEOUT
, connectTimeout_
);
144 public void setReadTimeout(int timeout
) {
145 readTimeout_
= timeout
;
146 if (null != this.client
) {
147 // WARNING, this modifies the HttpClient params, this might have an impact elsewhere if the
148 // same HttpClient is used for something else.
149 client
.getParams().setParameter(CoreConnectionPNames
.SO_TIMEOUT
, readTimeout_
);
153 public void setCustomHeaders(Map
<String
,String
> headers
) {
154 customHeaders_
= headers
;
157 public void setCustomHeader(String key
, String value
) {
158 if (customHeaders_
== null) {
159 customHeaders_
= new HashMap
<String
, String
>();
161 customHeaders_
.put(key
, value
);
164 public void open() {}
166 public void close() {
167 if (null != inputStream_
) {
169 inputStream_
.close();
170 } catch (IOException ioe
) {
177 public boolean isOpen() {
181 public int read(byte[] buf
, int off
, int len
) throws TTransportException
{
182 if (inputStream_
== null) {
183 throw new TTransportException("Response buffer is empty, no request.");
186 int ret
= inputStream_
.read(buf
, off
, len
);
188 throw new TTransportException("No more data available.");
191 } catch (IOException iox
) {
192 throw new TTransportException(iox
);
196 public void write(byte[] buf
, int off
, int len
) {
197 requestBuffer_
.write(buf
, off
, len
);
201 * copy from org.apache.http.util.EntityUtils#consume. Android has it's own httpcore
202 * that doesn't have a consume.
204 private static void consume(final HttpEntity entity
) throws IOException
{
205 if (entity
== null) {
208 if (entity
.isStreaming()) {
209 InputStream instream
= entity
.getContent();
210 if (instream
!= null) {
216 private void flushUsingHttpClient() throws TTransportException
{
218 if (null == this.client
) {
219 throw new TTransportException("Null HttpClient, aborting.");
222 // Extract request and reset buffer
223 byte[] data
= requestBuffer_
.toByteArray();
224 requestBuffer_
.reset();
226 HttpPost post
= null;
228 InputStream is
= null;
231 // Set request to path + query string
232 post
= new HttpPost(this.url_
.getFile());
235 // Headers are added to the HttpPost instance, not
239 post
.setHeader("Content-Type", "application/x-thrift");
240 post
.setHeader("Accept", "application/x-thrift");
241 post
.setHeader("User-Agent", "Java/THttpClient/HC");
243 if (null != customHeaders_
) {
244 for (Map
.Entry
<String
, String
> header
: customHeaders_
.entrySet()) {
245 post
.setHeader(header
.getKey(), header
.getValue());
249 post
.setEntity(new ByteArrayEntity(data
));
251 HttpResponse response
= this.client
.execute(this.host
, post
);
252 int responseCode
= response
.getStatusLine().getStatusCode();
255 // Retrieve the inputstream BEFORE checking the status code so
256 // resources get freed in the finally clause.
259 is
= response
.getEntity().getContent();
261 if (responseCode
!= HttpStatus
.SC_OK
) {
262 throw new TTransportException("HTTP Response code: " + responseCode
);
265 // Read the responses into a byte array so we can release the connection
266 // early. This implies that the whole content will have to be read in
267 // memory, and that momentarily we might use up twice the memory (while the
268 // thrift struct is being read up the chain).
269 // Proceeding differently might lead to exhaustion of connections and thus
272 byte[] buf
= new byte[1024];
273 ByteArrayOutputStream baos
= new ByteArrayOutputStream();
279 baos
.write(buf
, 0, len
);
284 // Indicate we're done with the content.
285 consume(response
.getEntity());
286 } catch (IOException ioe
) {
287 // We ignore this exception, it might only mean the server has no
288 // keep-alive capability.
291 inputStream_
= new ByteArrayInputStream(baos
.toByteArray());
292 } catch (IOException ioe
) {
293 // Abort method so the connection gets released back to the connection manager
297 throw new TTransportException(ioe
);
300 // Close the entity's input stream, this will release the underlying connection
303 } catch (IOException ioe
) {
304 throw new TTransportException(ioe
);
308 post
.releaseConnection();
313 public void flush() throws TTransportException
{
315 if (null != this.client
) {
316 flushUsingHttpClient();
320 // Extract request and reset buffer
321 byte[] data
= requestBuffer_
.toByteArray();
322 requestBuffer_
.reset();
325 // Create connection object
326 HttpURLConnection connection
= (HttpURLConnection
)url_
.openConnection();
328 // Timeouts, only if explicitly set
329 if (connectTimeout_
> 0) {
330 connection
.setConnectTimeout(connectTimeout_
);
332 if (readTimeout_
> 0) {
333 connection
.setReadTimeout(readTimeout_
);
337 connection
.setRequestMethod("POST");
338 connection
.setRequestProperty("Content-Type", "application/x-thrift");
339 connection
.setRequestProperty("Accept", "application/x-thrift");
340 connection
.setRequestProperty("User-Agent", "Java/THttpClient");
341 if (customHeaders_
!= null) {
342 for (Map
.Entry
<String
, String
> header
: customHeaders_
.entrySet()) {
343 connection
.setRequestProperty(header
.getKey(), header
.getValue());
346 connection
.setDoOutput(true);
347 connection
.connect();
348 connection
.getOutputStream().write(data
);
350 int responseCode
= connection
.getResponseCode();
351 if (responseCode
!= HttpURLConnection
.HTTP_OK
) {
352 throw new TTransportException("HTTP Response code: " + responseCode
);
355 // Read the responses
356 inputStream_
= connection
.getInputStream();
358 } catch (IOException iox
) {
359 throw new TTransportException(iox
);