blob: 986799cd271df580c50899eb3face0486cfb8dc4 [file] [log] [blame]
Park June Chul8d554f52019-04-24 10:19:01 +09001/**
Bryan Duxbury62359472010-06-24 20:34:34 +00002 * 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 *
Jens Geyerd5436f52014-10-03 19:50:38 +020010 * http://www.apache.org/licenses/LICENSE-2.0
Bryan Duxbury62359472010-06-24 20:34:34 +000011 *
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.
Jens Geyerd5436f52014-10-03 19:50:38 +020018 *
19 *
Bryan Duxbury62359472010-06-24 20:34:34 +000020 */
21
22using System;
23using System.Collections.Generic;
24using System.IO;
25using System.Net;
Roger Meier284a9b52011-12-08 13:39:56 +000026using System.Threading;
Jens Geyerc0ad3682014-05-08 22:31:34 +020027using System.Linq;
28using System.Security.Cryptography.X509Certificates;
Jens Geyer69511592017-05-30 22:32:01 +020029using System.IO.Compression;
Bryan Duxbury62359472010-06-24 20:34:34 +000030
31namespace Thrift.Transport
32{
Jens Geyerc0ad3682014-05-08 22:31:34 +020033 public class THttpClient : TTransport, IDisposable
34 {
35 private readonly Uri uri;
36 private readonly X509Certificate[] certificates;
37 private Stream inputStream;
38 private MemoryStream outputStream = new MemoryStream();
Jake Farrell5e022aa2012-05-18 00:33:54 +000039
40 // Timeouts in milliseconds
41 private int connectTimeout = 30000;
42
43 private int readTimeout = 30000;
44
Christian Weiss8fb719e2018-03-30 21:26:04 +020045 private IDictionary<string, string> customHeaders = new Dictionary<string, string>();
Park June Chul8d554f52019-04-24 10:19:01 +090046 private string userAgent = "C#/THttpClient";
Bryan Duxbury62359472010-06-24 20:34:34 +000047
Jake Farrell86d2a4a2012-05-19 14:29:15 +000048#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +000049 private IWebProxy proxy = WebRequest.DefaultWebProxy;
Jens Geyerb080f682014-02-22 21:10:45 +010050#endif
51
Jens Geyerd5436f52014-10-03 19:50:38 +020052 public THttpClient(Uri u)
Jens Geyerc0ad3682014-05-08 22:31:34 +020053 : this(u, Enumerable.Empty<X509Certificate>())
Jens Geyerd5436f52014-10-03 19:50:38 +020054 {
55 }
Park June Chul8d554f52019-04-24 10:19:01 +090056 public THttpClient(Uri u, string userAgent)
57 : this(u, userAgent, Enumerable.Empty<X509Certificate>())
58 {
59 }
Bryan Duxbury62359472010-06-24 20:34:34 +000060
Jens Geyerd5436f52014-10-03 19:50:38 +020061 public THttpClient(Uri u, IEnumerable<X509Certificate> certificates)
62 {
63 uri = u;
Jens Geyerc0ad3682014-05-08 22:31:34 +020064 this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
Jens Geyerd5436f52014-10-03 19:50:38 +020065 }
Park June Chul8d554f52019-04-24 10:19:01 +090066 public THttpClient(Uri u, string userAgent, IEnumerable<X509Certificate> certificates)
67 {
68 uri = u;
69 this.userAgent = userAgent;
70 this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
71 }
Jens Geyerc0ad3682014-05-08 22:31:34 +020072
73 public int ConnectTimeout
Jens Geyerd5436f52014-10-03 19:50:38 +020074 {
75 set
76 {
Christian Weiss8fb719e2018-03-30 21:26:04 +020077 connectTimeout = value;
Jens Geyerd5436f52014-10-03 19:50:38 +020078 }
79 }
Bryan Duxbury62359472010-06-24 20:34:34 +000080
Jens Geyerd5436f52014-10-03 19:50:38 +020081 public int ReadTimeout
82 {
83 set
84 {
85 readTimeout = value;
86 }
87 }
Bryan Duxbury62359472010-06-24 20:34:34 +000088
Christian Weiss8fb719e2018-03-30 21:26:04 +020089 public IDictionary<string, string> CustomHeaders
Jens Geyerd5436f52014-10-03 19:50:38 +020090 {
91 get
92 {
93 return customHeaders;
94 }
95 }
Bryan Duxbury62359472010-06-24 20:34:34 +000096
Jake Farrell86d2a4a2012-05-19 14:29:15 +000097#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +000098 public IWebProxy Proxy
99 {
100 set
101 {
102 proxy = value;
103 }
104 }
Jake Farrell86d2a4a2012-05-19 14:29:15 +0000105#endif
Jake Farrell5e022aa2012-05-18 00:33:54 +0000106
Jens Geyerd5436f52014-10-03 19:50:38 +0200107 public override bool IsOpen
108 {
109 get
110 {
111 return true;
112 }
113 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000114
Jens Geyerd5436f52014-10-03 19:50:38 +0200115 public override void Open()
116 {
117 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000118
Jens Geyerd5436f52014-10-03 19:50:38 +0200119 public override void Close()
120 {
121 if (inputStream != null)
122 {
123 inputStream.Close();
124 inputStream = null;
125 }
126 if (outputStream != null)
127 {
128 outputStream.Close();
129 outputStream = null;
130 }
131 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000132
Jens Geyerd5436f52014-10-03 19:50:38 +0200133 public override int Read(byte[] buf, int off, int len)
134 {
135 if (inputStream == null)
136 {
137 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent");
138 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000139
Jens Geyerd5436f52014-10-03 19:50:38 +0200140 try
141 {
142 int ret = inputStream.Read(buf, off, len);
Bryan Duxbury62359472010-06-24 20:34:34 +0000143
Jens Geyerd5436f52014-10-03 19:50:38 +0200144 if (ret == -1)
145 {
146 throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available");
147 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000148
Jens Geyerd5436f52014-10-03 19:50:38 +0200149 return ret;
150 }
151 catch (IOException iox)
152 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200153 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox);
Jens Geyerd5436f52014-10-03 19:50:38 +0200154 }
155 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000156
Jens Geyerd5436f52014-10-03 19:50:38 +0200157 public override void Write(byte[] buf, int off, int len)
158 {
159 outputStream.Write(buf, off, len);
160 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000161
Roger Meier284a9b52011-12-08 13:39:56 +0000162#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200163 public override void Flush()
164 {
165 try
166 {
167 SendRequest();
168 }
169 finally
170 {
171 outputStream = new MemoryStream();
172 }
173 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000174
Jens Geyerd5436f52014-10-03 19:50:38 +0200175 private void SendRequest()
176 {
177 try
178 {
179 HttpWebRequest connection = CreateRequest();
Jens Geyer69511592017-05-30 22:32:01 +0200180 connection.Headers.Add("Accept-Encoding", "gzip, deflate");
Bryan Duxbury62359472010-06-24 20:34:34 +0000181
Jens Geyerd5436f52014-10-03 19:50:38 +0200182 byte[] data = outputStream.ToArray();
183 connection.ContentLength = data.Length;
Bryan Duxbury62359472010-06-24 20:34:34 +0000184
Jens Geyerd5436f52014-10-03 19:50:38 +0200185 using (Stream requestStream = connection.GetRequestStream())
186 {
187 requestStream.Write(data, 0, data.Length);
Jens Geyer7dce7b22014-07-25 22:00:44 +0200188
Jens Geyerd5436f52014-10-03 19:50:38 +0200189 // Resolve HTTP hang that can happens after successive calls by making sure
190 // that we release the response and response stream. To support this, we copy
191 // the response to a memory stream.
192 using (var response = connection.GetResponse())
193 {
194 using (var responseStream = response.GetResponseStream())
195 {
196 // Copy the response to a memory stream so that we can
197 // cleanly close the response and response stream.
198 inputStream = new MemoryStream();
Jens Geyer69511592017-05-30 22:32:01 +0200199 byte[] buffer = new byte[8192]; // multiple of 4096
Jens Geyerd5436f52014-10-03 19:50:38 +0200200 int bytesRead;
201 while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
202 {
Jens Geyer69511592017-05-30 22:32:01 +0200203 inputStream.Write(buffer, 0, bytesRead);
Jens Geyerd5436f52014-10-03 19:50:38 +0200204 }
205 inputStream.Seek(0, 0);
206 }
Jens Geyer69511592017-05-30 22:32:01 +0200207
Jens Geyer197b0622017-05-31 10:35:00 +0200208 var encodings = response.Headers.GetValues("Content-Encoding");
209 if (encodings != null)
Jens Geyer69511592017-05-30 22:32:01 +0200210 {
Jens Geyer197b0622017-05-31 10:35:00 +0200211 foreach (var encoding in encodings)
Jens Geyer69511592017-05-30 22:32:01 +0200212 {
Jens Geyer197b0622017-05-31 10:35:00 +0200213 switch (encoding)
214 {
215 case "gzip":
216 DecompressGZipped(ref inputStream);
217 break;
218 case "deflate":
219 DecompressDeflated(ref inputStream);
220 break;
221 default:
222 break;
223 }
Jens Geyer69511592017-05-30 22:32:01 +0200224 }
225 }
Jens Geyerd5436f52014-10-03 19:50:38 +0200226 }
227 }
228 }
229 catch (IOException iox)
230 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200231 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox);
Jens Geyerd5436f52014-10-03 19:50:38 +0200232 }
233 catch (WebException wx)
234 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200235 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx, wx);
Jens Geyerd5436f52014-10-03 19:50:38 +0200236 }
237 }
Jens Geyer69511592017-05-30 22:32:01 +0200238
239 private void DecompressDeflated(ref Stream inputStream)
240 {
241 var tmp = new MemoryStream();
242 using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress))
243 {
244 decomp.CopyTo(tmp);
245 }
246 inputStream.Dispose();
247 inputStream = tmp;
248 inputStream.Seek(0, 0);
249 }
250
251 private void DecompressGZipped(ref Stream inputStream)
252 {
253 var tmp = new MemoryStream();
254 using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress))
255 {
256 decomp.CopyTo(tmp);
257 }
258 inputStream.Dispose();
259 inputStream = tmp;
260 inputStream.Seek(0, 0);
261 }
Roger Meier284a9b52011-12-08 13:39:56 +0000262#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200263 private HttpWebRequest CreateRequest()
264 {
265 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
Bryan Duxbury62359472010-06-24 20:34:34 +0000266
Jens Geyerc0ad3682014-05-08 22:31:34 +0200267
Roger Meier284a9b52011-12-08 13:39:56 +0000268#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200269 // Adding certificates through code is not supported with WP7 Silverlight
270 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
271 connection.ClientCertificates.AddRange(certificates);
Jens Geyerc0ad3682014-05-08 22:31:34 +0200272
Jens Geyerd5436f52014-10-03 19:50:38 +0200273 if (connectTimeout > 0)
274 {
275 connection.Timeout = connectTimeout;
276 }
277 if (readTimeout > 0)
278 {
279 connection.ReadWriteTimeout = readTimeout;
280 }
Roger Meier284a9b52011-12-08 13:39:56 +0000281#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200282 // Make the request
283 connection.ContentType = "application/x-thrift";
284 connection.Accept = "application/x-thrift";
Park June Chul8d554f52019-04-24 10:19:01 +0900285 connection.UserAgent = userAgent;
Jens Geyerd5436f52014-10-03 19:50:38 +0200286 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000287#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200288 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000289#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000290
Roger Meier284a9b52011-12-08 13:39:56 +0000291 //add custom headers here
Jens Geyerd5436f52014-10-03 19:50:38 +0200292 foreach (KeyValuePair<string, string> item in customHeaders)
293 {
Roger Meier284a9b52011-12-08 13:39:56 +0000294#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200295 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000296#else
297 connection.Headers[item.Key] = item.Value;
298#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200299 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000300
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000301#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000302 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000303#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000304
Roger Meier284a9b52011-12-08 13:39:56 +0000305 return connection;
Jens Geyerd5436f52014-10-03 19:50:38 +0200306 }
Roger Meier284a9b52011-12-08 13:39:56 +0000307
Roger Meier284a9b52011-12-08 13:39:56 +0000308 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
309 {
310 // Extract request and reset buffer
311 var data = outputStream.ToArray();
312
313 //requestBuffer_ = new MemoryStream();
314
315 try
316 {
317 // Create connection object
318 var flushAsyncResult = new FlushAsyncResult(callback, state);
319 flushAsyncResult.Connection = CreateRequest();
320
321 flushAsyncResult.Data = data;
322
323
324 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
325 return flushAsyncResult;
326
327 }
328 catch (IOException iox)
329 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200330 throw new TTransportException(iox.ToString(), iox);
Roger Meier284a9b52011-12-08 13:39:56 +0000331 }
332 }
333
334 public override void EndFlush(IAsyncResult asyncResult)
335 {
336 try
337 {
Christian Weiss8fb719e2018-03-30 21:26:04 +0200338 var flushAsyncResult = (FlushAsyncResult)asyncResult;
Roger Meier284a9b52011-12-08 13:39:56 +0000339
340 if (!flushAsyncResult.IsCompleted)
341 {
342 var waitHandle = flushAsyncResult.AsyncWaitHandle;
343 waitHandle.WaitOne(); // blocking INFINITEly
344 waitHandle.Close();
345 }
346
347 if (flushAsyncResult.AsyncException != null)
348 {
349 throw flushAsyncResult.AsyncException;
350 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200351 }
352 finally
Roger Meier284a9b52011-12-08 13:39:56 +0000353 {
354 outputStream = new MemoryStream();
355 }
356
357 }
358
Roger Meier284a9b52011-12-08 13:39:56 +0000359 private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
360 {
361 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
362 try
363 {
364 var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
365 reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
366 reqStream.Flush();
367 reqStream.Close();
368
369 // Start the asynchronous operation to get the response
370 flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
371 }
372 catch (Exception exception)
373 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200374 flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception);
Roger Meier284a9b52011-12-08 13:39:56 +0000375 flushAsyncResult.UpdateStatusToComplete();
376 flushAsyncResult.NotifyCallbackWhenAvailable();
377 }
378 }
379
380 private void GetResponseCallback(IAsyncResult asynchronousResult)
381 {
382 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
383 try
384 {
385 inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
386 }
387 catch (Exception exception)
388 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200389 flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception);
Roger Meier284a9b52011-12-08 13:39:56 +0000390 }
391 flushAsyncResult.UpdateStatusToComplete();
392 flushAsyncResult.NotifyCallbackWhenAvailable();
393 }
394
395 // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
396 class FlushAsyncResult : IAsyncResult
397 {
398 private volatile Boolean _isCompleted;
399 private ManualResetEvent _evt;
400 private readonly AsyncCallback _cbMethod;
Christian Weiss8fb719e2018-03-30 21:26:04 +0200401 private readonly object _state;
Roger Meier284a9b52011-12-08 13:39:56 +0000402
Christian Weiss8fb719e2018-03-30 21:26:04 +0200403 public FlushAsyncResult(AsyncCallback cbMethod, object state)
Roger Meier284a9b52011-12-08 13:39:56 +0000404 {
405 _cbMethod = cbMethod;
406 _state = state;
407 }
408
409 internal byte[] Data { get; set; }
410 internal HttpWebRequest Connection { get; set; }
411 internal TTransportException AsyncException { get; set; }
412
413 public object AsyncState
414 {
415 get { return _state; }
416 }
417 public WaitHandle AsyncWaitHandle
418 {
419 get { return GetEvtHandle(); }
420 }
421 public bool CompletedSynchronously
422 {
423 get { return false; }
424 }
425 public bool IsCompleted
426 {
427 get { return _isCompleted; }
428 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200429 private readonly object _locker = new object();
Roger Meier284a9b52011-12-08 13:39:56 +0000430 private ManualResetEvent GetEvtHandle()
431 {
432 lock (_locker)
433 {
434 if (_evt == null)
435 {
436 _evt = new ManualResetEvent(false);
437 }
438 if (_isCompleted)
439 {
440 _evt.Set();
441 }
442 }
443 return _evt;
444 }
445 internal void UpdateStatusToComplete()
446 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200447 _isCompleted = true; //1. set _iscompleted to true
Roger Meier284a9b52011-12-08 13:39:56 +0000448 lock (_locker)
449 {
450 if (_evt != null)
451 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200452 _evt.Set(); //2. set the event, when it exists
Roger Meier284a9b52011-12-08 13:39:56 +0000453 }
454 }
455 }
456
457 internal void NotifyCallbackWhenAvailable()
458 {
459 if (_cbMethod != null)
460 {
461 _cbMethod(this);
462 }
463 }
464 }
465
Christian Weiss8fb719e2018-03-30 21:26:04 +0200466 #region " IDisposable Support "
Jens Geyerd5436f52014-10-03 19:50:38 +0200467 private bool _IsDisposed;
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000468
Jens Geyerd5436f52014-10-03 19:50:38 +0200469 // IDisposable
470 protected override void Dispose(bool disposing)
471 {
472 if (!_IsDisposed)
473 {
474 if (disposing)
475 {
476 if (inputStream != null)
477 inputStream.Dispose();
478 if (outputStream != null)
479 outputStream.Dispose();
480 }
481 }
482 _IsDisposed = true;
483 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200484 #endregion
Jens Geyerd5436f52014-10-03 19:50:38 +0200485 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000486}