blob: a56a3e89eb8e8a903159b84464782a9e086f105c [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;
Bryan Duxbury62359472010-06-24 20:34:34 +000029
30namespace Thrift.Transport
31{
Jens Geyerc0ad3682014-05-08 22:31:34 +020032
33 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
Jens Geyerc0ad3682014-05-08 22:31:34 +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 {
66 connectTimeout = value;
67 }
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
Jens Geyerd5436f52014-10-03 19:50:38 +020078 public IDictionary<String, String> CustomHeaders
79 {
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 {
142 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
143 }
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();
Bryan Duxbury62359472010-06-24 20:34:34 +0000169
Jens Geyerd5436f52014-10-03 19:50:38 +0200170 byte[] data = outputStream.ToArray();
171 connection.ContentLength = data.Length;
Bryan Duxbury62359472010-06-24 20:34:34 +0000172
Jens Geyerd5436f52014-10-03 19:50:38 +0200173 using (Stream requestStream = connection.GetRequestStream())
174 {
175 requestStream.Write(data, 0, data.Length);
Jens Geyer7dce7b22014-07-25 22:00:44 +0200176
Jens Geyerd5436f52014-10-03 19:50:38 +0200177 // Resolve HTTP hang that can happens after successive calls by making sure
178 // that we release the response and response stream. To support this, we copy
179 // the response to a memory stream.
180 using (var response = connection.GetResponse())
181 {
182 using (var responseStream = response.GetResponseStream())
183 {
184 // Copy the response to a memory stream so that we can
185 // cleanly close the response and response stream.
186 inputStream = new MemoryStream();
187 byte[] buffer = new byte[8096];
188 int bytesRead;
189 while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
190 {
191 inputStream.Write (buffer, 0, bytesRead);
192 }
193 inputStream.Seek(0, 0);
194 }
195 }
196 }
197 }
198 catch (IOException iox)
199 {
200 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
201 }
202 catch (WebException wx)
203 {
204 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx);
205 }
206 }
Roger Meier284a9b52011-12-08 13:39:56 +0000207#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200208 private HttpWebRequest CreateRequest()
209 {
210 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
Bryan Duxbury62359472010-06-24 20:34:34 +0000211
Jens Geyerc0ad3682014-05-08 22:31:34 +0200212
Roger Meier284a9b52011-12-08 13:39:56 +0000213#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200214 // Adding certificates through code is not supported with WP7 Silverlight
215 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
216 connection.ClientCertificates.AddRange(certificates);
Jens Geyerc0ad3682014-05-08 22:31:34 +0200217
Jens Geyerd5436f52014-10-03 19:50:38 +0200218 if (connectTimeout > 0)
219 {
220 connection.Timeout = connectTimeout;
221 }
222 if (readTimeout > 0)
223 {
224 connection.ReadWriteTimeout = readTimeout;
225 }
Roger Meier284a9b52011-12-08 13:39:56 +0000226#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200227 // Make the request
228 connection.ContentType = "application/x-thrift";
229 connection.Accept = "application/x-thrift";
230 connection.UserAgent = "C#/THttpClient";
231 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000232#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200233 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000234#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000235
Roger Meier284a9b52011-12-08 13:39:56 +0000236 //add custom headers here
Jens Geyerd5436f52014-10-03 19:50:38 +0200237 foreach (KeyValuePair<string, string> item in customHeaders)
238 {
Roger Meier284a9b52011-12-08 13:39:56 +0000239#if !SILVERLIGHT
Jens Geyerd5436f52014-10-03 19:50:38 +0200240 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000241#else
242 connection.Headers[item.Key] = item.Value;
243#endif
Jens Geyerd5436f52014-10-03 19:50:38 +0200244 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000245
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000246#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000247 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000248#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000249
Roger Meier284a9b52011-12-08 13:39:56 +0000250 return connection;
Jens Geyerd5436f52014-10-03 19:50:38 +0200251 }
Roger Meier284a9b52011-12-08 13:39:56 +0000252
Roger Meier284a9b52011-12-08 13:39:56 +0000253 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
254 {
255 // Extract request and reset buffer
256 var data = outputStream.ToArray();
257
258 //requestBuffer_ = new MemoryStream();
259
260 try
261 {
262 // Create connection object
263 var flushAsyncResult = new FlushAsyncResult(callback, state);
264 flushAsyncResult.Connection = CreateRequest();
265
266 flushAsyncResult.Data = data;
267
268
269 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
270 return flushAsyncResult;
271
272 }
273 catch (IOException iox)
274 {
275 throw new TTransportException(iox.ToString());
276 }
277 }
278
279 public override void EndFlush(IAsyncResult asyncResult)
280 {
281 try
282 {
283 var flushAsyncResult = (FlushAsyncResult) asyncResult;
284
285 if (!flushAsyncResult.IsCompleted)
286 {
287 var waitHandle = flushAsyncResult.AsyncWaitHandle;
288 waitHandle.WaitOne(); // blocking INFINITEly
289 waitHandle.Close();
290 }
291
292 if (flushAsyncResult.AsyncException != null)
293 {
294 throw flushAsyncResult.AsyncException;
295 }
296 } finally
297 {
298 outputStream = new MemoryStream();
299 }
300
301 }
302
Roger Meier284a9b52011-12-08 13:39:56 +0000303 private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
304 {
305 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
306 try
307 {
308 var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
309 reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
310 reqStream.Flush();
311 reqStream.Close();
312
313 // Start the asynchronous operation to get the response
314 flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
315 }
316 catch (Exception exception)
317 {
318 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
319 flushAsyncResult.UpdateStatusToComplete();
320 flushAsyncResult.NotifyCallbackWhenAvailable();
321 }
322 }
323
324 private void GetResponseCallback(IAsyncResult asynchronousResult)
325 {
326 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
327 try
328 {
329 inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
330 }
331 catch (Exception exception)
332 {
333 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
334 }
335 flushAsyncResult.UpdateStatusToComplete();
336 flushAsyncResult.NotifyCallbackWhenAvailable();
337 }
338
339 // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
340 class FlushAsyncResult : IAsyncResult
341 {
342 private volatile Boolean _isCompleted;
343 private ManualResetEvent _evt;
344 private readonly AsyncCallback _cbMethod;
345 private readonly Object _state;
346
347 public FlushAsyncResult(AsyncCallback cbMethod, Object state)
348 {
349 _cbMethod = cbMethod;
350 _state = state;
351 }
352
353 internal byte[] Data { get; set; }
354 internal HttpWebRequest Connection { get; set; }
355 internal TTransportException AsyncException { get; set; }
356
357 public object AsyncState
358 {
359 get { return _state; }
360 }
361 public WaitHandle AsyncWaitHandle
362 {
363 get { return GetEvtHandle(); }
364 }
365 public bool CompletedSynchronously
366 {
367 get { return false; }
368 }
369 public bool IsCompleted
370 {
371 get { return _isCompleted; }
372 }
373 private readonly Object _locker = new Object();
374 private ManualResetEvent GetEvtHandle()
375 {
376 lock (_locker)
377 {
378 if (_evt == null)
379 {
380 _evt = new ManualResetEvent(false);
381 }
382 if (_isCompleted)
383 {
384 _evt.Set();
385 }
386 }
387 return _evt;
388 }
389 internal void UpdateStatusToComplete()
390 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200391 _isCompleted = true; //1. set _iscompleted to true
Roger Meier284a9b52011-12-08 13:39:56 +0000392 lock (_locker)
393 {
394 if (_evt != null)
395 {
Jens Geyerd5436f52014-10-03 19:50:38 +0200396 _evt.Set(); //2. set the event, when it exists
Roger Meier284a9b52011-12-08 13:39:56 +0000397 }
398 }
399 }
400
401 internal void NotifyCallbackWhenAvailable()
402 {
403 if (_cbMethod != null)
404 {
405 _cbMethod(this);
406 }
407 }
408 }
409
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000410#region " IDisposable Support "
Jens Geyerd5436f52014-10-03 19:50:38 +0200411 private bool _IsDisposed;
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000412
Jens Geyerd5436f52014-10-03 19:50:38 +0200413 // IDisposable
414 protected override void Dispose(bool disposing)
415 {
416 if (!_IsDisposed)
417 {
418 if (disposing)
419 {
420 if (inputStream != null)
421 inputStream.Dispose();
422 if (outputStream != null)
423 outputStream.Dispose();
424 }
425 }
426 _IsDisposed = true;
427 }
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000428#endregion
Jens Geyerd5436f52014-10-03 19:50:38 +0200429 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000430}