blob: 2acf4683e55a5cba1035f06d2a1928f28ef49439 [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 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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.
18 *
19 *
20 */
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 Geyerc0ad3682014-05-08 22:31:34 +020051 public THttpClient(Uri u)
52 : this(u, Enumerable.Empty<X509Certificate>())
Bryan Duxbury62359472010-06-24 20:34:34 +000053 {
Bryan Duxbury62359472010-06-24 20:34:34 +000054 }
55
Jens Geyerc0ad3682014-05-08 22:31:34 +020056 public THttpClient(Uri u, IEnumerable<X509Certificate> certificates)
57 {
58 uri = u;
59 this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
60 }
61
62 public int ConnectTimeout
Bryan Duxbury62359472010-06-24 20:34:34 +000063 {
64 set
65 {
66 connectTimeout = value;
67 }
68 }
69
70 public int ReadTimeout
71 {
72 set
73 {
74 readTimeout = value;
75 }
76 }
77
78 public IDictionary<String, String> CustomHeaders
79 {
80 get
81 {
82 return customHeaders;
83 }
84 }
85
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
Bryan Duxbury62359472010-06-24 20:34:34 +000096 public override bool IsOpen
97 {
98 get
99 {
100 return true;
101 }
102 }
103
104 public override void Open()
105 {
106 }
107
108 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 }
121
122 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 }
128
129 try
130 {
131 int ret = inputStream.Read(buf, off, len);
132
133 if (ret == -1)
134 {
135 throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available");
136 }
137
138 return ret;
139 }
140 catch (IOException iox)
141 {
142 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
143 }
144 }
145
146 public override void Write(byte[] buf, int off, int len)
147 {
148 outputStream.Write(buf, off, len);
149 }
150
Roger Meier284a9b52011-12-08 13:39:56 +0000151#if !SILVERLIGHT
Bryan Duxbury62359472010-06-24 20:34:34 +0000152 public override void Flush()
153 {
Bryan Duxburyea67a782010-08-06 17:50:51 +0000154 try
155 {
156 SendRequest();
157 }
158 finally
159 {
160 outputStream = new MemoryStream();
161 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000162 }
163
164 private void SendRequest()
165 {
166 try
167 {
168 HttpWebRequest connection = CreateRequest();
169
170 byte[] data = outputStream.ToArray();
171 connection.ContentLength = data.Length;
172
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000173 using (Stream requestStream = connection.GetRequestStream())
174 {
175 requestStream.Write(data, 0, data.Length);
176 inputStream = connection.GetResponse().GetResponseStream();
177 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000178 }
179 catch (IOException iox)
180 {
181 throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
182 }
183 catch (WebException wx)
184 {
185 throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx);
186 }
187 }
Roger Meier284a9b52011-12-08 13:39:56 +0000188#endif
Jens Geyerb080f682014-02-22 21:10:45 +0100189 private HttpWebRequest CreateRequest()
Bryan Duxbury62359472010-06-24 20:34:34 +0000190 {
191 HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
192
Jens Geyerc0ad3682014-05-08 22:31:34 +0200193
Roger Meier284a9b52011-12-08 13:39:56 +0000194#if !SILVERLIGHT
Jens Geyerc0ad3682014-05-08 22:31:34 +0200195 // Adding certificates through code is not supported with WP7 Silverlight
196 // see "Windows Phone 7 and Certificates_FINAL_121610.pdf"
197 connection.ClientCertificates.AddRange(certificates);
198
Bryan Duxbury62359472010-06-24 20:34:34 +0000199 if (connectTimeout > 0)
200 {
201 connection.Timeout = connectTimeout;
202 }
203 if (readTimeout > 0)
204 {
205 connection.ReadWriteTimeout = readTimeout;
206 }
Roger Meier284a9b52011-12-08 13:39:56 +0000207#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000208 // Make the request
209 connection.ContentType = "application/x-thrift";
210 connection.Accept = "application/x-thrift";
211 connection.UserAgent = "C#/THttpClient";
212 connection.Method = "POST";
Roger Meier284a9b52011-12-08 13:39:56 +0000213#if !SILVERLIGHT
Bryan Duxbury62359472010-06-24 20:34:34 +0000214 connection.ProtocolVersion = HttpVersion.Version10;
Roger Meier284a9b52011-12-08 13:39:56 +0000215#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000216
Roger Meier284a9b52011-12-08 13:39:56 +0000217 //add custom headers here
Bryan Duxbury62359472010-06-24 20:34:34 +0000218 foreach (KeyValuePair<string, string> item in customHeaders)
219 {
Roger Meier284a9b52011-12-08 13:39:56 +0000220#if !SILVERLIGHT
Bryan Duxbury62359472010-06-24 20:34:34 +0000221 connection.Headers.Add(item.Key, item.Value);
Roger Meier284a9b52011-12-08 13:39:56 +0000222#else
223 connection.Headers[item.Key] = item.Value;
224#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000225 }
226
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000227#if !SILVERLIGHT
Jake Farrell5e022aa2012-05-18 00:33:54 +0000228 connection.Proxy = proxy;
Jake Farrell12ac2ac2011-12-09 02:21:37 +0000229#endif
Bryan Duxbury62359472010-06-24 20:34:34 +0000230
Roger Meier284a9b52011-12-08 13:39:56 +0000231 return connection;
Bryan Duxbury62359472010-06-24 20:34:34 +0000232 }
Roger Meier284a9b52011-12-08 13:39:56 +0000233
Roger Meier284a9b52011-12-08 13:39:56 +0000234 public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
235 {
236 // Extract request and reset buffer
237 var data = outputStream.ToArray();
238
239 //requestBuffer_ = new MemoryStream();
240
241 try
242 {
243 // Create connection object
244 var flushAsyncResult = new FlushAsyncResult(callback, state);
245 flushAsyncResult.Connection = CreateRequest();
246
247 flushAsyncResult.Data = data;
248
249
250 flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
251 return flushAsyncResult;
252
253 }
254 catch (IOException iox)
255 {
256 throw new TTransportException(iox.ToString());
257 }
258 }
259
260 public override void EndFlush(IAsyncResult asyncResult)
261 {
262 try
263 {
264 var flushAsyncResult = (FlushAsyncResult) asyncResult;
265
266 if (!flushAsyncResult.IsCompleted)
267 {
268 var waitHandle = flushAsyncResult.AsyncWaitHandle;
269 waitHandle.WaitOne(); // blocking INFINITEly
270 waitHandle.Close();
271 }
272
273 if (flushAsyncResult.AsyncException != null)
274 {
275 throw flushAsyncResult.AsyncException;
276 }
277 } finally
278 {
279 outputStream = new MemoryStream();
280 }
281
282 }
283
Roger Meier284a9b52011-12-08 13:39:56 +0000284 private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
285 {
286 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
287 try
288 {
289 var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
290 reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
291 reqStream.Flush();
292 reqStream.Close();
293
294 // Start the asynchronous operation to get the response
295 flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
296 }
297 catch (Exception exception)
298 {
299 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
300 flushAsyncResult.UpdateStatusToComplete();
301 flushAsyncResult.NotifyCallbackWhenAvailable();
302 }
303 }
304
305 private void GetResponseCallback(IAsyncResult asynchronousResult)
306 {
307 var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
308 try
309 {
310 inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
311 }
312 catch (Exception exception)
313 {
314 flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
315 }
316 flushAsyncResult.UpdateStatusToComplete();
317 flushAsyncResult.NotifyCallbackWhenAvailable();
318 }
319
320 // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
321 class FlushAsyncResult : IAsyncResult
322 {
323 private volatile Boolean _isCompleted;
324 private ManualResetEvent _evt;
325 private readonly AsyncCallback _cbMethod;
326 private readonly Object _state;
327
328 public FlushAsyncResult(AsyncCallback cbMethod, Object state)
329 {
330 _cbMethod = cbMethod;
331 _state = state;
332 }
333
334 internal byte[] Data { get; set; }
335 internal HttpWebRequest Connection { get; set; }
336 internal TTransportException AsyncException { get; set; }
337
338 public object AsyncState
339 {
340 get { return _state; }
341 }
342 public WaitHandle AsyncWaitHandle
343 {
344 get { return GetEvtHandle(); }
345 }
346 public bool CompletedSynchronously
347 {
348 get { return false; }
349 }
350 public bool IsCompleted
351 {
352 get { return _isCompleted; }
353 }
354 private readonly Object _locker = new Object();
355 private ManualResetEvent GetEvtHandle()
356 {
357 lock (_locker)
358 {
359 if (_evt == null)
360 {
361 _evt = new ManualResetEvent(false);
362 }
363 if (_isCompleted)
364 {
365 _evt.Set();
366 }
367 }
368 return _evt;
369 }
370 internal void UpdateStatusToComplete()
371 {
372 _isCompleted = true; //1. set _iscompleted to true
373 lock (_locker)
374 {
375 if (_evt != null)
376 {
377 _evt.Set(); //2. set the event, when it exists
378 }
379 }
380 }
381
382 internal void NotifyCallbackWhenAvailable()
383 {
384 if (_cbMethod != null)
385 {
386 _cbMethod(this);
387 }
388 }
389 }
390
Roger Meierb1ec4cc2012-04-11 21:21:41 +0000391#region " IDisposable Support "
392 private bool _IsDisposed;
393
394 // IDisposable
395 protected override void Dispose(bool disposing)
396 {
397 if (!_IsDisposed)
398 {
399 if (disposing)
400 {
401 if (inputStream != null)
402 inputStream.Dispose();
403 if (outputStream != null)
404 outputStream.Dispose();
405 }
406 }
407 _IsDisposed = true;
408 }
409#endregion
410 }
Bryan Duxbury62359472010-06-24 20:34:34 +0000411}