blob: 667fc2526c4b1f8d00bca39b5e0ebc0e585c4078 [file] [log] [blame]
Bryan Duxbury62359472010-06-24 20:34:34 +00001/**
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 *
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>();
Bryan Duxbury62359472010-06-24 20:34:34 +000046
Jake Farrell86d2a4a2012-05-19 14:29:15 +000047#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +000048 private IWebProxy proxy = WebRequest.DefaultWebProxy;
Jens Geyerb080f682014-02-22 21:10:45 +010049#endif
50
Jens Geyerd5436f52014-10-03 19:50:38 +020051 public THttpClient(Uri u)
Jens Geyerc0ad3682014-05-08 22:31:34 +020052 : this(u, Enumerable.Empty<X509Certificate>())
Jens Geyerd5436f52014-10-03 19:50:38 +020053 {
54 }
Bryan Duxbury62359472010-06-24 20:34:34 +000055
Jens Geyerd5436f52014-10-03 19:50:38 +020056 public THttpClient(Uri u, IEnumerable<X509Certificate> certificates)
57 {
58 uri = u;
Jens Geyerc0ad3682014-05-08 22:31:34 +020059 this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
Jens Geyerd5436f52014-10-03 19:50:38 +020060 }
Jens Geyerc0ad3682014-05-08 22:31:34 +020061
62 public int ConnectTimeout
Jens Geyerd5436f52014-10-03 19:50:38 +020063 {
64 set
65 {
Christian Weiss8fb719e2018-03-30 21:26:04 +020066 connectTimeout = value;
Jens Geyerd5436f52014-10-03 19:50:38 +020067 }
68 }
Bryan Duxbury62359472010-06-24 20:34:34 +000069
Jens Geyerd5436f52014-10-03 19:50:38 +020070 public int ReadTimeout
71 {
72 set
73 {
74 readTimeout = value;
75 }
76 }
Bryan Duxbury62359472010-06-24 20:34:34 +000077
Christian Weiss8fb719e2018-03-30 21:26:04 +020078 public IDictionary<string, string> CustomHeaders
Jens Geyerd5436f52014-10-03 19:50:38 +020079 {
80 get
81 {
82 return customHeaders;
83 }
84 }
Bryan Duxbury62359472010-06-24 20:34:34 +000085
Jake Farrell86d2a4a2012-05-19 14:29:15 +000086#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +000087 public IWebProxy Proxy
88 {
89 set
90 {
91 proxy = value;
92 }
93 }
Jake Farrell86d2a4a2012-05-19 14:29:15 +000094#endif
Jake Farrell5e022aa2012-05-18 00:33:54 +000095
Jens Geyerd5436f52014-10-03 19:50:38 +020096 public override bool IsOpen
97 {
98 get
99 {
100 return true;
101 }
102 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000103
Jens Geyerd5436f52014-10-03 19:50:38 +0200104 public override void Open()
105 {
106 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000107
Jens Geyerd5436f52014-10-03 19:50:38 +0200108 public override void Close()
109 {
110 if (inputStream != null)
111 {
112 inputStream.Close();
113 inputStream = null;
114 }
115 if (outputStream != null)
116 {
117 outputStream.Close();
118 outputStream = null;
119 }
120 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000121
Jens Geyerd5436f52014-10-03 19:50:38 +0200122 public override int Read(byte[] buf, int off, int len)
123 {
124 if (inputStream == null)
125 {
126 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent");
127 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000128
Jens Geyerd5436f52014-10-03 19:50:38 +0200129 try
130 {
131 int ret = inputStream.Read(buf, off, len);
Bryan Duxbury62359472010-06-24 20:34:34 +0000132
Jens Geyerd5436f52014-10-03 19:50:38 +0200133 if (ret == -1)
134 {
135 throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available");
136 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000137
Jens Geyerd5436f52014-10-03 19:50:38 +0200138 return ret;
139 }
140 catch (IOException iox)
141 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200142 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox);
Jens Geyerd5436f52014-10-03 19:50:38 +0200143 }
144 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000145
Jens Geyerd5436f52014-10-03 19:50:38 +0200146 public override void Write(byte[] buf, int off, int len)
147 {
148 outputStream.Write(buf, off, len);
149 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000150
Roger Meier284a9b52011-12-08 13:39:56 +0000151#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200152 public override void Flush()
153 {
154 try
155 {
156 SendRequest();
157 }
158 finally
159 {
160 outputStream = new MemoryStream();
161 }
162 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000163
Jens Geyerd5436f52014-10-03 19:50:38 +0200164 private void SendRequest()
165 {
166 try
167 {
168 HttpWebRequest connection = CreateRequest();
Jens Geyer69511592017-05-30 22:32:01 +0200169 connection.Headers.Add("Accept-Encoding", "gzip, deflate");
Bryan Duxbury62359472010-06-24 20:34:34 +0000170
Jens Geyerd5436f52014-10-03 19:50:38 +0200171 byte[] data = outputStream.ToArray();
172 connection.ContentLength = data.Length;
Bryan Duxbury62359472010-06-24 20:34:34 +0000173
Jens Geyerd5436f52014-10-03 19:50:38 +0200174 using (Stream requestStream = connection.GetRequestStream())
175 {
176 requestStream.Write(data, 0, data.Length);
Jens Geyer7dce7b22014-07-25 22:00:44 +0200177
Jens Geyerd5436f52014-10-03 19:50:38 +0200178 // Resolve HTTP hang that can happens after successive calls by making sure
179 // that we release the response and response stream. To support this, we copy
180 // the response to a memory stream.
181 using (var response = connection.GetResponse())
182 {
183 using (var responseStream = response.GetResponseStream())
184 {
185 // Copy the response to a memory stream so that we can
186 // cleanly close the response and response stream.
187 inputStream = new MemoryStream();
Jens Geyer69511592017-05-30 22:32:01 +0200188 byte[] buffer = new byte[8192]; // multiple of 4096
Jens Geyerd5436f52014-10-03 19:50:38 +0200189 int bytesRead;
190 while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
191 {
Jens Geyer69511592017-05-30 22:32:01 +0200192 inputStream.Write(buffer, 0, bytesRead);
Jens Geyerd5436f52014-10-03 19:50:38 +0200193 }
194 inputStream.Seek(0, 0);
195 }
Jens Geyer69511592017-05-30 22:32:01 +0200196
Jens Geyer197b0622017-05-31 10:35:00 +0200197 var encodings = response.Headers.GetValues("Content-Encoding");
198 if (encodings != null)
Jens Geyer69511592017-05-30 22:32:01 +0200199 {
Jens Geyer197b0622017-05-31 10:35:00 +0200200 foreach (var encoding in encodings)
Jens Geyer69511592017-05-30 22:32:01 +0200201 {
Jens Geyer197b0622017-05-31 10:35:00 +0200202 switch (encoding)
203 {
204 case "gzip":
205 DecompressGZipped(ref inputStream);
206 break;
207 case "deflate":
208 DecompressDeflated(ref inputStream);
209 break;
210 default:
211 break;
212 }
Jens Geyer69511592017-05-30 22:32:01 +0200213 }
214 }
Jens Geyerd5436f52014-10-03 19:50:38 +0200215 }
216 }
217 }
218 catch (IOException iox)
219 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200220 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox);
Jens Geyerd5436f52014-10-03 19:50:38 +0200221 }
222 catch (WebException wx)
223 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200224 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx, wx);
Jens Geyerd5436f52014-10-03 19:50:38 +0200225 }
226 }
Jens Geyer69511592017-05-30 22:32:01 +0200227
228 private void DecompressDeflated(ref Stream inputStream)
229 {
230 var tmp = new MemoryStream();
231 using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress))
232 {
233 decomp.CopyTo(tmp);
234 }
235 inputStream.Dispose();
236 inputStream = tmp;
237 inputStream.Seek(0, 0);
238 }
239
240 private void DecompressGZipped(ref Stream inputStream)
241 {
242 var tmp = new MemoryStream();
243 using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress))
244 {
245 decomp.CopyTo(tmp);
246 }
247 inputStream.Dispose();
248 inputStream = tmp;
249 inputStream.Seek(0, 0);
250 }
Roger Meier284a9b52011-12-08 13:39:56 +0000251#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200252 private HttpWebRequest CreateRequest()
253 {
254 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
Bryan Duxbury62359472010-06-24 20:34:34 +0000255
Jens Geyerc0ad3682014-05-08 22:31:34 +0200256
Roger Meier284a9b52011-12-08 13:39:56 +0000257#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200258 // Adding certificates through code is not supported with WP7 Silverlight
259 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
260 connection.ClientCertificates.AddRange(certificates);
Jens Geyerc0ad3682014-05-08 22:31:34 +0200261
Jens Geyerd5436f52014-10-03 19:50:38 +0200262 if (connectTimeout > 0)
263 {
264 connection.Timeout = connectTimeout;
265 }
266 if (readTimeout > 0)
267 {
268 connection.ReadWriteTimeout = readTimeout;
269 }
Roger Meier284a9b52011-12-08 13:39:56 +0000270#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200271 // Make the request
272 connection.ContentType = "application/x-thrift";
273 connection.Accept = "application/x-thrift";
274 connection.UserAgent = "C#/THttpClient";
275 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000276#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200277 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000278#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000279
Roger Meier284a9b52011-12-08 13:39:56 +0000280 //add custom headers here
Jens Geyerd5436f52014-10-03 19:50:38 +0200281 foreach (KeyValuePair<string, string> item in customHeaders)
282 {
Roger Meier284a9b52011-12-08 13:39:56 +0000283#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200284 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000285#else
286 connection.Headers[item.Key] = item.Value;
287#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200288 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000289
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000290#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000291 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000292#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000293
Roger Meier284a9b52011-12-08 13:39:56 +0000294 return connection;
Jens Geyerd5436f52014-10-03 19:50:38 +0200295 }
Roger Meier284a9b52011-12-08 13:39:56 +0000296
Roger Meier284a9b52011-12-08 13:39:56 +0000297 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
298 {
299 // Extract request and reset buffer
300 var data = outputStream.ToArray();
301
302 //requestBuffer_ = new MemoryStream();
303
304 try
305 {
306 // Create connection object
307 var flushAsyncResult = new FlushAsyncResult(callback, state);
308 flushAsyncResult.Connection = CreateRequest();
309
310 flushAsyncResult.Data = data;
311
312
313 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
314 return flushAsyncResult;
315
316 }
317 catch (IOException iox)
318 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200319 throw new TTransportException(iox.ToString(), iox);
Roger Meier284a9b52011-12-08 13:39:56 +0000320 }
321 }
322
323 public override void EndFlush(IAsyncResult asyncResult)
324 {
325 try
326 {
Christian Weiss8fb719e2018-03-30 21:26:04 +0200327 var flushAsyncResult = (FlushAsyncResult)asyncResult;
Roger Meier284a9b52011-12-08 13:39:56 +0000328
329 if (!flushAsyncResult.IsCompleted)
330 {
331 var waitHandle = flushAsyncResult.AsyncWaitHandle;
332 waitHandle.WaitOne(); // blocking INFINITEly
333 waitHandle.Close();
334 }
335
336 if (flushAsyncResult.AsyncException != null)
337 {
338 throw flushAsyncResult.AsyncException;
339 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200340 }
341 finally
Roger Meier284a9b52011-12-08 13:39:56 +0000342 {
343 outputStream = new MemoryStream();
344 }
345
346 }
347
Roger Meier284a9b52011-12-08 13:39:56 +0000348 private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
349 {
350 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
351 try
352 {
353 var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
354 reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
355 reqStream.Flush();
356 reqStream.Close();
357
358 // Start the asynchronous operation to get the response
359 flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
360 }
361 catch (Exception exception)
362 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200363 flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception);
Roger Meier284a9b52011-12-08 13:39:56 +0000364 flushAsyncResult.UpdateStatusToComplete();
365 flushAsyncResult.NotifyCallbackWhenAvailable();
366 }
367 }
368
369 private void GetResponseCallback(IAsyncResult asynchronousResult)
370 {
371 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
372 try
373 {
374 inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
375 }
376 catch (Exception exception)
377 {
Jens Geyer6e67faa2018-08-06 23:31:38 +0200378 flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception);
Roger Meier284a9b52011-12-08 13:39:56 +0000379 }
380 flushAsyncResult.UpdateStatusToComplete();
381 flushAsyncResult.NotifyCallbackWhenAvailable();
382 }
383
384 // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
385 class FlushAsyncResult : IAsyncResult
386 {
387 private volatile Boolean _isCompleted;
388 private ManualResetEvent _evt;
389 private readonly AsyncCallback _cbMethod;
Christian Weiss8fb719e2018-03-30 21:26:04 +0200390 private readonly object _state;
Roger Meier284a9b52011-12-08 13:39:56 +0000391
Christian Weiss8fb719e2018-03-30 21:26:04 +0200392 public FlushAsyncResult(AsyncCallback cbMethod, object state)
Roger Meier284a9b52011-12-08 13:39:56 +0000393 {
394 _cbMethod = cbMethod;
395 _state = state;
396 }
397
398 internal byte[] Data { get; set; }
399 internal HttpWebRequest Connection { get; set; }
400 internal TTransportException AsyncException { get; set; }
401
402 public object AsyncState
403 {
404 get { return _state; }
405 }
406 public WaitHandle AsyncWaitHandle
407 {
408 get { return GetEvtHandle(); }
409 }
410 public bool CompletedSynchronously
411 {
412 get { return false; }
413 }
414 public bool IsCompleted
415 {
416 get { return _isCompleted; }
417 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200418 private readonly object _locker = new object();
Roger Meier284a9b52011-12-08 13:39:56 +0000419 private ManualResetEvent GetEvtHandle()
420 {
421 lock (_locker)
422 {
423 if (_evt == null)
424 {
425 _evt = new ManualResetEvent(false);
426 }
427 if (_isCompleted)
428 {
429 _evt.Set();
430 }
431 }
432 return _evt;
433 }
434 internal void UpdateStatusToComplete()
435 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200436 _isCompleted = true; //1. set _iscompleted to true
Roger Meier284a9b52011-12-08 13:39:56 +0000437 lock (_locker)
438 {
439 if (_evt != null)
440 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200441 _evt.Set(); //2. set the event, when it exists
Roger Meier284a9b52011-12-08 13:39:56 +0000442 }
443 }
444 }
445
446 internal void NotifyCallbackWhenAvailable()
447 {
448 if (_cbMethod != null)
449 {
450 _cbMethod(this);
451 }
452 }
453 }
454
Christian Weiss8fb719e2018-03-30 21:26:04 +0200455 #region " IDisposable Support "
Jens Geyerd5436f52014-10-03 19:50:38 +0200456 private bool _IsDisposed;
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000457
Jens Geyerd5436f52014-10-03 19:50:38 +0200458 // IDisposable
459 protected override void Dispose(bool disposing)
460 {
461 if (!_IsDisposed)
462 {
463 if (disposing)
464 {
465 if (inputStream != null)
466 inputStream.Dispose();
467 if (outputStream != null)
468 outputStream.Dispose();
469 }
470 }
471 _IsDisposed = true;
472 }
Christian Weiss8fb719e2018-03-30 21:26:04 +0200473 #endregion
Jens Geyerd5436f52014-10-03 19:50:38 +0200474 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000475}