]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | /** |
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 | ||
22 | using System; | |
23 | using System.Collections.Generic; | |
24 | using System.IO; | |
25 | using System.Net; | |
26 | using System.Threading; | |
27 | using System.Linq; | |
28 | using System.Security.Cryptography.X509Certificates; | |
29 | using System.IO.Compression; | |
30 | ||
31 | namespace Thrift.Transport | |
32 | { | |
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(); | |
39 | ||
40 | // Timeouts in milliseconds | |
41 | private int connectTimeout = 30000; | |
42 | ||
43 | private int readTimeout = 30000; | |
44 | ||
45 | private IDictionary<string, string> customHeaders = new Dictionary<string, string>(); | |
46 | private string userAgent = "C#/THttpClient"; | |
47 | ||
48 | #if !SILVERLIGHT | |
49 | private IWebProxy proxy = WebRequest.DefaultWebProxy; | |
50 | #endif | |
51 | ||
52 | public THttpClient(Uri u) | |
53 | : this(u, Enumerable.Empty<X509Certificate>()) | |
54 | { | |
55 | } | |
56 | public THttpClient(Uri u, string userAgent) | |
57 | : this(u, userAgent, Enumerable.Empty<X509Certificate>()) | |
58 | { | |
59 | } | |
60 | ||
61 | public THttpClient(Uri u, IEnumerable<X509Certificate> certificates) | |
62 | { | |
63 | uri = u; | |
64 | this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray(); | |
65 | } | |
66 | public THttpClient(Uri u, string userAgent, IEnumerable<X509Certificate> certificates) | |
67 | { | |
68 | uri = u; | |
69 | this.userAgent = userAgent; | |
70 | this.certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray(); | |
71 | } | |
72 | ||
73 | public int ConnectTimeout | |
74 | { | |
75 | set | |
76 | { | |
77 | connectTimeout = value; | |
78 | } | |
79 | } | |
80 | ||
81 | public int ReadTimeout | |
82 | { | |
83 | set | |
84 | { | |
85 | readTimeout = value; | |
86 | } | |
87 | } | |
88 | ||
89 | public IDictionary<string, string> CustomHeaders | |
90 | { | |
91 | get | |
92 | { | |
93 | return customHeaders; | |
94 | } | |
95 | } | |
96 | ||
97 | #if !SILVERLIGHT | |
98 | public IWebProxy Proxy | |
99 | { | |
100 | set | |
101 | { | |
102 | proxy = value; | |
103 | } | |
104 | } | |
105 | #endif | |
106 | ||
107 | public override bool IsOpen | |
108 | { | |
109 | get | |
110 | { | |
111 | return true; | |
112 | } | |
113 | } | |
114 | ||
115 | public override void Open() | |
116 | { | |
117 | } | |
118 | ||
119 | public override void Close() | |
120 | { | |
121 | if (inputStream != null) | |
122 | { | |
123 | inputStream.Close(); | |
124 | inputStream = null; | |
125 | } | |
126 | if (outputStream != null) | |
127 | { | |
128 | outputStream.Close(); | |
129 | outputStream = null; | |
130 | } | |
131 | } | |
132 | ||
133 | public override int Read(byte[] buf, int off, int len) | |
134 | { | |
135 | if (inputStream == null) | |
136 | { | |
137 | throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent"); | |
138 | } | |
139 | ||
140 | try | |
141 | { | |
142 | int ret = inputStream.Read(buf, off, len); | |
143 | ||
144 | if (ret == -1) | |
145 | { | |
146 | throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available"); | |
147 | } | |
148 | ||
149 | return ret; | |
150 | } | |
151 | catch (IOException iox) | |
152 | { | |
153 | throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox); | |
154 | } | |
155 | } | |
156 | ||
157 | public override void Write(byte[] buf, int off, int len) | |
158 | { | |
159 | outputStream.Write(buf, off, len); | |
160 | } | |
161 | ||
162 | #if !SILVERLIGHT | |
163 | public override void Flush() | |
164 | { | |
165 | try | |
166 | { | |
167 | SendRequest(); | |
168 | } | |
169 | finally | |
170 | { | |
171 | outputStream = new MemoryStream(); | |
172 | } | |
173 | } | |
174 | ||
175 | private void SendRequest() | |
176 | { | |
177 | try | |
178 | { | |
179 | HttpWebRequest connection = CreateRequest(); | |
180 | connection.Headers.Add("Accept-Encoding", "gzip, deflate"); | |
181 | ||
182 | byte[] data = outputStream.ToArray(); | |
183 | connection.ContentLength = data.Length; | |
184 | ||
185 | using (Stream requestStream = connection.GetRequestStream()) | |
186 | { | |
187 | requestStream.Write(data, 0, data.Length); | |
188 | ||
189 | // Resolve HTTP hang that can happens after successive calls by making sure | |
190 | // that we release the response and response stream. To support this, we copy | |
191 | // the response to a memory stream. | |
192 | using (var response = connection.GetResponse()) | |
193 | { | |
194 | using (var responseStream = response.GetResponseStream()) | |
195 | { | |
196 | // Copy the response to a memory stream so that we can | |
197 | // cleanly close the response and response stream. | |
198 | inputStream = new MemoryStream(); | |
199 | byte[] buffer = new byte[8192]; // multiple of 4096 | |
200 | int bytesRead; | |
201 | while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) | |
202 | { | |
203 | inputStream.Write(buffer, 0, bytesRead); | |
204 | } | |
205 | inputStream.Seek(0, 0); | |
206 | } | |
207 | ||
208 | var encodings = response.Headers.GetValues("Content-Encoding"); | |
209 | if (encodings != null) | |
210 | { | |
211 | foreach (var encoding in encodings) | |
212 | { | |
213 | switch (encoding) | |
214 | { | |
215 | case "gzip": | |
216 | DecompressGZipped(ref inputStream); | |
217 | break; | |
218 | case "deflate": | |
219 | DecompressDeflated(ref inputStream); | |
220 | break; | |
221 | default: | |
222 | break; | |
223 | } | |
224 | } | |
225 | } | |
226 | } | |
227 | } | |
228 | } | |
229 | catch (IOException iox) | |
230 | { | |
231 | throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox); | |
232 | } | |
233 | catch (WebException wx) | |
234 | { | |
235 | throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx, wx); | |
236 | } | |
237 | } | |
238 | ||
239 | private void DecompressDeflated(ref Stream inputStream) | |
240 | { | |
241 | var tmp = new MemoryStream(); | |
242 | using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress)) | |
243 | { | |
244 | decomp.CopyTo(tmp); | |
245 | } | |
246 | inputStream.Dispose(); | |
247 | inputStream = tmp; | |
248 | inputStream.Seek(0, 0); | |
249 | } | |
250 | ||
251 | private void DecompressGZipped(ref Stream inputStream) | |
252 | { | |
253 | var tmp = new MemoryStream(); | |
254 | using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress)) | |
255 | { | |
256 | decomp.CopyTo(tmp); | |
257 | } | |
258 | inputStream.Dispose(); | |
259 | inputStream = tmp; | |
260 | inputStream.Seek(0, 0); | |
261 | } | |
262 | #endif | |
263 | private HttpWebRequest CreateRequest() | |
264 | { | |
265 | HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri); | |
266 | ||
267 | ||
268 | #if !SILVERLIGHT | |
269 | // Adding certificates through code is not supported with WP7 Silverlight | |
270 | // see "Windows Phone 7 and Certificates_FINAL_121610.pdf" | |
271 | connection.ClientCertificates.AddRange(certificates); | |
272 | ||
273 | if (connectTimeout > 0) | |
274 | { | |
275 | connection.Timeout = connectTimeout; | |
276 | } | |
277 | if (readTimeout > 0) | |
278 | { | |
279 | connection.ReadWriteTimeout = readTimeout; | |
280 | } | |
281 | #endif | |
282 | // Make the request | |
283 | connection.ContentType = "application/x-thrift"; | |
284 | connection.Accept = "application/x-thrift"; | |
285 | connection.UserAgent = userAgent; | |
286 | connection.Method = "POST"; | |
287 | #if !SILVERLIGHT | |
288 | connection.ProtocolVersion = HttpVersion.Version10; | |
289 | #endif | |
290 | ||
291 | //add custom headers here | |
292 | foreach (KeyValuePair<string, string> item in customHeaders) | |
293 | { | |
294 | #if !SILVERLIGHT | |
295 | connection.Headers.Add(item.Key, item.Value); | |
296 | #else | |
297 | connection.Headers[item.Key] = item.Value; | |
298 | #endif | |
299 | } | |
300 | ||
301 | #if !SILVERLIGHT | |
302 | connection.Proxy = proxy; | |
303 | #endif | |
304 | ||
305 | return connection; | |
306 | } | |
307 | ||
308 | public override IAsyncResult BeginFlush(AsyncCallback callback, object state) | |
309 | { | |
310 | // Extract request and reset buffer | |
311 | var data = outputStream.ToArray(); | |
312 | ||
313 | //requestBuffer_ = new MemoryStream(); | |
314 | ||
315 | try | |
316 | { | |
317 | // Create connection object | |
318 | var flushAsyncResult = new FlushAsyncResult(callback, state); | |
319 | flushAsyncResult.Connection = CreateRequest(); | |
320 | ||
321 | flushAsyncResult.Data = data; | |
322 | ||
323 | ||
324 | flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult); | |
325 | return flushAsyncResult; | |
326 | ||
327 | } | |
328 | catch (IOException iox) | |
329 | { | |
330 | throw new TTransportException(iox.ToString(), iox); | |
331 | } | |
332 | } | |
333 | ||
334 | public override void EndFlush(IAsyncResult asyncResult) | |
335 | { | |
336 | try | |
337 | { | |
338 | var flushAsyncResult = (FlushAsyncResult)asyncResult; | |
339 | ||
340 | if (!flushAsyncResult.IsCompleted) | |
341 | { | |
342 | var waitHandle = flushAsyncResult.AsyncWaitHandle; | |
343 | waitHandle.WaitOne(); // blocking INFINITEly | |
344 | waitHandle.Close(); | |
345 | } | |
346 | ||
347 | if (flushAsyncResult.AsyncException != null) | |
348 | { | |
349 | throw flushAsyncResult.AsyncException; | |
350 | } | |
351 | } | |
352 | finally | |
353 | { | |
354 | outputStream = new MemoryStream(); | |
355 | } | |
356 | ||
357 | } | |
358 | ||
359 | private void GetRequestStreamCallback(IAsyncResult asynchronousResult) | |
360 | { | |
361 | var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState; | |
362 | try | |
363 | { | |
364 | var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult); | |
365 | reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length); | |
366 | reqStream.Flush(); | |
367 | reqStream.Close(); | |
368 | ||
369 | // Start the asynchronous operation to get the response | |
370 | flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult); | |
371 | } | |
372 | catch (Exception exception) | |
373 | { | |
374 | flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception); | |
375 | flushAsyncResult.UpdateStatusToComplete(); | |
376 | flushAsyncResult.NotifyCallbackWhenAvailable(); | |
377 | } | |
378 | } | |
379 | ||
380 | private void GetResponseCallback(IAsyncResult asynchronousResult) | |
381 | { | |
382 | var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState; | |
383 | try | |
384 | { | |
385 | inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream(); | |
386 | } | |
387 | catch (Exception exception) | |
388 | { | |
389 | flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception); | |
390 | } | |
391 | flushAsyncResult.UpdateStatusToComplete(); | |
392 | flushAsyncResult.NotifyCallbackWhenAvailable(); | |
393 | } | |
394 | ||
395 | // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx | |
396 | class FlushAsyncResult : IAsyncResult | |
397 | { | |
398 | private volatile Boolean _isCompleted; | |
399 | private ManualResetEvent _evt; | |
400 | private readonly AsyncCallback _cbMethod; | |
401 | private readonly object _state; | |
402 | ||
403 | public FlushAsyncResult(AsyncCallback cbMethod, object state) | |
404 | { | |
405 | _cbMethod = cbMethod; | |
406 | _state = state; | |
407 | } | |
408 | ||
409 | internal byte[] Data { get; set; } | |
410 | internal HttpWebRequest Connection { get; set; } | |
411 | internal TTransportException AsyncException { get; set; } | |
412 | ||
413 | public object AsyncState | |
414 | { | |
415 | get { return _state; } | |
416 | } | |
417 | public WaitHandle AsyncWaitHandle | |
418 | { | |
419 | get { return GetEvtHandle(); } | |
420 | } | |
421 | public bool CompletedSynchronously | |
422 | { | |
423 | get { return false; } | |
424 | } | |
425 | public bool IsCompleted | |
426 | { | |
427 | get { return _isCompleted; } | |
428 | } | |
429 | private readonly object _locker = new object(); | |
430 | private ManualResetEvent GetEvtHandle() | |
431 | { | |
432 | lock (_locker) | |
433 | { | |
434 | if (_evt == null) | |
435 | { | |
436 | _evt = new ManualResetEvent(false); | |
437 | } | |
438 | if (_isCompleted) | |
439 | { | |
440 | _evt.Set(); | |
441 | } | |
442 | } | |
443 | return _evt; | |
444 | } | |
445 | internal void UpdateStatusToComplete() | |
446 | { | |
447 | _isCompleted = true; //1. set _iscompleted to true | |
448 | lock (_locker) | |
449 | { | |
450 | if (_evt != null) | |
451 | { | |
452 | _evt.Set(); //2. set the event, when it exists | |
453 | } | |
454 | } | |
455 | } | |
456 | ||
457 | internal void NotifyCallbackWhenAvailable() | |
458 | { | |
459 | if (_cbMethod != null) | |
460 | { | |
461 | _cbMethod(this); | |
462 | } | |
463 | } | |
464 | } | |
465 | ||
466 | #region " IDisposable Support " | |
467 | private bool _IsDisposed; | |
468 | ||
469 | // IDisposable | |
470 | protected override void Dispose(bool disposing) | |
471 | { | |
472 | if (!_IsDisposed) | |
473 | { | |
474 | if (disposing) | |
475 | { | |
476 | if (inputStream != null) | |
477 | inputStream.Dispose(); | |
478 | if (outputStream != null) | |
479 | outputStream.Dispose(); | |
480 | } | |
481 | } | |
482 | _IsDisposed = true; | |
483 | } | |
484 | #endregion | |
485 | } | |
486 | } |