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.Threading;
30 using System.Threading.Tasks;
31 using System.Collections.Specialized;
32 using System.Net.Http.Headers;
35 namespace System.Net.Http
37 public class HttpClientHandler : HttpMessageHandler
39 static long groupCounter;
41 bool allowAutoRedirect;
42 DecompressionMethods automaticDecompression;
43 CookieContainer cookieContainer;
44 ICredentials credentials;
45 int maxAutomaticRedirections;
46 long maxRequestContentBufferSize;
50 bool useDefaultCredentials;
52 ClientCertificateOption certificate;
54 string connectionGroupName;
57 public HttpClientHandler ()
59 allowAutoRedirect = true;
60 maxAutomaticRedirections = 50;
61 maxRequestContentBufferSize = int.MaxValue;
64 connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
67 internal void EnsureModifiability ()
70 throw new InvalidOperationException (
71 "This instance has already started one or more requests. " +
72 "Properties can only be modified before sending the first request.");
75 public bool AllowAutoRedirect {
77 return allowAutoRedirect;
80 EnsureModifiability ();
81 allowAutoRedirect = value;
85 public DecompressionMethods AutomaticDecompression {
87 return automaticDecompression;
90 EnsureModifiability ();
91 automaticDecompression = value;
95 public ClientCertificateOption ClientCertificateOptions {
100 EnsureModifiability ();
105 public CookieContainer CookieContainer {
107 return cookieContainer ?? (cookieContainer = new CookieContainer ());
110 EnsureModifiability ();
111 cookieContainer = value;
115 public ICredentials Credentials {
120 EnsureModifiability ();
125 public int MaxAutomaticRedirections {
127 return maxAutomaticRedirections;
130 EnsureModifiability ();
132 throw new ArgumentOutOfRangeException ();
134 maxAutomaticRedirections = value;
138 public long MaxRequestContentBufferSize {
140 return maxRequestContentBufferSize;
143 EnsureModifiability ();
145 throw new ArgumentOutOfRangeException ();
147 maxRequestContentBufferSize = value;
151 public bool PreAuthenticate {
153 return preAuthenticate;
156 EnsureModifiability ();
157 preAuthenticate = value;
161 public IWebProxy Proxy {
166 EnsureModifiability ();
168 throw new InvalidOperationException ();
174 public virtual bool SupportsAutomaticDecompression {
180 public virtual bool SupportsProxy {
186 public virtual bool SupportsRedirectConfiguration {
192 public bool UseCookies {
197 EnsureModifiability ();
202 public bool UseDefaultCredentials {
204 return useDefaultCredentials;
207 EnsureModifiability ();
208 useDefaultCredentials = value;
212 public bool UseProxy {
217 EnsureModifiability ();
222 protected override void Dispose (bool disposing)
224 if (disposing && !disposed) {
225 Volatile.Write (ref disposed, true);
226 ServicePointManager.CloseConnectionGroup (connectionGroupName);
229 base.Dispose (disposing);
232 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
234 var wr = new HttpWebRequest (request.RequestUri);
235 wr.ThrowOnError = false;
236 wr.AllowWriteStreamBuffering = false;
238 wr.ConnectionGroupName = connectionGroupName;
239 wr.Method = request.Method.Method;
240 wr.ProtocolVersion = request.Version;
242 if (wr.ProtocolVersion == HttpVersion.Version10) {
243 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
245 wr.KeepAlive = request.Headers.ConnectionClose != true;
248 wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
250 if (allowAutoRedirect) {
251 wr.AllowAutoRedirect = true;
252 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
254 wr.AllowAutoRedirect = false;
257 wr.AutomaticDecompression = automaticDecompression;
258 wr.PreAuthenticate = preAuthenticate;
261 // It cannot be null or allowAutoRedirect won't work
262 wr.CookieContainer = CookieContainer;
265 if (useDefaultCredentials) {
266 wr.UseDefaultCredentials = true;
268 wr.Credentials = credentials;
275 // Add request headers
276 var headers = wr.Headers;
277 foreach (var header in request.Headers) {
278 var values = header.Value;
279 if (header.Key == "Host") {
281 // Host must be explicitly set for HttpWebRequest
283 wr.Host = request.Headers.Host;
287 if (header.Key == "Transfer-Encoding") {
288 // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
289 // from ContentLength by HttpWebRequest
290 values = values.Where (l => l != "chunked");
293 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
294 if (values_formated == null)
297 headers.AddInternal (header.Key, values_formated);
303 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
305 var response = new HttpResponseMessage (wr.StatusCode);
306 response.RequestMessage = requestMessage;
307 response.ReasonPhrase = wr.StatusDescription;
308 response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
310 var headers = wr.Headers;
311 for (int i = 0; i < headers.Count; ++i) {
312 var key = headers.GetKey(i);
313 var value = headers.GetValues (i);
315 HttpHeaders item_headers;
316 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
317 item_headers = response.Content.Headers;
319 item_headers = response.Headers;
321 item_headers.TryAddWithoutValidation (key, value);
324 requestMessage.RequestUri = wr.ResponseUri;
329 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
332 throw new ObjectDisposedException (GetType ().ToString ());
334 Volatile.Write (ref sentRequest, true);
335 var wrequest = CreateWebRequest (request);
336 HttpWebResponse wresponse = null;
339 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
340 var content = request.Content;
341 if (content != null) {
342 var headers = wrequest.Headers;
344 foreach (var header in content.Headers) {
345 foreach (var value in header.Value) {
346 headers.AddInternal (header.Key, value);
351 // Content length has to be set because HttpWebRequest is running without buffering
353 var contentLength = content.Headers.ContentLength;
354 if (contentLength != null) {
355 wrequest.ContentLength = contentLength.Value;
357 await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
358 wrequest.ContentLength = content.Headers.ContentLength.Value;
361 wrequest.ResendContentFactory = content.CopyTo;
363 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
364 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
365 } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
366 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
367 // This fixes the issue that's been reported on the forums:
368 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
369 wrequest.ContentLength = 0;
372 wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
374 } catch (WebException we) {
375 if (we.Status != WebExceptionStatus.RequestCanceled)
379 if (cancellationToken.IsCancellationRequested) {
380 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
381 cancelled.SetCanceled ();
382 return await cancelled.Task;
385 return CreateResponseMessage (wresponse, request, cancellationToken);