Merge pull request #2734 from nealef/master
[mono.git] / mcs / class / System.Net.Http / System.Net.Http / HttpClientHandler.cs
1 //
2 // HttpClientHandler.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27 //
28
29 using System.Threading;
30 using System.Threading.Tasks;
31 using System.Collections.Specialized;
32 using System.Net.Http.Headers;
33 using System.Linq;
34
35 namespace System.Net.Http
36 {
37         public class HttpClientHandler : HttpMessageHandler
38         {
39                 static long groupCounter;
40
41                 bool allowAutoRedirect;
42                 DecompressionMethods automaticDecompression;
43                 CookieContainer cookieContainer;
44                 ICredentials credentials;
45                 int maxAutomaticRedirections;
46                 long maxRequestContentBufferSize;
47                 bool preAuthenticate;
48                 IWebProxy proxy;
49                 bool useCookies;
50                 bool useDefaultCredentials;
51                 bool useProxy;
52                 ClientCertificateOption certificate;
53                 bool sentRequest;
54                 string connectionGroupName;
55                 bool disposed;
56
57                 public HttpClientHandler ()
58                 {
59                         allowAutoRedirect = true;
60                         maxAutomaticRedirections = 50;
61                         maxRequestContentBufferSize = int.MaxValue;
62                         useCookies = true;
63                         useProxy = true;
64                         connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
65                 }
66
67                 internal void EnsureModifiability ()
68                 {
69                         if (sentRequest)
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.");
73                 }
74
75                 public bool AllowAutoRedirect {
76                         get {
77                                 return allowAutoRedirect;
78                         }
79                         set {
80                                 EnsureModifiability ();
81                                 allowAutoRedirect = value;
82                         }
83                 }
84
85                 public DecompressionMethods AutomaticDecompression {
86                         get {
87                                 return automaticDecompression;
88                         }
89                         set {
90                                 EnsureModifiability ();
91                                 automaticDecompression = value;
92                         }
93                 }
94
95                 public ClientCertificateOption ClientCertificateOptions {
96                         get {
97                                 return certificate;
98                         }
99                         set {
100                                 EnsureModifiability ();
101                                 certificate = value;
102                         }
103                 }
104
105                 public CookieContainer CookieContainer {
106                         get {
107                                 return cookieContainer ?? (cookieContainer = new CookieContainer ());
108                         }
109                         set {
110                                 EnsureModifiability ();
111                                 cookieContainer = value;
112                         }
113                 }
114
115                 public ICredentials Credentials {
116                         get {
117                                 return credentials;
118                         }
119                         set {
120                                 EnsureModifiability ();
121                                 credentials = value;
122                         }
123                 }
124
125                 public int MaxAutomaticRedirections {
126                         get {
127                                 return maxAutomaticRedirections;
128                         }
129                         set {
130                                 EnsureModifiability ();
131                                 if (value <= 0)
132                                         throw new ArgumentOutOfRangeException ();
133
134                                 maxAutomaticRedirections = value;
135                         }
136                 }
137
138                 public long MaxRequestContentBufferSize {
139                         get {
140                                 return maxRequestContentBufferSize;
141                         }
142                         set {
143                                 EnsureModifiability ();
144                                 if (value < 0)
145                                         throw new ArgumentOutOfRangeException ();
146
147                                 maxRequestContentBufferSize = value;
148                         }
149                 }
150
151                 public bool PreAuthenticate {
152                         get {
153                                 return preAuthenticate;
154                         }
155                         set {
156                                 EnsureModifiability ();
157                                 preAuthenticate = value;
158                         }
159                 }
160
161                 public IWebProxy Proxy {
162                         get {
163                                 return proxy;
164                         }
165                         set {
166                                 EnsureModifiability ();
167                                 if (!UseProxy)
168                                         throw new InvalidOperationException ();
169
170                                 proxy = value;
171                         }
172                 }
173
174                 public virtual bool SupportsAutomaticDecompression {
175                         get {
176                                 return true;
177                         }
178                 }
179
180                 public virtual bool SupportsProxy {
181                         get {
182                                 return true;
183                         }
184                 }
185
186                 public virtual bool SupportsRedirectConfiguration {
187                         get {
188                                 return true;
189                         }
190                 }
191
192                 public bool UseCookies {
193                         get {
194                                 return useCookies;
195                         }
196                         set {
197                                 EnsureModifiability ();
198                                 useCookies = value;
199                         }
200                 }
201
202                 public bool UseDefaultCredentials {
203                         get {
204                                 return useDefaultCredentials;
205                         }
206                         set {
207                                 EnsureModifiability ();
208                                 useDefaultCredentials = value;
209                         }
210                 }
211
212                 public bool UseProxy {
213                         get {
214                                 return useProxy;
215                         }
216                         set {
217                                 EnsureModifiability ();
218                                 useProxy = value;
219                         }
220                 }
221
222                 protected override void Dispose (bool disposing)
223                 {
224                         if (disposing && !disposed) {
225                                 Volatile.Write (ref disposed, true);
226                                 ServicePointManager.CloseConnectionGroup (connectionGroupName);
227                         }
228
229                         base.Dispose (disposing);
230                 }
231
232                 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
233                 {
234                         var wr = new HttpWebRequest (request.RequestUri);
235                         wr.ThrowOnError = false;
236                         wr.AllowWriteStreamBuffering = false;
237
238                         wr.ConnectionGroupName = connectionGroupName;
239                         wr.Method = request.Method.Method;
240                         wr.ProtocolVersion = request.Version;
241
242                         if (wr.ProtocolVersion == HttpVersion.Version10) {
243                                 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
244                         } else {
245                                 wr.KeepAlive = request.Headers.ConnectionClose != true;
246                         }
247
248                         if (allowAutoRedirect) {
249                                 wr.AllowAutoRedirect = true;
250                                 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
251                         } else {
252                                 wr.AllowAutoRedirect = false;
253                         }
254
255                         wr.AutomaticDecompression = automaticDecompression;
256                         wr.PreAuthenticate = preAuthenticate;
257
258                         if (useCookies) {
259                                 // It cannot be null or allowAutoRedirect won't work
260                                 wr.CookieContainer = CookieContainer;
261                         }
262
263                         if (useDefaultCredentials) {
264                                 wr.UseDefaultCredentials = true;
265                         } else {
266                                 wr.Credentials = credentials;
267                         }
268
269                         if (useProxy) {
270                                 wr.Proxy = proxy;
271                         } else {
272                                 // Disables default WebRequest.DefaultWebProxy value
273                                 wr.Proxy = null;
274                         }
275
276                         wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
277
278                         // Add request headers
279                         var headers = wr.Headers;
280                         foreach (var header in request.Headers) {
281                                 var values = header.Value;
282                                 if (header.Key == "Host") {
283                                         //
284                                         // Host must be explicitly set for HttpWebRequest
285                                         //
286                                         wr.Host = request.Headers.Host;
287                                         continue;
288                                 }
289
290                                 if (header.Key == "Transfer-Encoding") {
291                                         // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
292                                         // from ContentLength by HttpWebRequest
293                                         values = values.Where (l => l != "chunked");
294                                 }
295
296                                 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
297                                 if (values_formated == null)
298                                         continue;
299
300                                 headers.AddInternal (header.Key, values_formated);
301                         }
302                         
303                         return wr;
304                 }
305
306                 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
307                 {
308                         var response = new HttpResponseMessage (wr.StatusCode);
309                         response.RequestMessage = requestMessage;
310                         response.ReasonPhrase = wr.StatusDescription;
311                         response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
312
313                         var headers = wr.Headers;
314                         for (int i = 0; i < headers.Count; ++i) {
315                                 var key = headers.GetKey(i);
316                                 var value = headers.GetValues (i);
317
318                                 HttpHeaders item_headers;
319                                 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
320                                         item_headers = response.Content.Headers;
321                                 else
322                                         item_headers = response.Headers;
323                                         
324                                 item_headers.TryAddWithoutValidation (key, value);
325                         }
326
327                         requestMessage.RequestUri = wr.ResponseUri;
328
329                         return response;
330                 }
331
332                 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
333                 {
334                         if (disposed)
335                                 throw new ObjectDisposedException (GetType ().ToString ());
336
337                         Volatile.Write (ref sentRequest, true);
338                         var wrequest = CreateWebRequest (request);
339                         HttpWebResponse wresponse = null;
340
341                         try {
342                                 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
343                                         var content = request.Content;
344                                         if (content != null) {
345                                                 var headers = wrequest.Headers;
346
347                                                 foreach (var header in content.Headers) {
348                                                         foreach (var value in header.Value) {
349                                                                 headers.AddInternal (header.Key, value);
350                                                         }
351                                                 }
352
353                                                 //
354                                                 // Content length has to be set because HttpWebRequest is running without buffering
355                                                 //
356                                                 var contentLength = content.Headers.ContentLength;
357                                                 if (contentLength != null) {
358                                                         wrequest.ContentLength = contentLength.Value;
359                                                 } else {
360                                                         await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
361                                                         wrequest.ContentLength = content.Headers.ContentLength.Value;
362                                                 }
363
364                                                 wrequest.ResendContentFactory = content.CopyTo;
365
366                                                 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
367                                                 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
368                                         } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
369                                                 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
370                                                 // This fixes the issue that's been reported on the forums:
371                                                 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
372                                                 wrequest.ContentLength = 0;
373                                         }
374
375                                         wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
376                                 }
377                         } catch (WebException we) {
378                                 if (we.Status != WebExceptionStatus.RequestCanceled)
379                                         throw;
380                         }
381
382                         if (cancellationToken.IsCancellationRequested) {
383                                 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
384                                 cancelled.SetCanceled ();
385                                 return await cancelled.Task;
386                         }
387                         
388                         return CreateResponseMessage (wresponse, request, cancellationToken);
389                 }
390         }
391 }