2 // HttpClientHandler.cs
5 // Marek Safar <marek.safar@gmail.com>
7 // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections.Generic;
30 using System.Security.Authentication;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
33 using System.Threading.Tasks;
34 using System.Collections.Specialized;
35 using System.Net.Http.Headers;
36 using System.Net.Security;
39 namespace System.Net.Http
41 public class HttpClientHandler : HttpMessageHandler
43 static long groupCounter;
45 bool allowAutoRedirect;
46 DecompressionMethods automaticDecompression;
47 CookieContainer cookieContainer;
48 ICredentials credentials;
49 int maxAutomaticRedirections;
50 long maxRequestContentBufferSize;
54 bool useDefaultCredentials;
56 ClientCertificateOption certificate;
58 string connectionGroupName;
61 public HttpClientHandler ()
63 allowAutoRedirect = true;
64 maxAutomaticRedirections = 50;
65 maxRequestContentBufferSize = int.MaxValue;
68 connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
71 internal void EnsureModifiability ()
74 throw new InvalidOperationException (
75 "This instance has already started one or more requests. " +
76 "Properties can only be modified before sending the first request.");
79 public bool AllowAutoRedirect {
81 return allowAutoRedirect;
84 EnsureModifiability ();
85 allowAutoRedirect = value;
89 public DecompressionMethods AutomaticDecompression {
91 return automaticDecompression;
94 EnsureModifiability ();
95 automaticDecompression = value;
99 public ClientCertificateOption ClientCertificateOptions {
104 EnsureModifiability ();
109 public CookieContainer CookieContainer {
111 return cookieContainer ?? (cookieContainer = new CookieContainer ());
114 EnsureModifiability ();
115 cookieContainer = value;
119 public ICredentials Credentials {
124 EnsureModifiability ();
129 public int MaxAutomaticRedirections {
131 return maxAutomaticRedirections;
134 EnsureModifiability ();
136 throw new ArgumentOutOfRangeException ();
138 maxAutomaticRedirections = value;
142 public long MaxRequestContentBufferSize {
144 return maxRequestContentBufferSize;
147 EnsureModifiability ();
149 throw new ArgumentOutOfRangeException ();
151 maxRequestContentBufferSize = value;
155 public bool PreAuthenticate {
157 return preAuthenticate;
160 EnsureModifiability ();
161 preAuthenticate = value;
165 public IWebProxy Proxy {
170 EnsureModifiability ();
172 throw new InvalidOperationException ();
178 public virtual bool SupportsAutomaticDecompression {
184 public virtual bool SupportsProxy {
190 public virtual bool SupportsRedirectConfiguration {
196 public bool UseCookies {
201 EnsureModifiability ();
206 public bool UseDefaultCredentials {
208 return useDefaultCredentials;
211 EnsureModifiability ();
212 useDefaultCredentials = value;
216 public bool UseProxy {
221 EnsureModifiability ();
226 protected override void Dispose (bool disposing)
228 if (disposing && !disposed) {
229 Volatile.Write (ref disposed, true);
230 ServicePointManager.CloseConnectionGroup (connectionGroupName);
233 base.Dispose (disposing);
236 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
238 var wr = new HttpWebRequest (request.RequestUri);
239 wr.ThrowOnError = false;
240 wr.AllowWriteStreamBuffering = false;
242 wr.ConnectionGroupName = connectionGroupName;
243 wr.Method = request.Method.Method;
244 wr.ProtocolVersion = request.Version;
246 if (wr.ProtocolVersion == HttpVersion.Version10) {
247 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
249 wr.KeepAlive = request.Headers.ConnectionClose != true;
252 if (allowAutoRedirect) {
253 wr.AllowAutoRedirect = true;
254 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
256 wr.AllowAutoRedirect = false;
259 wr.AutomaticDecompression = automaticDecompression;
260 wr.PreAuthenticate = preAuthenticate;
263 // It cannot be null or allowAutoRedirect won't work
264 wr.CookieContainer = CookieContainer;
267 if (useDefaultCredentials) {
268 wr.UseDefaultCredentials = true;
270 wr.Credentials = credentials;
276 // Disables default WebRequest.DefaultWebProxy value
280 wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
282 // Add request headers
283 var headers = wr.Headers;
284 foreach (var header in request.Headers) {
285 var values = header.Value;
286 if (header.Key == "Host") {
288 // Host must be explicitly set for HttpWebRequest
290 wr.Host = request.Headers.Host;
294 if (header.Key == "Transfer-Encoding") {
296 // Chunked Transfer-Encoding is set for HttpWebRequest later when Content length is checked
298 values = values.Where (l => l != "chunked");
301 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
302 if (values_formated == null)
305 headers.AddInternal (header.Key, values_formated);
311 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
313 var response = new HttpResponseMessage (wr.StatusCode);
314 response.RequestMessage = requestMessage;
315 response.ReasonPhrase = wr.StatusDescription;
316 response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
318 var headers = wr.Headers;
319 for (int i = 0; i < headers.Count; ++i) {
320 var key = headers.GetKey(i);
321 var value = headers.GetValues (i);
323 HttpHeaders item_headers;
324 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
325 item_headers = response.Content.Headers;
327 item_headers = response.Headers;
329 item_headers.TryAddWithoutValidation (key, value);
332 requestMessage.RequestUri = wr.ResponseUri;
337 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
340 throw new ObjectDisposedException (GetType ().ToString ());
342 Volatile.Write (ref sentRequest, true);
343 var wrequest = CreateWebRequest (request);
344 HttpWebResponse wresponse = null;
347 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
348 var content = request.Content;
349 if (content != null) {
350 var headers = wrequest.Headers;
352 foreach (var header in content.Headers) {
353 foreach (var value in header.Value) {
354 headers.AddInternal (header.Key, value);
358 if (request.Headers.TransferEncodingChunked == true) {
359 wrequest.SendChunked = true;
362 // Content length has to be set because HttpWebRequest is running without buffering
364 var contentLength = content.Headers.ContentLength;
365 if (contentLength != null) {
366 wrequest.ContentLength = contentLength.Value;
368 if (MaxRequestContentBufferSize == 0)
369 throw new InvalidOperationException ("The content length of the request content can't be determined. Either set TransferEncodingChunked to true, load content into buffer, or set MaxRequestContentBufferSize.");
371 await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
372 wrequest.ContentLength = content.Headers.ContentLength.Value;
376 wrequest.ResendContentFactory = content.CopyTo;
378 using (var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false)) {
379 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
381 } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
382 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
383 // This fixes the issue that's been reported on the forums:
384 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
385 wrequest.ContentLength = 0;
388 wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
390 } catch (WebException we) {
391 if (we.Status != WebExceptionStatus.RequestCanceled)
392 throw new HttpRequestException ("An error occurred while sending the request", we);
393 } catch (System.IO.IOException ex) {
394 throw new HttpRequestException ("An error occurred while sending the request", ex);
397 if (cancellationToken.IsCancellationRequested) {
398 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
399 cancelled.SetCanceled ();
400 return await cancelled.Task;
403 return CreateResponseMessage (wresponse, request, cancellationToken);
406 public bool CheckCertificateRevocationList {
408 throw new NotImplementedException ();
411 throw new NotImplementedException ();
415 public X509CertificateCollection ClientCertificates {
417 throw new NotImplementedException ();
421 public ICredentials DefaultProxyCredentials {
423 throw new NotImplementedException ();
426 throw new NotImplementedException ();
430 public int MaxConnectionsPerServer {
432 throw new NotImplementedException ();
435 throw new NotImplementedException ();
439 public int MaxResponseHeadersLength {
441 throw new NotImplementedException ();
444 throw new NotImplementedException ();
448 public IDictionary<string,object> Properties {
450 throw new NotImplementedException ();
454 public Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool> ServerCertificateCustomValidationCallback {
456 throw new NotImplementedException ();
459 throw new NotImplementedException ();
463 public SslProtocols SslProtocols {
465 throw new NotImplementedException ();
468 throw new NotImplementedException ();