blob: f678d1e1b26366894f1bf776401a1bf55682b39d [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
Jens Geyer197b0622017-05-31 10:35:00 +0200198 var encodings = response.Headers.GetValues("Content-Encoding");
199 if (encodings != null)
Jens Geyer69511592017-05-30 22:32:01 +0200200 {
Jens Geyer197b0622017-05-31 10:35:00 +0200201 foreach (var encoding in encodings)
Jens Geyer69511592017-05-30 22:32:01 +0200202 {
Jens Geyer197b0622017-05-31 10:35:00 +0200203 switch (encoding)
204 {
205 case "gzip":
206 DecompressGZipped(ref inputStream);
207 break;
208 case "deflate":
209 DecompressDeflated(ref inputStream);
210 break;
211 default:
212 break;
213 }
Jens Geyer69511592017-05-30 22:32:01 +0200214 }
215 }
Jens Geyerd5436f52014-10-03 19:50:38 +0200216 }
217 }
218 }
219 catch (IOException iox)
220 {
221 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
222 }
223 catch (WebException wx)
224 {
225 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx);
226 }
227 }
Jens Geyer69511592017-05-30 22:32:01 +0200228
229 private void DecompressDeflated(ref Stream inputStream)
230 {
231 var tmp = new MemoryStream();
232 using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress))
233 {
234 decomp.CopyTo(tmp);
235 }
236 inputStream.Dispose();
237 inputStream = tmp;
238 inputStream.Seek(0, 0);
239 }
240
241 private void DecompressGZipped(ref Stream inputStream)
242 {
243 var tmp = new MemoryStream();
244 using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress))
245 {
246 decomp.CopyTo(tmp);
247 }
248 inputStream.Dispose();
249 inputStream = tmp;
250 inputStream.Seek(0, 0);
251 }
Roger Meier284a9b52011-12-08 13:39:56 +0000252#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200253 private HttpWebRequest CreateRequest()
254 {
255 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
Bryan Duxbury62359472010-06-24 20:34:34 +0000256
Jens Geyerc0ad3682014-05-08 22:31:34 +0200257
Roger Meier284a9b52011-12-08 13:39:56 +0000258#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200259 // Adding certificates through code is not supported with WP7 Silverlight
260 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
261 connection.ClientCertificates.AddRange(certificates);
Jens Geyerc0ad3682014-05-08 22:31:34 +0200262
Jens Geyerd5436f52014-10-03 19:50:38 +0200263 if (connectTimeout > 0)
264 {
265 connection.Timeout = connectTimeout;
266 }
267 if (readTimeout > 0)
268 {
269 connection.ReadWriteTimeout = readTimeout;
270 }
Roger Meier284a9b52011-12-08 13:39:56 +0000271#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200272 // Make the request
273 connection.ContentType = "application/x-thrift";
274 connection.Accept = "application/x-thrift";
275 connection.UserAgent = "C#/THttpClient";
276 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000277#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200278 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000279#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000280
Roger Meier284a9b52011-12-08 13:39:56 +0000281 //add custom headers here
Jens Geyerd5436f52014-10-03 19:50:38 +0200282 foreach (KeyValuePair<string, string> item in customHeaders)
283 {
Roger Meier284a9b52011-12-08 13:39:56 +0000284#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200285 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000286#else
287 connection.Headers[item.Key] = item.Value;
288#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200289 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000290
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000291#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000292 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000293#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000294
Roger Meier284a9b52011-12-08 13:39:56 +0000295 return connection;
Jens Geyerd5436f52014-10-03 19:50:38 +0200296 }
Roger Meier284a9b52011-12-08 13:39:56 +0000297
Roger Meier284a9b52011-12-08 13:39:56 +0000298 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
299 {
300 // Extract request and reset buffer
301 var data = outputStream.ToArray();
302
303 //requestBuffer_ = new MemoryStream();
304
305 try
306 {
307 // Create connection object
308 var flushAsyncResult = new FlushAsyncResult(callback, state);
309 flushAsyncResult.Connection = CreateRequest();
310
311 flushAsyncResult.Data = data;
312
313
314 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
315 return flushAsyncResult;
316
317 }
318 catch (IOException iox)
319 {
320 throw new TTransportException(iox.ToString());
321 }
322 }
323
324 public override void EndFlush(IAsyncResult asyncResult)
325 {
326 try
327 {
328 var flushAsyncResult = (FlushAsyncResult) asyncResult;
329
330 if (!flushAsyncResult.IsCompleted)
331 {
332 var waitHandle = flushAsyncResult.AsyncWaitHandle;
333 waitHandle.WaitOne(); // blocking INFINITEly
334 waitHandle.Close();
335 }
336
337 if (flushAsyncResult.AsyncException != null)
338 {
339 throw flushAsyncResult.AsyncException;
340 }
341 } finally
342 {
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 {
363 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
364 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 {
378 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
379 }
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;
390 private readonly Object _state;
391
392 public FlushAsyncResult(AsyncCallback cbMethod, Object state)
393 {
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 }
418 private readonly Object _locker = new Object();
419 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
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000455#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 }
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000473#endregion
Jens Geyerd5436f52014-10-03 19:50:38 +0200474 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000475}