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;
34 namespace System.Net.Http
36 public class HttpClientHandler : HttpMessageHandler
38 static long groupCounter;
40 bool allowAutoRedirect;
41 DecompressionMethods automaticDecompression;
42 CookieContainer cookieContainer;
43 ICredentials credentials;
44 int maxAutomaticRedirections;
45 long maxRequestContentBufferSize;
49 bool useDefaultCredentials;
51 ClientCertificateOption certificate;
53 string connectionGroupName;
56 public HttpClientHandler ()
58 allowAutoRedirect = true;
59 maxAutomaticRedirections = 50;
60 maxRequestContentBufferSize = int.MaxValue;
63 connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
66 internal void EnsureModifiability ()
69 throw new InvalidOperationException (
70 "This instance has already started one or more requests. " +
71 "Properties can only be modified before sending the first request.");
74 public bool AllowAutoRedirect {
76 return allowAutoRedirect;
79 EnsureModifiability ();
80 allowAutoRedirect = value;
84 public DecompressionMethods AutomaticDecompression {
86 return automaticDecompression;
89 EnsureModifiability ();
90 automaticDecompression = value;
94 public ClientCertificateOption ClientCertificateOptions {
99 EnsureModifiability ();
104 public CookieContainer CookieContainer {
106 return cookieContainer ?? (cookieContainer = new CookieContainer ());
109 EnsureModifiability ();
110 cookieContainer = value;
114 public ICredentials Credentials {
119 EnsureModifiability ();
124 public int MaxAutomaticRedirections {
126 return maxAutomaticRedirections;
129 EnsureModifiability ();
131 throw new ArgumentOutOfRangeException ();
133 maxAutomaticRedirections = value;
137 public long MaxRequestContentBufferSize {
139 return maxRequestContentBufferSize;
142 EnsureModifiability ();
144 throw new ArgumentOutOfRangeException ();
146 maxRequestContentBufferSize = value;
150 public bool PreAuthenticate {
152 return preAuthenticate;
155 EnsureModifiability ();
156 preAuthenticate = value;
160 public IWebProxy Proxy {
165 EnsureModifiability ();
167 throw new InvalidOperationException ();
173 public virtual bool SupportsAutomaticDecompression {
179 public virtual bool SupportsProxy {
185 public virtual bool SupportsRedirectConfiguration {
191 public bool UseCookies {
196 EnsureModifiability ();
201 public bool UseDefaultCredentials {
203 return useDefaultCredentials;
206 EnsureModifiability ();
207 useDefaultCredentials = value;
211 public bool UseProxy {
216 EnsureModifiability ();
221 protected override void Dispose (bool disposing)
223 if (disposing && !disposed) {
224 Volatile.Write (ref disposed, true);
225 ServicePointManager.CloseConnectionGroup (connectionGroupName);
228 base.Dispose (disposing);
231 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
233 var wr = new HttpWebRequest (request.RequestUri);
234 wr.ThrowOnError = false;
235 wr.AllowWriteStreamBuffering = false;
237 wr.ConnectionGroupName = connectionGroupName;
238 wr.Method = request.Method.Method;
239 wr.ProtocolVersion = request.Version;
241 if (wr.ProtocolVersion == HttpVersion.Version10) {
242 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
244 wr.KeepAlive = request.Headers.ConnectionClose != true;
247 wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
249 if (allowAutoRedirect) {
250 wr.AllowAutoRedirect = true;
251 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
253 wr.AllowAutoRedirect = false;
256 wr.AutomaticDecompression = automaticDecompression;
257 wr.PreAuthenticate = preAuthenticate;
260 // It cannot be null or allowAutoRedirect won't work
261 wr.CookieContainer = CookieContainer;
264 if (useDefaultCredentials) {
265 wr.UseDefaultCredentials = true;
267 wr.Credentials = credentials;
274 // Add request headers
275 var headers = wr.Headers;
276 foreach (var header in request.Headers) {
277 foreach (var value in header.Value) {
278 headers.AddValue (header.Key, value);
285 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
287 var response = new HttpResponseMessage (wr.StatusCode);
288 response.RequestMessage = requestMessage;
289 response.ReasonPhrase = wr.StatusDescription;
290 response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
292 var headers = wr.Headers;
293 for (int i = 0; i < headers.Count; ++i) {
294 var key = headers.GetKey(i);
295 var value = headers.GetValues (i);
297 HttpHeaders item_headers;
298 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
299 item_headers = response.Content.Headers;
301 item_headers = response.Headers;
303 item_headers.TryAddWithoutValidation (key, value);
306 requestMessage.RequestUri = wr.ResponseUri;
311 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
314 throw new ObjectDisposedException (GetType ().ToString ());
316 Volatile.Write (ref sentRequest, true);
317 var wrequest = CreateWebRequest (request);
318 HttpWebResponse wresponse = null;
321 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
322 var content = request.Content;
323 if (content != null) {
324 var headers = wrequest.Headers;
326 foreach (var header in content.Headers) {
327 foreach (var value in header.Value) {
328 headers.AddValue (header.Key, value);
333 // Content length has to be set because HttpWebRequest is running without buffering
335 var contentLength = content.Headers.ContentLength;
336 if (contentLength != null) {
337 wrequest.ContentLength = contentLength.Value;
339 await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
340 wrequest.ContentLength = content.Headers.ContentLength.Value;
343 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
344 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
345 } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
346 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
347 // This fixes the issue that's been reported on the forums:
348 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
349 wrequest.ContentLength = 0;
352 wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
354 } catch (WebException we) {
355 if (we.Status != WebExceptionStatus.RequestCanceled)
359 if (cancellationToken.IsCancellationRequested) {
360 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
361 cancelled.SetCanceled ();
362 return await cancelled.Task;
365 return CreateResponseMessage (wresponse, request, cancellationToken);