Merge pull request #5198 from BrzVlad/fix-binprot-stats
[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.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;
37 using System.Linq;
38
39 namespace System.Net.Http
40 {
41         public class HttpClientHandler : HttpMessageHandler
42         {
43                 static long groupCounter;
44
45                 bool allowAutoRedirect;
46                 DecompressionMethods automaticDecompression;
47                 CookieContainer cookieContainer;
48                 ICredentials credentials;
49                 int maxAutomaticRedirections;
50                 long maxRequestContentBufferSize;
51                 bool preAuthenticate;
52                 IWebProxy proxy;
53                 bool useCookies;
54                 bool useDefaultCredentials;
55                 bool useProxy;
56                 ClientCertificateOption certificate;
57                 bool sentRequest;
58                 string connectionGroupName;
59                 bool disposed;
60
61                 public HttpClientHandler ()
62                 {
63                         allowAutoRedirect = true;
64                         maxAutomaticRedirections = 50;
65                         maxRequestContentBufferSize = int.MaxValue;
66                         useCookies = true;
67                         useProxy = true;
68                         connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
69                 }
70
71                 internal void EnsureModifiability ()
72                 {
73                         if (sentRequest)
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.");
77                 }
78
79                 public bool AllowAutoRedirect {
80                         get {
81                                 return allowAutoRedirect;
82                         }
83                         set {
84                                 EnsureModifiability ();
85                                 allowAutoRedirect = value;
86                         }
87                 }
88
89                 public DecompressionMethods AutomaticDecompression {
90                         get {
91                                 return automaticDecompression;
92                         }
93                         set {
94                                 EnsureModifiability ();
95                                 automaticDecompression = value;
96                         }
97                 }
98
99                 public ClientCertificateOption ClientCertificateOptions {
100                         get {
101                                 return certificate;
102                         }
103                         set {
104                                 EnsureModifiability ();
105                                 certificate = value;
106                         }
107                 }
108
109                 public CookieContainer CookieContainer {
110                         get {
111                                 return cookieContainer ?? (cookieContainer = new CookieContainer ());
112                         }
113                         set {
114                                 EnsureModifiability ();
115                                 cookieContainer = value;
116                         }
117                 }
118
119                 public ICredentials Credentials {
120                         get {
121                                 return credentials;
122                         }
123                         set {
124                                 EnsureModifiability ();
125                                 credentials = value;
126                         }
127                 }
128
129                 public int MaxAutomaticRedirections {
130                         get {
131                                 return maxAutomaticRedirections;
132                         }
133                         set {
134                                 EnsureModifiability ();
135                                 if (value <= 0)
136                                         throw new ArgumentOutOfRangeException ();
137
138                                 maxAutomaticRedirections = value;
139                         }
140                 }
141
142                 public long MaxRequestContentBufferSize {
143                         get {
144                                 return maxRequestContentBufferSize;
145                         }
146                         set {
147                                 EnsureModifiability ();
148                                 if (value < 0)
149                                         throw new ArgumentOutOfRangeException ();
150
151                                 maxRequestContentBufferSize = value;
152                         }
153                 }
154
155                 public bool PreAuthenticate {
156                         get {
157                                 return preAuthenticate;
158                         }
159                         set {
160                                 EnsureModifiability ();
161                                 preAuthenticate = value;
162                         }
163                 }
164
165                 public IWebProxy Proxy {
166                         get {
167                                 return proxy;
168                         }
169                         set {
170                                 EnsureModifiability ();
171                                 if (!UseProxy)
172                                         throw new InvalidOperationException ();
173
174                                 proxy = value;
175                         }
176                 }
177
178                 public virtual bool SupportsAutomaticDecompression {
179                         get {
180                                 return true;
181                         }
182                 }
183
184                 public virtual bool SupportsProxy {
185                         get {
186                                 return true;
187                         }
188                 }
189
190                 public virtual bool SupportsRedirectConfiguration {
191                         get {
192                                 return true;
193                         }
194                 }
195
196                 public bool UseCookies {
197                         get {
198                                 return useCookies;
199                         }
200                         set {
201                                 EnsureModifiability ();
202                                 useCookies = value;
203                         }
204                 }
205
206                 public bool UseDefaultCredentials {
207                         get {
208                                 return useDefaultCredentials;
209                         }
210                         set {
211                                 EnsureModifiability ();
212                                 useDefaultCredentials = value;
213                         }
214                 }
215
216                 public bool UseProxy {
217                         get {
218                                 return useProxy;
219                         }
220                         set {
221                                 EnsureModifiability ();
222                                 useProxy = value;
223                         }
224                 }
225
226                 protected override void Dispose (bool disposing)
227                 {
228                         if (disposing && !disposed) {
229                                 Volatile.Write (ref disposed, true);
230                                 ServicePointManager.CloseConnectionGroup (connectionGroupName);
231                         }
232
233                         base.Dispose (disposing);
234                 }
235
236                 internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
237                 {
238                         var wr = new HttpWebRequest (request.RequestUri);
239                         wr.ThrowOnError = false;
240                         wr.AllowWriteStreamBuffering = false;
241
242                         wr.ConnectionGroupName = connectionGroupName;
243                         wr.Method = request.Method.Method;
244                         wr.ProtocolVersion = request.Version;
245
246                         if (wr.ProtocolVersion == HttpVersion.Version10) {
247                                 wr.KeepAlive = request.Headers.ConnectionKeepAlive;
248                         } else {
249                                 wr.KeepAlive = request.Headers.ConnectionClose != true;
250                         }
251
252                         if (allowAutoRedirect) {
253                                 wr.AllowAutoRedirect = true;
254                                 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
255                         } else {
256                                 wr.AllowAutoRedirect = false;
257                         }
258
259                         wr.AutomaticDecompression = automaticDecompression;
260                         wr.PreAuthenticate = preAuthenticate;
261
262                         if (useCookies) {
263                                 // It cannot be null or allowAutoRedirect won't work
264                                 wr.CookieContainer = CookieContainer;
265                         }
266
267                         if (useDefaultCredentials) {
268                                 wr.UseDefaultCredentials = true;
269                         } else {
270                                 wr.Credentials = credentials;
271                         }
272
273                         if (useProxy) {
274                                 wr.Proxy = proxy;
275                         } else {
276                                 // Disables default WebRequest.DefaultWebProxy value
277                                 wr.Proxy = null;
278                         }
279
280                         wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
281
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") {
287                                         //
288                                         // Host must be explicitly set for HttpWebRequest
289                                         //
290                                         wr.Host = request.Headers.Host;
291                                         continue;
292                                 }
293
294                                 if (header.Key == "Transfer-Encoding") {
295                                         //
296                                         // Chunked Transfer-Encoding is set for HttpWebRequest later when Content length is checked
297                                         //
298                                         values = values.Where (l => l != "chunked");
299                                 }
300
301                                 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
302                                 if (values_formated == null)
303                                         continue;
304
305                                 headers.AddInternal (header.Key, values_formated);
306                         }
307                         
308                         return wr;
309                 }
310
311                 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
312                 {
313                         var response = new HttpResponseMessage (wr.StatusCode);
314                         response.RequestMessage = requestMessage;
315                         response.ReasonPhrase = wr.StatusDescription;
316                         response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
317
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);
322
323                                 HttpHeaders item_headers;
324                                 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
325                                         item_headers = response.Content.Headers;
326                                 else
327                                         item_headers = response.Headers;
328                                         
329                                 item_headers.TryAddWithoutValidation (key, value);
330                         }
331
332                         requestMessage.RequestUri = wr.ResponseUri;
333
334                         return response;
335                 }
336
337                 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
338                 {
339                         if (disposed)
340                                 throw new ObjectDisposedException (GetType ().ToString ());
341
342                         Volatile.Write (ref sentRequest, true);
343                         var wrequest = CreateWebRequest (request);
344                         HttpWebResponse wresponse = null;
345
346                         try {
347                                 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
348                                         var content = request.Content;
349                                         if (content != null) {
350                                                 var headers = wrequest.Headers;
351
352                                                 foreach (var header in content.Headers) {
353                                                         foreach (var value in header.Value) {
354                                                                 headers.AddInternal (header.Key, value);
355                                                         }
356                                                 }
357
358                                                 if (request.Headers.TransferEncodingChunked == true) {
359                                                         wrequest.SendChunked = true;
360                                                 } else {
361                                                         //
362                                                         // Content length has to be set because HttpWebRequest is running without buffering
363                                                         //
364                                                         var contentLength = content.Headers.ContentLength;
365                                                         if (contentLength != null) {
366                                                                 wrequest.ContentLength = contentLength.Value;
367                                                         } else {
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.");
370
371                                                                 await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
372                                                                 wrequest.ContentLength = content.Headers.ContentLength.Value;
373                                                         }
374                                                 }
375
376                                                 wrequest.ResendContentFactory = content.CopyTo;
377
378                                                 using (var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false)) {
379                                                         await request.Content.CopyToAsync (stream).ConfigureAwait (false);
380                                                 }
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;
386                                         }
387
388                                         wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
389                                 }
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);
395                         }
396
397                         if (cancellationToken.IsCancellationRequested) {
398                                 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
399                                 cancelled.SetCanceled ();
400                                 return await cancelled.Task;
401                         }
402                         
403                         return CreateResponseMessage (wresponse, request, cancellationToken);
404                 }
405
406                 public bool CheckCertificateRevocationList {
407                         get {
408                                 throw new NotImplementedException ();
409                         }
410                         set {
411                                 throw new NotImplementedException ();
412                         }
413                 }
414
415                 public X509CertificateCollection ClientCertificates {
416                         get {
417                                 throw new NotImplementedException ();
418                         }
419                 }
420
421                 public ICredentials DefaultProxyCredentials {
422                         get {
423                                 throw new NotImplementedException ();
424                         }
425                         set {
426                                 throw new NotImplementedException ();
427                         }
428                 }
429
430                 public int MaxConnectionsPerServer {
431                         get {
432                                 throw new NotImplementedException ();
433                         }
434                         set {
435                                 throw new NotImplementedException ();
436                         }
437                 }
438
439                 public int MaxResponseHeadersLength {
440                         get {
441                                 throw new NotImplementedException ();
442                         }
443                         set {
444                                 throw new NotImplementedException ();
445                         }
446                 }
447
448                 public IDictionary<string,object> Properties {
449                         get {
450                                 throw new NotImplementedException ();
451                         }
452                 }
453
454                 public Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool> ServerCertificateCustomValidationCallback {
455                         get {
456                                 throw new NotImplementedException ();
457                         }
458                         set {
459                                 throw new NotImplementedException ();
460                         }
461                 }
462
463                 public SslProtocols SslProtocols {
464                         get {
465                                 throw new NotImplementedException ();
466                         }
467                         set {
468                                 throw new NotImplementedException ();
469                         }
470                 }
471         }
472 }