root / trunk / Pithos.Network / docs / restclient.html @ 1caef52e
History | View | Annotate | Download (17.3 kB)
1 |
<!DOCTYPE html />
|
---|---|
2 |
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>RestClient.cs</title> |
6 |
<meta http-equiv="content-type" content="text/html; charset=UTF-8" /> |
7 |
<link href="focco.css" rel="stylesheet" media="all" type="text/css" /> |
8 |
<script src="prettify.js" type="text/javascript"></script> |
9 |
</head>
|
10 |
<body onload="prettyPrint()"> |
11 |
<div id="container"> |
12 |
<div id="background"></div> |
13 |
<div id="jump_to"> |
14 |
Jump To …
|
15 |
<div id="jump_wrapper"> |
16 |
<div id="jump_page"> |
17 |
<a class="source" href="cloudfilesclient.html"> |
18 |
CloudFilesClient.cs |
19 |
</a>
|
20 |
<a class="source" href="restclient.html"> |
21 |
RestClient.cs |
22 |
</a>
|
23 |
<a class="source" href="timeoutretrycondition.html"> |
24 |
TimeoutRetryCondition.cs |
25 |
</a>
|
26 |
<a class="source" href="properties/assemblyinfo.html"> |
27 |
Properties\AssemblyInfo.cs |
28 |
</a>
|
29 |
</div>
|
30 |
</div>
|
31 |
</div>
|
32 |
<table cellpadding="0" cellspacing="0"> |
33 |
<thead>
|
34 |
<tr>
|
35 |
<th class="docs"> |
36 |
<h1>RestClient.cs</h1> |
37 |
</th>
|
38 |
<th class="code"></th> |
39 |
</tr>
|
40 |
</thead>
|
41 |
<tbody>
|
42 |
<tr id="section_1"> |
43 |
<td class="docs"> |
44 |
<div class="pilwrap"> |
45 |
<a class="pilcrow" href="#section_1">¶</a> |
46 |
</div>
|
47 |
<hr /> |
48 |
|
49 |
<p><copyright file="RestClient.cs" company="Microsoft"> |
50 |
TODO: Update copyright text.</p>
|
51 |
|
52 |
<h2></copyright></h2> |
53 |
|
54 |
</td>
|
55 |
<td class="code"> |
56 |
<pre><code class='prettyprint'> |
57 |
using System.Collections.Specialized; |
58 |
using System.Diagnostics; |
59 |
using System.Diagnostics.Contracts; |
60 |
using System.IO; |
61 |
using System.Net; |
62 |
using System.Runtime.Serialization; |
63 |
using System.Threading.Tasks; |
64 |
|
65 |
namespace Pithos.Network |
66 |
{ |
67 |
using System; |
68 |
using System.Collections.Generic; |
69 |
using System.Linq; |
70 |
using System.Text; |
71 |
|
72 |
</code></pre> |
73 |
</td>
|
74 |
</tr>
|
75 |
<tr id="section_2"> |
76 |
<td class="docs"> |
77 |
<div class="pilwrap"> |
78 |
<a class="pilcrow" href="#section_2">¶</a> |
79 |
</div>
|
80 |
<p>/ <summary> |
81 |
/ TODO: Update summary. |
82 |
/ </summary></p> |
83 |
|
84 |
</td>
|
85 |
<td class="code"> |
86 |
<pre><code class='prettyprint'> public class RestClient:WebClient |
87 |
{ |
88 |
public int Timeout { get; set; } |
89 |
|
90 |
public bool TimedOut { get; set; } |
91 |
|
92 |
public HttpStatusCode StatusCode { get; private set; } |
93 |
|
94 |
public string StatusDescription { get; set; } |
95 |
|
96 |
|
97 |
public int Retries { get; set; } |
98 |
|
99 |
private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>(); |
100 |
public Dictionary<string, string> Parameters |
101 |
{ |
102 |
get { return _parameters; } |
103 |
} |
104 |
|
105 |
public RestClient():base() |
106 |
{ |
107 |
|
108 |
} |
109 |
|
110 |
|
111 |
public RestClient(RestClient other) |
112 |
: base() |
113 |
{ |
114 |
CopyHeaders(other); |
115 |
Timeout = other.Timeout; |
116 |
Retries = other.Retries; |
117 |
BaseAddress = other.BaseAddress; |
118 |
|
119 |
foreach (var parameter in other.Parameters) |
120 |
{ |
121 |
Parameters.Add(parameter.Key,parameter.Value); |
122 |
} |
123 |
|
124 |
this.Proxy = other.Proxy; |
125 |
} |
126 |
|
127 |
protected override WebRequest GetWebRequest(Uri address) |
128 |
{ |
129 |
TimedOut = false; |
130 |
var webRequest = base.GetWebRequest(address); |
131 |
var request = webRequest as HttpWebRequest; |
132 |
if (IfModifiedSince.HasValue) |
133 |
request.IfModifiedSince = IfModifiedSince.Value; |
134 |
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
135 |
if(Timeout>0)
|
136 |
request.Timeout = Timeout; |
137 |
return request; |
138 |
} |
139 |
|
140 |
public DateTime? IfModifiedSince { get; set; } |
141 |
|
142 |
protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) |
143 |
{ |
144 |
var response = (HttpWebResponse) base.GetWebResponse(request, result); |
145 |
StatusCode=response.StatusCode; |
146 |
StatusDescription=response.StatusDescription; |
147 |
return response; |
148 |
} |
149 |
|
150 |
|
151 |
|
152 |
protected override WebResponse GetWebResponse(WebRequest request) |
153 |
{ |
154 |
try |
155 |
{ |
156 |
var response = (HttpWebResponse)base.GetWebResponse(request); |
157 |
StatusCode = response.StatusCode; |
158 |
StatusDescription = response.StatusDescription; |
159 |
return response; |
160 |
} |
161 |
catch (WebException exc) |
162 |
{ |
163 |
if (exc.Response!=null && exc.Response.ContentLength > 0) |
164 |
{ |
165 |
string content = GetContent(exc.Response); |
166 |
Trace.TraceError(content); |
167 |
} |
168 |
throw; |
169 |
} |
170 |
} |
171 |
|
172 |
private static string GetContent(WebResponse webResponse) |
173 |
{ |
174 |
string content; |
175 |
using (var stream = webResponse.GetResponseStream()) |
176 |
using (var reader = new StreamReader(stream)) |
177 |
{ |
178 |
content = reader.ReadToEnd(); |
179 |
} |
180 |
return content; |
181 |
} |
182 |
|
183 |
public string DownloadStringWithRetry(string address,int retries=0) |
184 |
{ |
185 |
if (address == null) |
186 |
throw new ArgumentNullException("address"); |
187 |
|
188 |
var actualAddress = GetActualAddress(address); |
189 |
|
190 |
TraceStart("GET",actualAddress); |
191 |
|
192 |
var actualRetries = (retries == 0) ? Retries : retries; |
193 |
|
194 |
|
195 |
|
196 |
var task = Retry(() =>
|
197 |
{ |
198 |
var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress); |
199 |
var content = base.DownloadString(uriString); |
200 |
|
201 |
if (StatusCode == HttpStatusCode.NoContent) |
202 |
return String.Empty; |
203 |
return content; |
204 |
|
205 |
}, actualRetries); |
206 |
|
207 |
var result = task.Result; |
208 |
return result; |
209 |
} |
210 |
|
211 |
public void Head(string address,int retries=0) |
212 |
{ |
213 |
RetryWithoutContent(address, retries, "HEAD"); |
214 |
} |
215 |
|
216 |
public void PutWithRetry(string address, int retries = 0) |
217 |
{ |
218 |
RetryWithoutContent(address, retries, "PUT"); |
219 |
} |
220 |
|
221 |
public void DeleteWithRetry(string address,int retries=0) |
222 |
{ |
223 |
RetryWithoutContent(address, retries, "DELETE"); |
224 |
} |
225 |
|
226 |
public string GetHeaderValue(string headerName) |
227 |
{ |
228 |
var values=this.ResponseHeaders.GetValues(headerName); |
229 |
if (values == null) |
230 |
throw new WebException(String.Format("The {0} header is missing", headerName)); |
231 |
else |
232 |
return values[0]; |
233 |
} |
234 |
|
235 |
private void RetryWithoutContent(string address, int retries, string method) |
236 |
{ |
237 |
if (address == null) |
238 |
throw new ArgumentNullException("address"); |
239 |
|
240 |
var actualAddress = GetActualAddress(address); |
241 |
var actualRetries = (retries == 0) ? Retries : retries; |
242 |
|
243 |
var task = Retry(() =>
|
244 |
{ |
245 |
var uriString = String.Join("/",BaseAddress ,actualAddress); |
246 |
var uri = new Uri(uriString); |
247 |
var request = GetWebRequest(uri); |
248 |
request.Method = method; |
249 |
if (ResponseHeaders!=null) |
250 |
ResponseHeaders.Clear(); |
251 |
|
252 |
TraceStart(method, uriString); |
253 |
|
254 |
var response = (HttpWebResponse)GetWebResponse(request); |
255 |
StatusCode = response.StatusCode; |
256 |
StatusDescription = response.StatusDescription; |
257 |
|
258 |
|
259 |
return 0; |
260 |
}, actualRetries); |
261 |
|
262 |
task.Wait(); |
263 |
} |
264 |
|
265 |
/*private string RetryWithContent(string address, int retries, string method) |
266 |
{ |
267 |
if (address == null) |
268 |
throw new ArgumentNullException("address"); |
269 |
|
270 |
var actualAddress = GetActualAddress(address); |
271 |
var actualRetries = (retries == 0) ? Retries : retries; |
272 |
|
273 |
var task = Retry(() =>
|
274 |
{ |
275 |
var uriString = String.Join("/",BaseAddress ,actualAddress); |
276 |
var uri = new Uri(uriString); |
277 |
|
278 |
var request = GetWebRequest(uri); |
279 |
request.Method = method; |
280 |
|
281 |
if (ResponseHeaders!=null) |
282 |
ResponseHeaders.Clear(); |
283 |
|
284 |
TraceStart(method, uriString); |
285 |
|
286 |
var getResponse = request.GetResponseAsync(); |
287 |
|
288 |
var setStatus= getResponse.ContinueWith(t =>
|
289 |
{ |
290 |
var response = (HttpWebResponse)t.Result; |
291 |
StatusCode = response.StatusCode; |
292 |
StatusDescription = response.StatusDescription; |
293 |
return response; |
294 |
}); |
295 |
|
296 |
var getData = setStatus.ContinueWith(t =>
|
297 |
{ |
298 |
var response = t.Result; |
299 |
return response.GetResponseStream() |
300 |
.ReadAllBytesAsync(); |
301 |
}).Unwrap(); |
302 |
|
303 |
var data = getData.Result; |
304 |
var content=Encoding.UTF8.GetString(data); |
305 |
|
306 |
</code></pre> |
307 |
</td>
|
308 |
</tr>
|
309 |
<tr id="section_3"> |
310 |
<td class="docs"> |
311 |
<div class="pilwrap"> |
312 |
<a class="pilcrow" href="#section_3">¶</a> |
313 |
</div>
|
314 |
<pre><code> var response = (HttpWebResponse)GetWebResponse(request); |
315 |
</code></pre> |
316 |
|
317 |
</td>
|
318 |
<td class="code"> |
319 |
<pre><code class='prettyprint'> |
320 |
|
321 |
/* |
322 |
StatusCode = response.StatusCode; |
323 |
StatusDescription = response.StatusDescription; |
324 |
#1# |
325 |
|
326 |
|
327 |
return content; |
328 |
}, actualRetries); |
329 |
|
330 |
return task.Result; |
331 |
}*/ |
332 |
|
333 |
private static void TraceStart(string method, string actualAddress) |
334 |
{ |
335 |
Trace.WriteLine(String.Format("[{0}] {1} {2}", method, DateTime.Now, actualAddress)); |
336 |
} |
337 |
|
338 |
private string GetActualAddress(string address) |
339 |
{ |
340 |
if (Parameters.Count == 0) |
341 |
return address; |
342 |
var addressBuilder=new StringBuilder(address); |
343 |
|
344 |
bool isFirst = true; |
345 |
foreach (var parameter in Parameters) |
346 |
{ |
347 |
if(isFirst) |
348 |
addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value); |
349 |
else |
350 |
addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value); |
351 |
isFirst = false; |
352 |
} |
353 |
return addressBuilder.ToString(); |
354 |
} |
355 |
|
356 |
public string DownloadStringWithRetry(Uri address,int retries=0) |
357 |
{ |
358 |
if (address == null) |
359 |
throw new ArgumentNullException("address"); |
360 |
|
361 |
var actualRetries = (retries == 0) ? Retries : retries; |
362 |
var task = Retry(() =>
|
363 |
{ |
364 |
var content = base.DownloadString(address); |
365 |
|
366 |
if (StatusCode == HttpStatusCode.NoContent) |
367 |
return String.Empty; |
368 |
return content; |
369 |
|
370 |
}, actualRetries); |
371 |
|
372 |
var result = task.Result; |
373 |
return result; |
374 |
} |
375 |
|
376 |
|
377 |
</code></pre> |
378 |
</td>
|
379 |
</tr>
|
380 |
<tr id="section_4"> |
381 |
<td class="docs"> |
382 |
<div class="pilwrap"> |
383 |
<a class="pilcrow" href="#section_4">¶</a> |
384 |
</div>
|
385 |
<p>/ <summary> |
386 |
/ Copies headers from another RestClient |
387 |
/ </summary>
|
388 |
/ <param name="source">The RestClient from which the headers are copied</param></p> |
389 |
|
390 |
</td>
|
391 |
<td class="code"> |
392 |
<pre><code class='prettyprint'> public void CopyHeaders(RestClient source) |
393 |
{ |
394 |
Contract.Requires(source != null, "source can't be null"); |
395 |
if (source == null) |
396 |
throw new ArgumentNullException("source", "source can't be null"); |
397 |
CopyHeaders(source.Headers,Headers); |
398 |
} |
399 |
|
400 |
</code></pre> |
401 |
</td>
|
402 |
</tr>
|
403 |
<tr id="section_5"> |
404 |
<td class="docs"> |
405 |
<div class="pilwrap"> |
406 |
<a class="pilcrow" href="#section_5">¶</a> |
407 |
</div>
|
408 |
<p>/ <summary> |
409 |
/ Copies headers from one header collection to another |
410 |
/ </summary>
|
411 |
/ <param name="source">The source collection from which the headers are copied</param> |
412 |
/ <param name="target">The target collection to which the headers are copied</param></p> |
413 |
|
414 |
</td>
|
415 |
<td class="code"> |
416 |
<pre><code class='prettyprint'> public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target) |
417 |
{ |
418 |
Contract.Requires(source != null, "source can't be null"); |
419 |
Contract.Requires(target != null, "target can't be null"); |
420 |
if (source == null) |
421 |
throw new ArgumentNullException("source", "source can't be null"); |
422 |
if (target == null) |
423 |
throw new ArgumentNullException("target", "target can't be null"); |
424 |
for (int i = 0; i < source.Count; i++)
|
425 |
{ |
426 |
target.Add(source.GetKey(i), source[i]); |
427 |
} |
428 |
} |
429 |
|
430 |
public void AssertStatusOK(string message) |
431 |
{ |
432 |
if (StatusCode >= HttpStatusCode.BadRequest)
|
433 |
throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription)); |
434 |
} |
435 |
|
436 |
|
437 |
private Task<T> Retry<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null) |
438 |
{ |
439 |
if (tcs == null) |
440 |
tcs = new TaskCompletionSource<T>(); |
441 |
Task.Factory.StartNew(original).ContinueWith(_original =>
|
442 |
{ |
443 |
if (!_original.IsFaulted) |
444 |
tcs.SetFromTask(_original); |
445 |
else |
446 |
{ |
447 |
var e = _original.Exception.InnerException; |
448 |
var we = (e as WebException); |
449 |
if (we==null) |
450 |
tcs.SetException(e); |
451 |
else |
452 |
{ |
453 |
var statusCode = GetStatusCode(we); |
454 |
|
455 |
</code></pre> |
456 |
</td>
|
457 |
</tr>
|
458 |
<tr id="section_6"> |
459 |
<td class="docs"> |
460 |
<div class="pilwrap"> |
461 |
<a class="pilcrow" href="#section_6">¶</a> |
462 |
</div>
|
463 |
<p>Return null for 404</p> |
464 |
|
465 |
</td>
|
466 |
<td class="code"> |
467 |
<pre><code class='prettyprint'> if (statusCode == HttpStatusCode.NotFound) |
468 |
tcs.SetResult(default(T)); |
469 |
</code></pre> |
470 |
</td>
|
471 |
</tr>
|
472 |
<tr id="section_7"> |
473 |
<td class="docs"> |
474 |
<div class="pilwrap"> |
475 |
<a class="pilcrow" href="#section_7">¶</a> |
476 |
</div>
|
477 |
<p>Retry for timeouts and service unavailable</p> |
478 |
|
479 |
</td>
|
480 |
<td class="code"> |
481 |
<pre><code class='prettyprint'> else if (we.Status == WebExceptionStatus.Timeout || |
482 |
(we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable)) |
483 |
{ |
484 |
TimedOut = true; |
485 |
if (retryCount == 0) |
486 |
{ |
487 |
Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e); |
488 |
tcs.SetException(new RetryException("Timed out too many times.", e)); |
489 |
} |
490 |
else |
491 |
{ |
492 |
Trace.TraceError( |
493 |
"[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout, |
494 |
retryCount, e); |
495 |
Retry(original, retryCount - 1, tcs); |
496 |
} |
497 |
} |
498 |
else |
499 |
tcs.SetException(e); |
500 |
} |
501 |
}; |
502 |
}); |
503 |
return tcs.Task; |
504 |
} |
505 |
|
506 |
private HttpStatusCode GetStatusCode(WebException we) |
507 |
{ |
508 |
var statusCode = HttpStatusCode.RequestTimeout; |
509 |
if (we.Response != null) |
510 |
{ |
511 |
statusCode = ((HttpWebResponse) we.Response).StatusCode; |
512 |
this.StatusCode = statusCode; |
513 |
} |
514 |
return statusCode; |
515 |
} |
516 |
} |
517 |
|
518 |
public class RetryException:Exception |
519 |
{ |
520 |
public RetryException() |
521 |
:base() |
522 |
{ |
523 |
|
524 |
} |
525 |
|
526 |
public RetryException(string message) |
527 |
:base(message) |
528 |
{ |
529 |
|
530 |
} |
531 |
|
532 |
public RetryException(string message,Exception innerException) |
533 |
:base(message,innerException) |
534 |
{ |
535 |
|
536 |
} |
537 |
|
538 |
public RetryException(SerializationInfo info,StreamingContext context) |
539 |
:base(info,context) |
540 |
{ |
541 |
|
542 |
} |
543 |
} |
544 |
} |
545 |
</code></pre> |
546 |
</td>
|
547 |
</tr>
|
548 |
</tbody>
|
549 |
</table>
|
550 |
</div>
|
551 |
</body>
|
552 |
</html>
|