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") {
295 // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
296 // from ContentLength by HttpWebRequest
297 values = values.Where (l => l != "chunked");
300 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
301 if (values_formated == null)
304 headers.AddInternal (header.Key, values_formated);
310 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
312 var response = new HttpResponseMessage (wr.StatusCode);
313 response.RequestMessage = requestMessage;
314 response.ReasonPhrase = wr.StatusDescription;
315 response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
317 var headers = wr.Headers;
318 for (int i = 0; i < headers.Count; ++i) {
319 var key = headers.GetKey(i);
320 var value = headers.GetValues (i);
322 HttpHeaders item_headers;
323 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
324 item_headers = response.Content.Headers;
326 item_headers = response.Headers;
328 item_headers.TryAddWithoutValidation (key, value);
331 requestMessage.RequestUri = wr.ResponseUri;
336 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
339 throw new ObjectDisposedException (GetType ().ToString ());
341 Volatile.Write (ref sentRequest, true);
342 var wrequest = CreateWebRequest (request);
343 HttpWebResponse wresponse = null;
346 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
347 var content = request.Content;
348 if (content != null) {
349 var headers = wrequest.Headers;
351 foreach (var header in content.Headers) {
352 foreach (var value in header.Value) {
353 headers.AddInternal (header.Key, value);
358 // Content length has to be set because HttpWebRequest is running without buffering
360 var contentLength = content.Headers.ContentLength;
361 if (contentLength != null) {
362 wrequest.ContentLength = contentLength.Value;
364 await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
365 wrequest.ContentLength = content.Headers.ContentLength.Value;
368 wrequest.ResendContentFactory = content.CopyTo;
370 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
371 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
372 } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
373 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
374 // This fixes the issue that's been reported on the forums:
375 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
376 wrequest.ContentLength = 0;
379 wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
381 } catch (WebException we) {
382 if (we.Status != WebExceptionStatus.RequestCanceled)
383 throw new HttpRequestException ("An error occurred while sending the request", we);
384 } catch (System.IO.IOException ex) {
385 throw new HttpRequestException ("An error occurred while sending the request", ex);
388 if (cancellationToken.IsCancellationRequested) {
389 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
390 cancelled.SetCanceled ();
391 return await cancelled.Task;
394 return CreateResponseMessage (wresponse, request, cancellationToken);
398 public bool CheckCertificateRevocationList {
400 throw new NotImplementedException ();
403 throw new NotImplementedException ();
407 public X509CertificateCollection ClientCertificates {
409 throw new NotImplementedException ();
413 public ICredentials DefaultProxyCredentials {
415 throw new NotImplementedException ();
418 throw new NotImplementedException ();
422 public int MaxConnectionsPerServer {
424 throw new NotImplementedException ();
427 throw new NotImplementedException ();
431 public int MaxResponseHeadersLength {
433 throw new NotImplementedException ();
436 throw new NotImplementedException ();
440 public IDictionary<string,object> Properties {
442 throw new NotImplementedException ();
446 public Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool> ServerCertificateCustomValidationCallback {
448 throw new NotImplementedException ();
451 throw new NotImplementedException ();
455 public SslProtocols SslProtocols {
457 throw new NotImplementedException ();
460 throw new NotImplementedException ();