[System.Net.Http] HttpClient timeout range checks. Fixes #25755
[mono.git] / mcs / class / System.Net.Http / System.Net.Http / HttpClient.cs
1 //
2 // HttpClient.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.Net.Http.Headers;
31 using System.Threading.Tasks;
32 using System.IO;
33
34 namespace System.Net.Http
35 {
36         public partial class HttpClient : HttpMessageInvoker
37         {
38                 static readonly TimeSpan TimeoutDefault = TimeSpan.FromSeconds (100);
39
40                 Uri base_address;
41                 CancellationTokenSource cts;
42                 bool disposed;
43                 HttpRequestHeaders headers;
44                 long buffer_size;
45                 TimeSpan timeout;
46
47 #if !XAMARIN_MODERN
48                 public HttpClient ()
49                         : this (new HttpClientHandler (), true)
50                 {
51                 }
52 #endif
53                 
54                 public HttpClient (HttpMessageHandler handler)
55                         : this (handler, true)
56                 {
57                 }
58
59                 public HttpClient (HttpMessageHandler handler, bool disposeHandler)
60                         : base (handler, disposeHandler)
61                 {
62                         buffer_size = int.MaxValue;
63                         timeout = TimeoutDefault;
64                         cts = new CancellationTokenSource ();
65                 }
66
67                 public Uri BaseAddress {
68                         get {
69                                 return base_address;
70                         }
71                         set {
72                                 base_address = value;
73                         }
74                 }
75
76                 public HttpRequestHeaders DefaultRequestHeaders {
77                         get {
78                                 return headers ?? (headers = new HttpRequestHeaders ());
79                         }
80                 }
81
82                 public long MaxResponseContentBufferSize {
83                         get {
84                                 return buffer_size;
85                         }
86                         set {
87                                 if (value <= 0)
88                                         throw new ArgumentOutOfRangeException ();
89
90                                 buffer_size = value;
91                         }
92                 }
93
94                 public TimeSpan Timeout {
95                         get {
96                                 return timeout;
97                         }
98                         set {
99                                 if (value != System.Threading.Timeout.InfiniteTimeSpan && (value <= TimeSpan.Zero || value.Ticks > int.MaxValue))
100                                         throw new ArgumentOutOfRangeException ();
101
102                                 timeout = value;
103                         }
104                 }
105
106                 public void CancelPendingRequests ()
107                 {
108                         // Cancel only any already running requests not any new request after this cancellation
109                         using (var c = Interlocked.Exchange (ref cts, new CancellationTokenSource ()))
110                                 c.Cancel ();
111                 }
112
113                 protected override void Dispose (bool disposing)
114                 {
115                         if (disposing && !disposed) {
116                                 disposed = true;
117
118                                 cts.Dispose ();
119                         }
120                         
121                         base.Dispose (disposing);
122                 }
123
124                 public Task<HttpResponseMessage> DeleteAsync (string requestUri)
125                 {
126                         return SendAsync (new HttpRequestMessage (HttpMethod.Delete, requestUri));
127                 }
128
129                 public Task<HttpResponseMessage> DeleteAsync (string requestUri, CancellationToken cancellationToken)
130                 {
131                         return SendAsync (new HttpRequestMessage (HttpMethod.Delete, requestUri), cancellationToken);
132                 }
133
134                 public Task<HttpResponseMessage> DeleteAsync (Uri requestUri)
135                 {
136                         return SendAsync (new HttpRequestMessage (HttpMethod.Delete, requestUri));
137                 }
138
139                 public Task<HttpResponseMessage> DeleteAsync (Uri requestUri, CancellationToken cancellationToken)
140                 {
141                         return SendAsync (new HttpRequestMessage (HttpMethod.Delete, requestUri), cancellationToken);
142                 }
143
144                 public Task<HttpResponseMessage> GetAsync (string requestUri)
145                 {
146                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri));
147                 }
148
149                 public Task<HttpResponseMessage> GetAsync (string requestUri, CancellationToken cancellationToken)
150                 {
151                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), cancellationToken);
152                 }
153
154                 public Task<HttpResponseMessage> GetAsync (string requestUri, HttpCompletionOption completionOption)
155                 {
156                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), completionOption);
157                 }
158
159                 public Task<HttpResponseMessage> GetAsync (string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
160                 {
161                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), completionOption, cancellationToken);
162                 }
163
164                 public Task<HttpResponseMessage> GetAsync (Uri requestUri)
165                 {
166                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri));
167                 }
168
169                 public Task<HttpResponseMessage> GetAsync (Uri requestUri, CancellationToken cancellationToken)
170                 {
171                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), cancellationToken);
172                 }
173
174                 public Task<HttpResponseMessage> GetAsync (Uri requestUri, HttpCompletionOption completionOption)
175                 {
176                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), completionOption);
177                 }
178
179                 public Task<HttpResponseMessage> GetAsync (Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
180                 {
181                         return SendAsync (new HttpRequestMessage (HttpMethod.Get, requestUri), completionOption, cancellationToken);
182                 }
183
184                 public Task<HttpResponseMessage> PostAsync (string requestUri, HttpContent content)
185                 {
186                         return SendAsync (new HttpRequestMessage (HttpMethod.Post, requestUri) { Content = content });
187                 }
188
189                 public Task<HttpResponseMessage> PostAsync (string requestUri, HttpContent content, CancellationToken cancellationToken)
190                 {
191                         return SendAsync (new HttpRequestMessage (HttpMethod.Post, requestUri) { Content = content }, cancellationToken);
192                 }
193
194                 public Task<HttpResponseMessage> PostAsync (Uri requestUri, HttpContent content)
195                 {
196                         return SendAsync (new HttpRequestMessage (HttpMethod.Post, requestUri) { Content = content });
197                 }
198
199                 public Task<HttpResponseMessage> PostAsync (Uri requestUri, HttpContent content, CancellationToken cancellationToken)
200                 {
201                         return SendAsync (new HttpRequestMessage (HttpMethod.Post, requestUri) { Content = content }, cancellationToken);
202                 }
203
204                 public Task<HttpResponseMessage> PutAsync (Uri requestUri, HttpContent content)
205                 {
206                         return SendAsync (new HttpRequestMessage (HttpMethod.Put, requestUri) { Content = content });
207                 }
208
209                 public Task<HttpResponseMessage> PutAsync (Uri requestUri, HttpContent content, CancellationToken cancellationToken)
210                 {
211                         return SendAsync (new HttpRequestMessage (HttpMethod.Put, requestUri) { Content = content }, cancellationToken);
212                 }
213
214                 public Task<HttpResponseMessage> PutAsync (string requestUri, HttpContent content)
215                 {
216                         return SendAsync (new HttpRequestMessage (HttpMethod.Put, requestUri) { Content = content });
217                 }
218
219                 public Task<HttpResponseMessage> PutAsync (string requestUri, HttpContent content, CancellationToken cancellationToken)
220                 {
221                         return SendAsync (new HttpRequestMessage (HttpMethod.Put, requestUri) { Content = content }, cancellationToken);
222                 }
223
224                 public Task<HttpResponseMessage> SendAsync (HttpRequestMessage request)
225                 {
226                         return SendAsync (request, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
227                 }
228
229                 public Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, HttpCompletionOption completionOption)
230                 {
231                         return SendAsync (request, completionOption, CancellationToken.None);
232                 }
233
234                 public override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
235                 {
236                         return SendAsync (request, HttpCompletionOption.ResponseContentRead, cancellationToken);
237                 }
238
239                 public Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
240                 {
241                         if (request == null)
242                                 throw new ArgumentNullException ("request");
243
244                         if (request.SetIsUsed ())
245                                 throw new InvalidOperationException ("Cannot send the same request message multiple times");
246
247                         var uri = request.RequestUri;
248                         if (uri == null) {
249                                 if (base_address == null)
250                                         throw new InvalidOperationException ("The request URI must either be an absolute URI or BaseAddress must be set");
251
252                                 request.RequestUri = base_address;
253                         } else if (!uri.IsAbsoluteUri || uri.Scheme == Uri.UriSchemeFile && uri.OriginalString.StartsWith ("/", StringComparison.Ordinal)) {
254                                 if (base_address == null)
255                                         throw new InvalidOperationException ("The request URI must either be an absolute URI or BaseAddress must be set");
256
257                                 request.RequestUri = new Uri (base_address, uri);
258                         }
259
260                         if (headers != null) {
261                                 request.Headers.AddHeaders (headers);
262                         }
263
264                         return SendAsyncWorker (request, completionOption, cancellationToken);
265                 }
266
267                 async Task<HttpResponseMessage> SendAsyncWorker (HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
268                 {
269                         using (var lcts = CancellationTokenSource.CreateLinkedTokenSource (cts.Token, cancellationToken)) {
270                                 lcts.CancelAfter (timeout);
271
272                                 var task = base.SendAsync (request, lcts.Token);
273                                 if (task == null)
274                                         throw new InvalidOperationException ("Handler failed to return a value");
275                                         
276                                 var response = await task.ConfigureAwait (false);
277                                 if (response == null)
278                                         throw new InvalidOperationException ("Handler failed to return a response");
279
280                                 //
281                                 // Read the content when default HttpCompletionOption.ResponseContentRead is set
282                                 //
283                                 if (response.Content != null && (completionOption & HttpCompletionOption.ResponseHeadersRead) == 0) {
284                                         await response.Content.LoadIntoBufferAsync (MaxResponseContentBufferSize).ConfigureAwait (false);
285                                 }
286                                         
287                                 return response;
288                         }
289                 }
290
291                 public async Task<byte[]> GetByteArrayAsync (string requestUri)
292                 {
293                         using (var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseContentRead).ConfigureAwait (false)) {
294                                 resp.EnsureSuccessStatusCode ();
295                                 return await resp.Content.ReadAsByteArrayAsync ().ConfigureAwait (false);
296                         }
297                 }
298
299                 public async Task<byte[]> GetByteArrayAsync (Uri requestUri)
300                 {
301                         using (var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseContentRead).ConfigureAwait (false)) {
302                                 resp.EnsureSuccessStatusCode ();
303                                 return await resp.Content.ReadAsByteArrayAsync ().ConfigureAwait (false);
304                         }
305                 }
306
307                 public async Task<Stream> GetStreamAsync (string requestUri)
308                 {
309                         var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait (false);
310                         resp.EnsureSuccessStatusCode ();
311                         return await resp.Content.ReadAsStreamAsync ().ConfigureAwait (false);
312                 }
313
314                 public async Task<Stream> GetStreamAsync (Uri requestUri)
315                 {
316                         var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait (false);
317                         resp.EnsureSuccessStatusCode ();
318                         return await resp.Content.ReadAsStreamAsync ().ConfigureAwait (false);
319                 }
320
321                 public async Task<string> GetStringAsync (string requestUri)
322                 {
323                         using (var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseContentRead).ConfigureAwait (false)) {
324                                 resp.EnsureSuccessStatusCode ();
325                                 return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);
326                         }
327                 }
328
329                 public async Task<string> GetStringAsync (Uri requestUri)
330                 {
331                         using (var resp = await GetAsync (requestUri, HttpCompletionOption.ResponseContentRead).ConfigureAwait (false)) {
332                                 resp.EnsureSuccessStatusCode ();
333                                 return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);
334                         }
335                 }
336         }
337 }