]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/THttpClient.java
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / java / src / org / apache / thrift / transport / THttpClient.java
1 /*
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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
17 * under the License.
18 */
19
20 package org.apache.thrift.transport;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.InputStream;
25 import java.io.IOException;
26
27 import java.net.URL;
28 import java.net.HttpURLConnection;
29 import java.util.HashMap;
30 import java.util.Map;
31
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;
40
41 /**
42 * HTTP implementation of the TTransport interface. Used for working with a
43 * Thrift web services implementation (using for example TServlet).
44 *
45 * This class offers two implementations of the HTTP transport.
46 * One uses HttpURLConnection instances, the other HttpClient from Apache
47 * Http Components.
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.
53 *
54 * When using HttpClient, the following configuration leads to 5-15%
55 * better performance than the HttpURLConnection implementation:
56 *
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
61 *
62 * Also note that under high load, the HttpURLConnection implementation
63 * may exhaust the open file descriptor limit.
64 *
65 * @see <a href="https://issues.apache.org/jira/browse/THRIFT-970">THRIFT-970</a>
66 */
67
68 public class THttpClient extends TTransport {
69
70 private URL url_ = null;
71
72 private final ByteArrayOutputStream requestBuffer_ = new ByteArrayOutputStream();
73
74 private InputStream inputStream_ = null;
75
76 private int connectTimeout_ = 0;
77
78 private int readTimeout_ = 0;
79
80 private Map<String,String> customHeaders_ = null;
81
82 private final HttpHost host;
83
84 private final HttpClient client;
85
86 public static class Factory extends TTransportFactory {
87
88 private final String url;
89 private final HttpClient client;
90
91 public Factory(String url) {
92 this.url = url;
93 this.client = null;
94 }
95
96 public Factory(String url, HttpClient client) {
97 this.url = url;
98 this.client = client;
99 }
100
101 @Override
102 public TTransport getTransport(TTransport trans) {
103 try {
104 if (null != client) {
105 return new THttpClient(url, client);
106 } else {
107 return new THttpClient(url);
108 }
109 } catch (TTransportException tte) {
110 return null;
111 }
112 }
113 }
114
115 public THttpClient(String url) throws TTransportException {
116 try {
117 url_ = new URL(url);
118 this.client = null;
119 this.host = null;
120 } catch (IOException iox) {
121 throw new TTransportException(iox);
122 }
123 }
124
125 public THttpClient(String url, HttpClient client) throws TTransportException {
126 try {
127 url_ = new URL(url);
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);
132 }
133 }
134
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_);
141 }
142 }
143
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_);
150 }
151 }
152
153 public void setCustomHeaders(Map<String,String> headers) {
154 customHeaders_ = headers;
155 }
156
157 public void setCustomHeader(String key, String value) {
158 if (customHeaders_ == null) {
159 customHeaders_ = new HashMap<String, String>();
160 }
161 customHeaders_.put(key, value);
162 }
163
164 public void open() {}
165
166 public void close() {
167 if (null != inputStream_) {
168 try {
169 inputStream_.close();
170 } catch (IOException ioe) {
171 ;
172 }
173 inputStream_ = null;
174 }
175 }
176
177 public boolean isOpen() {
178 return true;
179 }
180
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.");
184 }
185 try {
186 int ret = inputStream_.read(buf, off, len);
187 if (ret == -1) {
188 throw new TTransportException("No more data available.");
189 }
190 return ret;
191 } catch (IOException iox) {
192 throw new TTransportException(iox);
193 }
194 }
195
196 public void write(byte[] buf, int off, int len) {
197 requestBuffer_.write(buf, off, len);
198 }
199
200 /**
201 * copy from org.apache.http.util.EntityUtils#consume. Android has it's own httpcore
202 * that doesn't have a consume.
203 */
204 private static void consume(final HttpEntity entity) throws IOException {
205 if (entity == null) {
206 return;
207 }
208 if (entity.isStreaming()) {
209 InputStream instream = entity.getContent();
210 if (instream != null) {
211 instream.close();
212 }
213 }
214 }
215
216 private void flushUsingHttpClient() throws TTransportException {
217
218 if (null == this.client) {
219 throw new TTransportException("Null HttpClient, aborting.");
220 }
221
222 // Extract request and reset buffer
223 byte[] data = requestBuffer_.toByteArray();
224 requestBuffer_.reset();
225
226 HttpPost post = null;
227
228 InputStream is = null;
229
230 try {
231 // Set request to path + query string
232 post = new HttpPost(this.url_.getFile());
233
234 //
235 // Headers are added to the HttpPost instance, not
236 // to HttpClient.
237 //
238
239 post.setHeader("Content-Type", "application/x-thrift");
240 post.setHeader("Accept", "application/x-thrift");
241 post.setHeader("User-Agent", "Java/THttpClient/HC");
242
243 if (null != customHeaders_) {
244 for (Map.Entry<String, String> header : customHeaders_.entrySet()) {
245 post.setHeader(header.getKey(), header.getValue());
246 }
247 }
248
249 post.setEntity(new ByteArrayEntity(data));
250
251 HttpResponse response = this.client.execute(this.host, post);
252 int responseCode = response.getStatusLine().getStatusCode();
253
254 //
255 // Retrieve the inputstream BEFORE checking the status code so
256 // resources get freed in the finally clause.
257 //
258
259 is = response.getEntity().getContent();
260
261 if (responseCode != HttpStatus.SC_OK) {
262 throw new TTransportException("HTTP Response code: " + responseCode);
263 }
264
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
270 // to app failure.
271
272 byte[] buf = new byte[1024];
273 ByteArrayOutputStream baos = new ByteArrayOutputStream();
274
275 int len = 0;
276 do {
277 len = is.read(buf);
278 if (len > 0) {
279 baos.write(buf, 0, len);
280 }
281 } while (-1 != len);
282
283 try {
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.
289 }
290
291 inputStream_ = new ByteArrayInputStream(baos.toByteArray());
292 } catch (IOException ioe) {
293 // Abort method so the connection gets released back to the connection manager
294 if (null != post) {
295 post.abort();
296 }
297 throw new TTransportException(ioe);
298 } finally {
299 if (null != is) {
300 // Close the entity's input stream, this will release the underlying connection
301 try {
302 is.close();
303 } catch (IOException ioe) {
304 throw new TTransportException(ioe);
305 }
306 }
307 if (post != null) {
308 post.releaseConnection();
309 }
310 }
311 }
312
313 public void flush() throws TTransportException {
314
315 if (null != this.client) {
316 flushUsingHttpClient();
317 return;
318 }
319
320 // Extract request and reset buffer
321 byte[] data = requestBuffer_.toByteArray();
322 requestBuffer_.reset();
323
324 try {
325 // Create connection object
326 HttpURLConnection connection = (HttpURLConnection)url_.openConnection();
327
328 // Timeouts, only if explicitly set
329 if (connectTimeout_ > 0) {
330 connection.setConnectTimeout(connectTimeout_);
331 }
332 if (readTimeout_ > 0) {
333 connection.setReadTimeout(readTimeout_);
334 }
335
336 // Make the request
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());
344 }
345 }
346 connection.setDoOutput(true);
347 connection.connect();
348 connection.getOutputStream().write(data);
349
350 int responseCode = connection.getResponseCode();
351 if (responseCode != HttpURLConnection.HTTP_OK) {
352 throw new TTransportException("HTTP Response code: " + responseCode);
353 }
354
355 // Read the responses
356 inputStream_ = connection.getInputStream();
357
358 } catch (IOException iox) {
359 throw new TTransportException(iox);
360 }
361 }
362 }