Merge pull request #1936 from esdrubal/DotNetRelativeOrAbsolute
[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                         wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
249
250                         if (allowAutoRedirect) {
251                                 wr.AllowAutoRedirect = true;
252                                 wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
253                         } else {
254                                 wr.AllowAutoRedirect = false;
255                         }
256
257                         wr.AutomaticDecompression = automaticDecompression;
258                         wr.PreAuthenticate = preAuthenticate;
259
260                         if (useCookies) {
261                                 // It cannot be null or allowAutoRedirect won't work
262                                 wr.CookieContainer = CookieContainer;
263                         }
264
265                         if (useDefaultCredentials) {
266                                 wr.UseDefaultCredentials = true;
267                         } else {
268                                 wr.Credentials = credentials;
269                         }
270
271                         if (useProxy) {
272                                 wr.Proxy = proxy;
273                         }
274
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 == "Transfer-Encoding") {
280                                         // Chunked Transfer-Encoding is never set for HttpWebRequest. It's detected
281                                         // from ContentLength by HttpWebRequest
282                                         values = values.Where (l => l != "chunked");
283                                 }
284
285                                 var values_formated = HttpRequestHeaders.GetSingleHeaderString (header.Key, values);
286                                 if (values_formated == null)
287                                         continue;
288
289                                 headers.AddValue (header.Key, values_formated);
290                         }
291                         
292                         return wr;
293                 }
294
295                 HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
296                 {
297                         var response = new HttpResponseMessage (wr.StatusCode);
298                         response.RequestMessage = requestMessage;
299                         response.ReasonPhrase = wr.StatusDescription;
300                         response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
301
302                         var headers = wr.Headers;
303                         for (int i = 0; i < headers.Count; ++i) {
304                                 var key = headers.GetKey(i);
305                                 var value = headers.GetValues (i);
306
307                                 HttpHeaders item_headers;
308                                 if (HttpHeaders.GetKnownHeaderKind (key) == Headers.HttpHeaderKind.Content)
309                                         item_headers = response.Content.Headers;
310                                 else
311                                         item_headers = response.Headers;
312                                         
313                                 item_headers.TryAddWithoutValidation (key, value);
314                         }
315
316                         requestMessage.RequestUri = wr.ResponseUri;
317
318                         return response;
319                 }
320
321                 protected async internal override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
322                 {
323                         if (disposed)
324                                 throw new ObjectDisposedException (GetType ().ToString ());
325
326                         Volatile.Write (ref sentRequest, true);
327                         var wrequest = CreateWebRequest (request);
328                         HttpWebResponse wresponse = null;
329
330                         try {
331                                 using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
332                                         var content = request.Content;
333                                         if (content != null) {
334                                                 var headers = wrequest.Headers;
335
336                                                 foreach (var header in content.Headers) {
337                                                         foreach (var value in header.Value) {
338                                                                 headers.AddValue (header.Key, value);
339                                                         }
340                                                 }
341
342                                                 //
343                                                 // Content length has to be set because HttpWebRequest is running without buffering
344                                                 //
345                                                 var contentLength = content.Headers.ContentLength;
346                                                 if (contentLength != null) {
347                                                         wrequest.ContentLength = contentLength.Value;
348                                                 } else {
349                                                         await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
350                                                         wrequest.ContentLength = content.Headers.ContentLength.Value;
351                                                 }
352
353                                                 wrequest.ResendContentFactory = content.CopyTo;
354
355                                                 var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false);
356                                                 await request.Content.CopyToAsync (stream).ConfigureAwait (false);
357                                         } else if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) {
358                                                 // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
359                                                 // This fixes the issue that's been reported on the forums:
360                                                 // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
361                                                 wrequest.ContentLength = 0;
362                                         }
363
364                                         wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
365                                 }
366                         } catch (WebException we) {
367                                 if (we.Status != WebExceptionStatus.RequestCanceled)
368                                         throw;
369                         }
370
371                         if (cancellationToken.IsCancellationRequested) {
372                                 var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
373                                 cancelled.SetCanceled ();
374                                 return await cancelled.Task;
375                         }
376                         
377                         return CreateResponseMessage (wresponse, request, cancellationToken);
378                 }
379         }
380 }