Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / PithosClient.cs @ 82db721b

History | View | Annotate | Download (14.5 kB)

1
// -----------------------------------------------------------------------
2
// <copyright file="PithosClient.cs" company="Microsoft">
3
// TODO: Update copyright text.
4
// </copyright>
5
// -----------------------------------------------------------------------
6

    
7
using System.Collections.Specialized;
8
using System.Diagnostics;
9
using System.Diagnostics.Contracts;
10
using System.IO;
11
using System.Net;
12
using System.Runtime.Serialization;
13
using System.Threading.Tasks;
14

    
15
namespace Pithos.Network
16
{
17
    using System;
18
    using System.Collections.Generic;
19
    using System.Linq;
20
    using System.Text;
21

    
22
    /// <summary>
23
    /// TODO: Update summary.
24
    /// </summary>
25
    public class PithosClient:WebClient
26
    {
27
        public int Timeout { get; set; }
28

    
29
        public bool TimedOut { get; set; }
30

    
31
        public HttpStatusCode StatusCode { get; private set; }
32

    
33
        public string StatusDescription { get; set; }
34

    
35

    
36
        public int Retries { get; set; }
37

    
38
        private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();
39
        public Dictionary<string, string> Parameters
40
        {
41
            get { return _parameters; }            
42
        }
43

    
44
        public PithosClient():base()
45
        {
46
            
47
        }
48

    
49
       
50
        public PithosClient(PithosClient other)
51
            : base()
52
        {
53
            CopyHeaders(other);
54
            Timeout = other.Timeout;
55
            Retries = other.Retries;
56
            BaseAddress = other.BaseAddress;             
57

    
58
            foreach (var parameter in other.Parameters)
59
            {
60
                Parameters.Add(parameter.Key,parameter.Value);
61
            }
62

    
63
            this.Proxy = other.Proxy;
64
        }
65

    
66
        protected override WebRequest GetWebRequest(Uri address)
67
        {
68
            TimedOut = false;
69
            HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;
70
            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
71
            if(Timeout>0)
72
                request.Timeout = Timeout;
73
            return request; 
74
        }
75

    
76
        protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
77
        {
78
            var response = (HttpWebResponse) base.GetWebResponse(request, result);
79
            StatusCode=response.StatusCode;
80
            StatusDescription=response.StatusDescription;
81
            return response;
82
        }
83

    
84

    
85

    
86
        protected override WebResponse GetWebResponse(WebRequest request)
87
        {
88
            try
89
            {
90

    
91
            var response = (HttpWebResponse)base.GetWebResponse(request);
92
            StatusCode = response.StatusCode;
93
            StatusDescription = response.StatusDescription;
94
            return response;
95
            }
96
            catch (WebException exc)
97
            {
98
                if (exc.Response!=null && exc.Response.ContentLength > 0)
99
                {
100
                    string content = GetContent(exc.Response);
101
                    Trace.TraceError(content);
102
                }
103
                throw;
104
            }
105
        }
106

    
107
        private static string GetContent(WebResponse webResponse)
108
        {
109
            string content;
110
            using (var stream = webResponse.GetResponseStream())
111
            using (var reader = new StreamReader(stream))
112
            {
113
                content = reader.ReadToEnd();
114
            }
115
            return content;
116
        }
117

    
118
        public string DownloadStringWithRetry(string address,int retries=0)
119
        {
120
            if (address == null)
121
                throw new ArgumentNullException("address");
122

    
123
            var actualAddress = GetActualAddress(address);
124

    
125
            TraceStart("GET",actualAddress);            
126
            
127
            var actualRetries = (retries == 0) ? Retries : retries;
128
            
129

    
130
            
131
            var task = Retry(() =>
132
            {
133
                var uriString = String.Join("/", BaseAddress, actualAddress);
134
                var content = base.DownloadString(uriString);
135

    
136
                if (StatusCode == HttpStatusCode.NoContent)
137
                    return String.Empty;
138
                return content;
139

    
140
            }, actualRetries);
141

    
142
            var result = task.Result;
143
            return result;
144
        }
145

    
146
        public void Head(string address,int retries=0)
147
        {
148
            RetryWithoutContent(address, retries, "HEAD");
149
        }
150

    
151
        public void PutWithRetry(string address, int retries = 0)
152
        {
153
            RetryWithoutContent(address, retries, "PUT");
154
        }
155

    
156
        public void DeleteWithRetry(string address,int retries=0)
157
        {
158
            RetryWithoutContent(address, retries, "DELETE");
159
        }
160

    
161
        public string GetHeaderValue(string headerName)
162
        {
163
            var values=this.ResponseHeaders.GetValues(headerName);
164
            if (values == null)
165
                throw new WebException(String.Format("The {0}  header is missing", headerName));
166
            else
167
                return values[0];
168
        }
169

    
170
        private void RetryWithoutContent(string address, int retries, string method)
171
        {
172
            if (address == null)
173
                throw new ArgumentNullException("address");
174

    
175
            var actualAddress = GetActualAddress(address);            
176
            var actualRetries = (retries == 0) ? Retries : retries;
177

    
178
            var task = Retry(() =>
179
            {
180
                var uriString = String.Join("/",BaseAddress ,actualAddress);
181
                var uri = new Uri(uriString);
182
                var request =  GetWebRequest(uri);
183
                request.Method = method;
184
                if (ResponseHeaders!=null)
185
                    ResponseHeaders.Clear();
186

    
187
                TraceStart(method, uriString);
188

    
189
                var response = (HttpWebResponse)GetWebResponse(request);
190
                StatusCode = response.StatusCode;
191
                StatusDescription = response.StatusDescription;                
192
                
193

    
194
                return 0;
195
            }, actualRetries);
196

    
197
            task.Wait();
198
        }
199

    
200
        private static void TraceStart(string method, string actualAddress)
201
        {
202
            Trace.WriteLine(String.Format("[{0}] {1} {2}", method, DateTime.Now, actualAddress));
203
        }
204

    
205
        private string GetActualAddress(string address)
206
        {
207
            if (Parameters.Count == 0)
208
                return address;
209
            var addressBuilder=new StringBuilder(address);            
210

    
211
            bool isFirst = true;
212
            foreach (var parameter in Parameters)
213
            {
214
                if(isFirst)
215
                    addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);
216
                else
217
                    addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);
218
                isFirst = false;
219
            }
220
            return addressBuilder.ToString();
221
        }
222

    
223
        public string DownloadStringWithRetry(Uri address,int retries=0)
224
        {
225
            if (address == null)
226
                throw new ArgumentNullException("address");
227

    
228
            var actualRetries = (retries == 0) ? Retries : retries;            
229
            var task = Retry(() =>
230
            {
231
                var content = base.DownloadString(address);
232

    
233
                if (StatusCode == HttpStatusCode.NoContent)
234
                    return String.Empty;
235
                return content;
236

    
237
            }, actualRetries);
238

    
239
            var result = task.Result;
240
            return result;
241
        }
242

    
243
      
244
        /// <summary>
245
        /// Copies headers from another PithosClient
246
        /// </summary>
247
        /// <param name="source">The PithosClient from which the headers are copied</param>
248
        public void CopyHeaders(PithosClient source)
249
        {
250
            Contract.Requires(source != null, "source can't be null");
251
            if (source == null)
252
                throw new ArgumentNullException("source", "source can't be null");
253
            CopyHeaders(source.Headers,Headers);
254
        }
255
        
256
        /// <summary>
257
        /// Copies headers from one header collection to another
258
        /// </summary>
259
        /// <param name="source">The source collection from which the headers are copied</param>
260
        /// <param name="target">The target collection to which the headers are copied</param>
261
        public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)
262
        {
263
            Contract.Requires(source != null, "source can't be null");
264
            Contract.Requires(target != null, "target can't be null");
265
            if (source == null)
266
                throw new ArgumentNullException("source", "source can't be null");
267
            if (target == null)
268
                throw new ArgumentNullException("target", "target can't be null");
269
            for (int i = 0; i < source.Count; i++)
270
            {
271
                target.Add(source.GetKey(i), source[i]);
272
            }            
273
        }
274

    
275
        public void AssertStatusOK(string message)
276
        {
277
            if (StatusCode >= HttpStatusCode.BadRequest)
278
                throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));
279
        }
280

    
281
        /*private Func<T> Retry<T>(Func< T> original, int retryCount)
282
        {
283
            return () =>
284
            {
285
                while (true)
286
                {
287
                    try
288
                    {
289
                        return original();
290
                    }
291
                    catch (WebException e)
292
                    {
293
                        var statusCode = ((HttpWebResponse)e.Response).StatusCode;
294
                        this.StatusCode = statusCode;
295

    
296
                        switch (e.Status)
297
                        {
298
                            case WebExceptionStatus.Timeout:
299

    
300
                                TimedOut = true;
301
                                if (retryCount == 0)
302
                                {
303
                                    Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e);
304
                                    throw new RetryException("Timed out too many times.", e);
305
                                }
306
                                retryCount--;
307
                                Trace.TraceError(
308
                                    "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
309
                                    retryCount, e);
310

    
311
                                break;
312
                            case WebExceptionStatus.ProtocolError:
313
                                switch (statusCode)
314
                                {
315
                                    case HttpStatusCode.NotFound:
316
                                        {
317
                                            return default(T);
318
                                        }
319
                                    case HttpStatusCode.ServiceUnavailable:
320
                                        {
321

    
322
                                            TimedOut = false;
323
                                            if (retryCount == 0)
324
                                            {
325
                                                Trace.TraceError("[ERROR] Failed too many times. {0}\n", e);
326
                                                throw new RetryException("Failed too many times.", e);
327
                                            }
328
                                            retryCount--;
329
                                            Trace.TraceError(
330
                                                "[RETRY] Failed due to 503. Will retry {0} more times\n{1}", retryCount, e);
331
                                            break;
332
                                        }
333
                                    default:
334
                                        throw;
335
                                }
336
                                break;
337
                            default:
338
                                throw;
339
                        }
340
                    }
341
                }
342
            };
343
        }*/
344
        
345
        private Task<T> Retry<T>(Func< T> original, int retryCount)
346
        {
347
            return Task.Factory.StartNew(() => original()).ContinueWith(_original =>
348
                {
349
                    if(_original.IsFaulted )
350
                    {
351
                        var e = _original.Exception.InnerException;
352
                        if (e is WebException)
353
                        {
354
                            var we = (e as WebException);
355

    
356
                            var statusCode = HttpStatusCode.RequestTimeout;
357
                            if (we.Response != null)
358
                            {                                
359
                                statusCode = ((HttpWebResponse) we.Response).StatusCode;
360
                                this.StatusCode = statusCode;
361
                            }
362

    
363
                            if (we.Status==WebExceptionStatus.Timeout || 
364
                                (we.Status==WebExceptionStatus.ProtocolError && statusCode==HttpStatusCode.ServiceUnavailable))
365
                            {
366
                                TimedOut = true;
367
                                if (retryCount == 0)
368
                                {
369
                                    Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e);
370
                                    throw new RetryException("Timed out too many times.", e);
371
                                }
372
                                Trace.TraceError(
373
                                    "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
374
                                    retryCount, e);
375
                                return Retry(original, retryCount - 1);
376
                            }
377

    
378
                            if (statusCode==HttpStatusCode.NotFound)
379
                                return Task<T>.Factory.StartNew(() => default(T));                            
380
                        }
381
                        throw e;
382
                    }
383
                    else                    
384
                        return Task<T>.Factory.StartNew(() => _original.Result);
385
                }).Unwrap();
386
        }
387

    
388
       
389
    }
390

    
391
    public class RetryException:Exception
392
    {
393
        public RetryException()
394
            :base()
395
        {
396
            
397
        }
398

    
399
        public RetryException(string message)
400
            :base(message)
401
        {
402
            
403
        }
404

    
405
        public RetryException(string message,Exception innerException)
406
            :base(message,innerException)
407
        {
408
            
409
        }
410

    
411
        public RetryException(SerializationInfo info,StreamingContext context)
412
            :base(info,context)
413
        {
414
            
415
        }
416
    }
417
}