blob: e68d33dc8ce80b819fec3d567f0e1b985d1aaf36 [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
34 public class THttpClient : TTransport, IDisposable
35 {
36 private readonly Uri uri;
37 private readonly X509Certificate[] certificates;
38 private Stream inputStream;
39 private MemoryStream outputStream = new MemoryStream();
Jake Farrell5e022aa2012-05-18 00:33:54 +000040
41 // Timeouts in milliseconds
42 private int connectTimeout = 30000;
43
44 private int readTimeout = 30000;
45
Jens Geyerc0ad3682014-05-08 22:31:34 +020046 private IDictionary<String, String> customHeaders = new Dictionary<string, string>();
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 }
Bryan Duxbury62359472010-06-24 20:34:34 +000056
Jens Geyerd5436f52014-10-03 19:50:38 +020057 public THttpClient(Uri u, IEnumerable<X509Certificate> certificates)
58 {
59 uri = u;
Jens Geyerc0ad3682014-05-08 22:31:34 +020060 this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
Jens Geyerd5436f52014-10-03 19:50:38 +020061 }
Jens Geyerc0ad3682014-05-08 22:31:34 +020062
63 public int ConnectTimeout
Jens Geyerd5436f52014-10-03 19:50:38 +020064 {
65 set
66 {
67 connectTimeout = value;
68 }
69 }
Bryan Duxbury62359472010-06-24 20:34:34 +000070
Jens Geyerd5436f52014-10-03 19:50:38 +020071 public int ReadTimeout
72 {
73 set
74 {
75 readTimeout = value;
76 }
77 }
Bryan Duxbury62359472010-06-24 20:34:34 +000078
Jens Geyerd5436f52014-10-03 19:50:38 +020079 public IDictionary<String, String> CustomHeaders
80 {
81 get
82 {
83 return customHeaders;
84 }
85 }
Bryan Duxbury62359472010-06-24 20:34:34 +000086
Jake Farrell86d2a4a2012-05-19 14:29:15 +000087#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +000088 public IWebProxy Proxy
89 {
90 set
91 {
92 proxy = value;
93 }
94 }
Jake Farrell86d2a4a2012-05-19 14:29:15 +000095#endif
Jake Farrell5e022aa2012-05-18 00:33:54 +000096
Jens Geyerd5436f52014-10-03 19:50:38 +020097 public override bool IsOpen
98 {
99 get
100 {
101 return true;
102 }
103 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000104
Jens Geyerd5436f52014-10-03 19:50:38 +0200105 public override void Open()
106 {
107 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000108
Jens Geyerd5436f52014-10-03 19:50:38 +0200109 public override void Close()
110 {
111 if (inputStream != null)
112 {
113 inputStream.Close();
114 inputStream = null;
115 }
116 if (outputStream != null)
117 {
118 outputStream.Close();
119 outputStream = null;
120 }
121 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000122
Jens Geyerd5436f52014-10-03 19:50:38 +0200123 public override int Read(byte[] buf, int off, int len)
124 {
125 if (inputStream == null)
126 {
127 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent");
128 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000129
Jens Geyerd5436f52014-10-03 19:50:38 +0200130 try
131 {
132 int ret = inputStream.Read(buf, off, len);
Bryan Duxbury62359472010-06-24 20:34:34 +0000133
Jens Geyerd5436f52014-10-03 19:50:38 +0200134 if (ret == -1)
135 {
136 throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available");
137 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000138
Jens Geyerd5436f52014-10-03 19:50:38 +0200139 return ret;
140 }
141 catch (IOException iox)
142 {
143 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
144 }
145 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000146
Jens Geyerd5436f52014-10-03 19:50:38 +0200147 public override void Write(byte[] buf, int off, int len)
148 {
149 outputStream.Write(buf, off, len);
150 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000151
Roger Meier284a9b52011-12-08 13:39:56 +0000152#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200153 public override void Flush()
154 {
155 try
156 {
157 SendRequest();
158 }
159 finally
160 {
161 outputStream = new MemoryStream();
162 }
163 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000164
Jens Geyerd5436f52014-10-03 19:50:38 +0200165 private void SendRequest()
166 {
167 try
168 {
169 HttpWebRequest connection = CreateRequest();
Jens Geyer69511592017-05-30 22:32:01 +0200170 connection.Headers.Add("Accept-Encoding", "gzip, deflate");
Bryan Duxbury62359472010-06-24 20:34:34 +0000171
Jens Geyerd5436f52014-10-03 19:50:38 +0200172 byte[] data = outputStream.ToArray();
173 connection.ContentLength = data.Length;
Bryan Duxbury62359472010-06-24 20:34:34 +0000174
Jens Geyerd5436f52014-10-03 19:50:38 +0200175 using (Stream requestStream = connection.GetRequestStream())
176 {
177 requestStream.Write(data, 0, data.Length);
Jens Geyer7dce7b22014-07-25 22:00:44 +0200178
Jens Geyerd5436f52014-10-03 19:50:38 +0200179 // Resolve HTTP hang that can happens after successive calls by making sure
180 // that we release the response and response stream. To support this, we copy
181 // the response to a memory stream.
182 using (var response = connection.GetResponse())
183 {
184 using (var responseStream = response.GetResponseStream())
185 {
186 // Copy the response to a memory stream so that we can
187 // cleanly close the response and response stream.
188 inputStream = new MemoryStream();
Jens Geyer69511592017-05-30 22:32:01 +0200189 byte[] buffer = new byte[8192]; // multiple of 4096
Jens Geyerd5436f52014-10-03 19:50:38 +0200190 int bytesRead;
191 while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
192 {
Jens Geyer69511592017-05-30 22:32:01 +0200193 inputStream.Write(buffer, 0, bytesRead);
Jens Geyerd5436f52014-10-03 19:50:38 +0200194 }
195 inputStream.Seek(0, 0);
196 }
Jens Geyer69511592017-05-30 22:32:01 +0200197
198 foreach( var encoding in response.Headers.GetValues("Content-Encoding"))
199 {
200 switch(encoding)
201 {
202 case "gzip":
203 DecompressGZipped(ref inputStream);
204 break;
205 case "deflate":
206 DecompressDeflated(ref inputStream);
207 break;
208 default:
209 break;
210 }
211 }
Jens Geyerd5436f52014-10-03 19:50:38 +0200212 }
213 }
214 }
215 catch (IOException iox)
216 {
217 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
218 }
219 catch (WebException wx)
220 {
221 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx);
222 }
223 }
Jens Geyer69511592017-05-30 22:32:01 +0200224
225 private void DecompressDeflated(ref Stream inputStream)
226 {
227 var tmp = new MemoryStream();
228 using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress))
229 {
230 decomp.CopyTo(tmp);
231 }
232 inputStream.Dispose();
233 inputStream = tmp;
234 inputStream.Seek(0, 0);
235 }
236
237 private void DecompressGZipped(ref Stream inputStream)
238 {
239 var tmp = new MemoryStream();
240 using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress))
241 {
242 decomp.CopyTo(tmp);
243 }
244 inputStream.Dispose();
245 inputStream = tmp;
246 inputStream.Seek(0, 0);
247 }
Roger Meier284a9b52011-12-08 13:39:56 +0000248#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200249 private HttpWebRequest CreateRequest()
250 {
251 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
Bryan Duxbury62359472010-06-24 20:34:34 +0000252
Jens Geyerc0ad3682014-05-08 22:31:34 +0200253
Roger Meier284a9b52011-12-08 13:39:56 +0000254#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200255 // Adding certificates through code is not supported with WP7 Silverlight
256 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
257 connection.ClientCertificates.AddRange(certificates);
Jens Geyerc0ad3682014-05-08 22:31:34 +0200258
Jens Geyerd5436f52014-10-03 19:50:38 +0200259 if (connectTimeout > 0)
260 {
261 connection.Timeout = connectTimeout;
262 }
263 if (readTimeout > 0)
264 {
265 connection.ReadWriteTimeout = readTimeout;
266 }
Roger Meier284a9b52011-12-08 13:39:56 +0000267#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200268 // Make the request
269 connection.ContentType = "application/x-thrift";
270 connection.Accept = "application/x-thrift";
271 connection.UserAgent = "C#/THttpClient";
272 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000273#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200274 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000275#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000276
Roger Meier284a9b52011-12-08 13:39:56 +0000277 //add custom headers here
Jens Geyerd5436f52014-10-03 19:50:38 +0200278 foreach (KeyValuePair<string, string> item in customHeaders)
279 {
Roger Meier284a9b52011-12-08 13:39:56 +0000280#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200281 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000282#else
283 connection.Headers[item.Key] = item.Value;
284#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200285 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000286
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000287#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000288 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000289#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000290
Roger Meier284a9b52011-12-08 13:39:56 +0000291 return connection;
Jens Geyerd5436f52014-10-03 19:50:38 +0200292 }
Roger Meier284a9b52011-12-08 13:39:56 +0000293
Roger Meier284a9b52011-12-08 13:39:56 +0000294 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
295 {
296 // Extract request and reset buffer
297 var data = outputStream.ToArray();
298
299 //requestBuffer_ = new MemoryStream();
300
301 try
302 {
303 // Create connection object
304 var flushAsyncResult = new FlushAsyncResult(callback, state);
305 flushAsyncResult.Connection = CreateRequest();
306
307 flushAsyncResult.Data = data;
308
309
310 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
311 return flushAsyncResult;
312
313 }
314 catch (IOException iox)
315 {
316 throw new TTransportException(iox.ToString());
317 }
318 }
319
320 public override void EndFlush(IAsyncResult asyncResult)
321 {
322 try
323 {
324 var flushAsyncResult = (FlushAsyncResult) asyncResult;
325
326 if (!flushAsyncResult.IsCompleted)
327 {
328 var waitHandle = flushAsyncResult.AsyncWaitHandle;
329 waitHandle.WaitOne(); // blocking INFINITEly
330 waitHandle.Close();
331 }
332
333 if (flushAsyncResult.AsyncException != null)
334 {
335 throw flushAsyncResult.AsyncException;
336 }
337 } finally
338 {
339 outputStream = new MemoryStream();
340 }
341
342 }
343
Roger Meier284a9b52011-12-08 13:39:56 +0000344 private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
345 {
346 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
347 try
348 {
349 var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
350 reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
351 reqStream.Flush();
352 reqStream.Close();
353
354 // Start the asynchronous operation to get the response
355 flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
356 }
357 catch (Exception exception)
358 {
359 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
360 flushAsyncResult.UpdateStatusToComplete();
361 flushAsyncResult.NotifyCallbackWhenAvailable();
362 }
363 }
364
365 private void GetResponseCallback(IAsyncResult asynchronousResult)
366 {
367 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
368 try
369 {
370 inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
371 }
372 catch (Exception exception)
373 {
374 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
375 }
376 flushAsyncResult.UpdateStatusToComplete();
377 flushAsyncResult.NotifyCallbackWhenAvailable();
378 }
379
380 // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
381 class FlushAsyncResult : IAsyncResult
382 {
383 private volatile Boolean _isCompleted;
384 private ManualResetEvent _evt;
385 private readonly AsyncCallback _cbMethod;
386 private readonly Object _state;
387
388 public FlushAsyncResult(AsyncCallback cbMethod, Object state)
389 {
390 _cbMethod = cbMethod;
391 _state = state;
392 }
393
394 internal byte[] Data { get; set; }
395 internal HttpWebRequest Connection { get; set; }
396 internal TTransportException AsyncException { get; set; }
397
398 public object AsyncState
399 {
400 get { return _state; }
401 }
402 public WaitHandle AsyncWaitHandle
403 {
404 get { return GetEvtHandle(); }
405 }
406 public bool CompletedSynchronously
407 {
408 get { return false; }
409 }
410 public bool IsCompleted
411 {
412 get { return _isCompleted; }
413 }
414 private readonly Object _locker = new Object();
415 private ManualResetEvent GetEvtHandle()
416 {
417 lock (_locker)
418 {
419 if (_evt == null)
420 {
421 _evt = new ManualResetEvent(false);
422 }
423 if (_isCompleted)
424 {
425 _evt.Set();
426 }
427 }
428 return _evt;
429 }
430 internal void UpdateStatusToComplete()
431 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200432 _isCompleted = true; //1. set _iscompleted to true
Roger Meier284a9b52011-12-08 13:39:56 +0000433 lock (_locker)
434 {
435 if (_evt != null)
436 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200437 _evt.Set(); //2. set the event, when it exists
Roger Meier284a9b52011-12-08 13:39:56 +0000438 }
439 }
440 }
441
442 internal void NotifyCallbackWhenAvailable()
443 {
444 if (_cbMethod != null)
445 {
446 _cbMethod(this);
447 }
448 }
449 }
450
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000451#region " IDisposable Support "
Jens Geyerd5436f52014-10-03 19:50:38 +0200452 private bool _IsDisposed;
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000453
Jens Geyerd5436f52014-10-03 19:50:38 +0200454 // IDisposable
455 protected override void Dispose(bool disposing)
456 {
457 if (!_IsDisposed)
458 {
459 if (disposing)
460 {
461 if (inputStream != null)
462 inputStream.Dispose();
463 if (outputStream != null)
464 outputStream.Dispose();
465 }
466 }
467 _IsDisposed = true;
468 }
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000469#endregion
Jens Geyerd5436f52014-10-03 19:50:38 +0200470 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000471}