2 using System.Collections.Generic;
\r
3 using System.ComponentModel;
\r
4 using System.Diagnostics.Contracts;
\r
6 using System.Net.Http;
\r
7 using System.Net.Http.Headers;
\r
8 using System.Reflection;
\r
12 using System.Threading;
\r
13 using System.Threading.Tasks;
\r
16 namespace Pithos.Network
\r
18 public static class WebExtensions
\r
20 private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
\r
22 public static string ReadToEnd(this HttpWebResponse response)
\r
24 using (var stream = response.GetResponseStream())
\r
28 using (var reader = new StreamReader(stream))
\r
30 var body = reader.ReadToEnd();
\r
36 public static void LogError(this ILog log, HttpWebResponse response)
\r
38 if (log.IsDebugEnabled)
\r
40 if (response != null)
\r
42 var body = response.ReadToEnd();
\r
43 log.ErrorFormat("Headers:\n{0}\nBody:{1}", response.Headers, body);
\r
48 public static TextReader GetLoggedReader(this Stream stream, ILog log)
\r
50 var reader = new StreamReader(stream);
\r
51 if (!log.IsDebugEnabled)
\r
56 var body = reader.ReadToEnd();
\r
57 log.DebugFormat("JSON response: {0}", body);
\r
58 return new StringReader(body);
\r
63 public static IEnumerable<T> Range<T>(this IList<T> list, int start, int end)
\r
65 Contract.Requires(start >= 0);
\r
66 Contract.Requires(end < list.Count);
\r
67 Contract.Requires(start <= end);
\r
72 for (var i = 0; i <= end; i++)
\r
74 yield return list[i];
\r
79 public static Task<byte[]> UploadDataTaskAsync(this WebClient webClient, Uri address, string method, byte[] data, CancellationToken cancellationToken, IProgress<UploadProgressChangedEventArgs> progress)
\r
81 var tcs = new TaskCompletionSource<byte[]>(address);
\r
82 if (cancellationToken.IsCancellationRequested)
\r
84 tcs.TrySetCanceled();
\r
88 CancellationTokenRegistration ctr = cancellationToken.Register(()=>
\r
90 webClient.CancelAsync();
\r
92 UploadDataCompletedEventHandler completedHandler = null;
\r
93 UploadProgressChangedEventHandler progressHandler = null;
\r
94 if (progress != null)
\r
95 progressHandler = (s, e) => PithosEAPCommon.HandleProgress(tcs, e, () => e, progress);
\r
96 completedHandler =(sender, e) =>PithosEAPCommon.HandleCompletion(tcs, true, e,() => e.Result,() =>
\r
99 webClient.UploadDataCompleted -= completedHandler;
\r
100 webClient.UploadProgressChanged -= progressHandler;
\r
102 webClient.UploadDataCompleted += completedHandler;
\r
103 webClient.UploadProgressChanged += progressHandler;
\r
106 webClient.UploadDataAsync(address, method, data, tcs);
\r
107 if (cancellationToken.IsCancellationRequested)
\r
108 webClient.CancelAsync();
\r
112 webClient.UploadDataCompleted -= completedHandler;
\r
113 webClient.UploadProgressChanged -= progressHandler;
\r
120 public static async Task<T> WithRetries<T>(this Func<Task<T>> func, int retries)
\r
122 while (retries > 0)
\r
126 var result = await func().ConfigureAwait(false);
\r
129 catch (Exception exc)
\r
131 if (--retries == 0)
\r
132 throw new RetryException("Failed too many times", exc);
\r
135 throw new RetryException();
\r
138 public static async Task<HttpResponseMessage> WithRetriesForWeb(this Func<Task<HttpResponseMessage>> func, int retries)
\r
140 var waitTime = TimeSpan.FromSeconds(10);
\r
141 var acceptedCodes = new[] { HttpStatusCode.Moved, HttpStatusCode.MovedPermanently, HttpStatusCode.Found, HttpStatusCode.Redirect,HttpStatusCode.SeeOther,
\r
142 HttpStatusCode.RedirectMethod,HttpStatusCode.NotModified,HttpStatusCode.TemporaryRedirect,HttpStatusCode.RedirectKeepVerb};
\r
143 while (retries > 0)
\r
145 var result = await func().ConfigureAwait(false);
\r
146 if (result.IsSuccessStatusCode || acceptedCodes.Contains(result.StatusCode))
\r
149 if (--retries == 0)
\r
150 throw new RetryException("Failed too many times");
\r
152 //Wait for service unavailable
\r
153 if (result.StatusCode == HttpStatusCode.ServiceUnavailable)
\r
155 Log.InfoFormat("[UNAVAILABLE] Waiting before retry: {0}",result.ReasonPhrase);
\r
156 await TaskEx.Delay(waitTime).ConfigureAwait(false);
\r
157 //increase the timeout for repeated timeouts
\r
158 if (waitTime<TimeSpan.FromSeconds(10))
\r
159 waitTime = waitTime.Add(TimeSpan.FromSeconds(10));
\r
161 //Throw in all other cases
\r
163 result.EnsureSuccessStatusCode();
\r
165 throw new RetryException();
\r
169 public static async Task<string> GetStringAsyncWithRetries(this HttpClient client, Uri requestUri, int retries,DateTime? since=null)
\r
171 var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
\r
172 if (since.HasValue)
\r
174 request.Headers.IfModifiedSince = since.Value;
\r
176 //Func<Task<HttpResponseMessage>> call = () => _baseHttpClient.SendAsync(request);
\r
177 using (var response = await client.SendAsyncWithRetries(request,3).ConfigureAwait(false))
\r
179 if (response.StatusCode == HttpStatusCode.NoContent)
\r
180 return String.Empty;
\r
182 var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
\r
188 public static Task<HttpResponseMessage> HeadAsyncWithRetries(this HttpClient client, Uri requestUri, int retries)
\r
190 return client.HeadAsyncWithRetries(requestUri, retries, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
\r
193 public static Task<HttpResponseMessage> HeadAsyncWithRetries(this HttpClient client, Uri requestUri, int retries, HttpCompletionOption completionOption, CancellationToken cancellationToken)
\r
195 return client.SendAsyncWithRetries(new HttpRequestMessage(HttpMethod.Head, requestUri), retries, completionOption, cancellationToken);
\r
198 public static Task<HttpResponseMessage> GetAsyncWithRetries(this HttpClient client, Uri requestUri, int retries)
\r
200 return client.GetAsyncWithRetries(requestUri, retries, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
\r
203 public static Task<HttpResponseMessage> GetAsyncWithRetries(this HttpClient client, Uri requestUri, int retries, HttpCompletionOption completionOption, CancellationToken cancellationToken)
\r
205 return client.SendAsyncWithRetries(new HttpRequestMessage(HttpMethod.Get, requestUri), retries, completionOption, cancellationToken);
\r
209 public static Task<HttpResponseMessage> SendAsyncWithRetries(this HttpClient client,HttpRequestMessage message, int retries)
\r
211 return client.SendAsyncWithRetries(message, retries, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
\r
214 public static async Task<HttpResponseMessage> SendAsyncWithRetries(this HttpClient client,HttpRequestMessage message, int retries,HttpCompletionOption completionOption, CancellationToken cancellationToken)
\r
216 var waitTime = TimeSpan.FromSeconds(10);
\r
217 var acceptedCodes = new[] { HttpStatusCode.Moved, HttpStatusCode.MovedPermanently, HttpStatusCode.Found, HttpStatusCode.Redirect,HttpStatusCode.SeeOther,
\r
218 HttpStatusCode.RedirectMethod,HttpStatusCode.NotModified,HttpStatusCode.TemporaryRedirect,HttpStatusCode.RedirectKeepVerb,HttpStatusCode.Conflict};
\r
219 while (retries > 0)
\r
221 var timedOut = false;
\r
222 if (Log.IsDebugEnabled)
\r
223 Log.DebugFormat("[REQUEST] {0}", message);
\r
224 HttpResponseMessage result=null;
\r
227 result = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false);
\r
229 catch (WebException exc)
\r
231 if (exc.Status != WebExceptionStatus.Timeout)
\r
234 if (Log.IsDebugEnabled)
\r
235 Log.DebugFormat("[RESPONSE] [{0}]:[{1}] FAIL WITH TIMEOUT", message.Method, message.RequestUri);
\r
237 catch (Exception exc)
\r
239 Log.FatalFormat("Unexpected error while sending:\n{0}\n{1}", message, exc);
\r
245 if (--retries == 0)
\r
246 throw new RetryException("Failed too many times");
\r
250 if (result.IsSuccessStatusCode || acceptedCodes.Contains(result.StatusCode))
\r
252 if (Log.IsDebugEnabled)
\r
253 Log.DebugFormat("[RESPONSE] [{0}]:[{1}] OK: [{2}]", message.Method, message.RequestUri,
\r
254 result.StatusCode);
\r
257 //Failed, will have to abort or retry
\r
258 if (Log.IsDebugEnabled)
\r
259 Log.DebugFormat("[RESPONSE] [{0}]:[{1}] FAIL: [{2}]\n{3}", message.Method, message.RequestUri,
\r
260 result.StatusCode, result);
\r
262 if (--retries == 0)
\r
263 throw new RetryException("Failed too many times");
\r
265 //Wait for service unavailable
\r
266 if (result.StatusCode == HttpStatusCode.ServiceUnavailable ||
\r
267 result.StatusCode == HttpStatusCode.BadGateway)
\r
270 Log.WarnFormat("[UNAVAILABLE] Waiting before retrying [{0}]:[{1}] due to [{2}]", message.Method,
\r
271 message.RequestUri, result.ReasonPhrase);
\r
272 await TaskEx.Delay(waitTime).ConfigureAwait(false);
\r
273 //increase the timeout for repeated timeouts
\r
274 if (waitTime < TimeSpan.FromSeconds(10))
\r
275 waitTime = waitTime.Add(TimeSpan.FromSeconds(10));
\r
277 //Throw in all other cases
\r
279 result.EnsureSuccessStatusCode();
\r
281 throw new RetryException();
\r
284 public static string GetFirstValue(this HttpResponseHeaders headers, string name)
\r
287 throw new ArgumentNullException("headers");
\r
288 if (String.IsNullOrWhiteSpace(name))
\r
289 throw new ArgumentNullException("name");
\r
290 Contract.EndContractBlock();
\r
292 IEnumerable<string> values;
\r
293 if (headers.TryGetValues(name, out values))
\r
295 return values.FirstOrDefault();
\r
300 public static Dictionary<string, string> GetMeta(this HttpResponseHeaders headers,string metaPrefix)
\r
302 if (headers == null)
\r
303 throw new ArgumentNullException("headers");
\r
304 if (String.IsNullOrWhiteSpace(metaPrefix))
\r
305 throw new ArgumentNullException("metaPrefix");
\r
306 Contract.EndContractBlock();
\r
308 var dict = (from header in headers
\r
309 where header.Key.StartsWith(metaPrefix)
\r
310 let name = header.Key.Substring(metaPrefix.Length)
\r
311 select new { Name = name, Value = String.Join(",",header.Value) })
\r
312 .ToDictionary(t => t.Name, t => t.Value);
\r
318 internal static class PithosEAPCommon
\r
320 internal static void HandleProgress<T, E>(TaskCompletionSource<T> tcs, ProgressChangedEventArgs eventArgs, Func<E> getProgress, IProgress<E> callback)
\r
322 if (eventArgs.UserState != tcs)
\r
324 callback.Report(getProgress());
\r
327 internal static void HandleCompletion<T>(TaskCompletionSource<T> tcs, bool requireMatch, AsyncCompletedEventArgs e, Func<T> getResult, Action unregisterHandler)
\r
331 if (e.UserState != tcs)
\r
336 unregisterHandler();
\r
341 tcs.TrySetCanceled();
\r
342 else if (e.Error != null)
\r
343 tcs.TrySetException(e.Error);
\r
345 tcs.TrySetResult(getResult());
\r