-
Notifications
You must be signed in to change notification settings - Fork 212
/
Response.cs
339 lines (275 loc) · 10.2 KB
/
Response.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
//-----------------------------------------------------------------------
// <copyright file="Response.cs" company="Mapbox">
// Copyright (c) 2016 Mapbox. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_2017_1_OR_NEWER
#define UNITY
#endif
namespace Mapbox.Platform
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using Utils;
#if NETFX_CORE
using System.Net.Http;
using System.Threading.Tasks;
#endif
#if UNITY
using UnityEngine.Networking;
using Mapbox.Unity.Utilities;
#endif
/// <summary> A response from a <see cref="IFileSource" /> request. </summary>
public class Response
{
private Response() { }
public IAsyncRequest Request { get; private set; }
public bool RateLimitHit
{
get { return StatusCode.HasValue ? 429 == StatusCode.Value : false; }
}
/// <summary>Flag to indicate if the request was successful</summary>
public bool HasError
{
get { return _exceptions == null ? false : _exceptions.Count > 0; }
}
/// <summary>Flag to indicate if the request was fullfilled from a local cache</summary>
public bool LoadedFromCache;
/// <summary>Flag to indicate if the request was issued before but was issued again and updated</summary>
public bool IsUpdate = false;
public string RequestUrl;
public int? StatusCode;
public string ContentType;
/// <summary>Length of rate-limiting interval in seconds. https://www.mapbox.com/api-documentation/#rate-limits </summary>
public int? XRateLimitInterval;
/// <summary>Maximum number of requests you may make in the current interval before reaching the limit. https://www.mapbox.com/api-documentation/#rate-limits </summary>
public long? XRateLimitLimit;
/// <summary>Timestamp of when the current interval will end and the ratelimit counter is reset. https://www.mapbox.com/api-documentation/#rate-limits </summary>
public DateTime? XRateLimitReset;
private List<Exception> _exceptions;
/// <summary> Exceptions that might have occured during the request. </summary>
public ReadOnlyCollection<Exception> Exceptions
{
get { return null == _exceptions ? null : _exceptions.AsReadOnly(); }
}
/// <summary> Messages of exceptions otherwise empty string. </summary>
public string ExceptionsAsString
{
get
{
if (null == _exceptions || _exceptions.Count == 0) { return string.Empty; }
return string.Join(Environment.NewLine, _exceptions.Select(e => e.Message).ToArray());
}
}
/// <summary> Headers of the response. </summary>
public Dictionary<string, string> Headers;
/// <summary> Raw data fetched from the request. </summary>
public byte[] Data;
public void AddException(Exception ex)
{
if (null == _exceptions) { _exceptions = new List<Exception>(); }
_exceptions.Add(ex);
}
// TODO: we should store timestamp of the cache!
public static Response FromCache(byte[] data)
{
Response response = new Response();
response.Data = data;
response.LoadedFromCache = true;
return response;
}
#if !NETFX_CORE && !UNITY // full .NET Framework
public static Response FromWebResponse(IAsyncRequest request, HttpWebResponse apiResponse, Exception apiEx) {
Response response = new Response();
response.Request = request;
if (null != apiEx) {
response.AddException(apiEx);
}
// timeout: API response is null
if (null == apiResponse) {
response.AddException(new Exception("No Reponse."));
} else {
// https://www.mapbox.com/api-documentation/#rate-limits
if (null != apiResponse.Headers) {
response.Headers = new Dictionary<string, string>();
for (int i = 0; i < apiResponse.Headers.Count; i++) {
// TODO: implement .Net Core / UWP implementation
string key = apiResponse.Headers.Keys[i];
string val = apiResponse.Headers[i];
response.Headers.Add(key, val);
if (key.Equals("X-Rate-Limit-Interval", StringComparison.InvariantCultureIgnoreCase)) {
int limitInterval;
if (int.TryParse(val, out limitInterval)) { response.XRateLimitInterval = limitInterval; }
} else if (key.Equals("X-Rate-Limit-Limit", StringComparison.InvariantCultureIgnoreCase)) {
long limitLimit;
if (long.TryParse(val, out limitLimit)) { response.XRateLimitLimit = limitLimit; }
} else if (key.Equals("X-Rate-Limit-Reset", StringComparison.InvariantCultureIgnoreCase)) {
double unixTimestamp;
if (double.TryParse(val, out unixTimestamp)) {
response.XRateLimitReset = UnixTimestampUtils.From(unixTimestamp);
}
} else if (key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase)) {
response.ContentType = val;
}
}
}
if (apiResponse.StatusCode != HttpStatusCode.OK) {
response.AddException(new Exception(string.Format("{0}: {1}", apiResponse.StatusCode, apiResponse.StatusDescription)));
}
int statusCode = (int)apiResponse.StatusCode;
response.StatusCode = statusCode;
if (429 == statusCode) {
response.AddException(new Exception("Rate limit hit"));
}
if (null != apiResponse) {
using (Stream responseStream = apiResponse.GetResponseStream()) {
byte[] buffer = new byte[0x1000];
int bytesRead;
using (MemoryStream ms = new MemoryStream()) {
while (0 != (bytesRead = responseStream.Read(buffer, 0, buffer.Length))) {
ms.Write(buffer, 0, bytesRead);
}
response.Data = ms.ToArray();
}
}
apiResponse.Close();
}
}
return response;
}
#endif
#if NETFX_CORE && !UNITY //UWP but not Unity
public static async Task<Response> FromWebResponse(IAsyncRequest request, HttpResponseMessage apiResponse, Exception apiEx) {
Response response = new Response();
response.Request = request;
if (null != apiEx) {
response.AddException(apiEx);
}
// timeout: API response is null
if (null == apiResponse) {
response.AddException(new Exception("No Reponse."));
} else {
// https://www.mapbox.com/api-documentation/#rate-limits
if (null != apiResponse.Headers) {
response.Headers = new Dictionary<string, string>();
foreach (var hdr in apiResponse.Headers) {
string key = hdr.Key;
string val = hdr.Value.FirstOrDefault();
response.Headers.Add(key, val);
if (key.Equals("X-Rate-Limit-Interval", StringComparison.OrdinalIgnoreCase)) {
int limitInterval;
if (int.TryParse(val, out limitInterval)) { response.XRateLimitInterval = limitInterval; }
} else if (key.Equals("X-Rate-Limit-Limit", StringComparison.OrdinalIgnoreCase)) {
long limitLimit;
if (long.TryParse(val, out limitLimit)) { response.XRateLimitLimit = limitLimit; }
} else if (key.Equals("X-Rate-Limit-Reset", StringComparison.OrdinalIgnoreCase)) {
double unixTimestamp;
if (double.TryParse(val, out unixTimestamp)) {
DateTime beginningOfTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
response.XRateLimitReset = beginningOfTime.AddSeconds(unixTimestamp).ToLocalTime();
}
} else if (key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) {
response.ContentType = val;
}
}
}
if (apiResponse.StatusCode != HttpStatusCode.OK) {
response.AddException(new Exception(string.Format("{0}: {1}", apiResponse.StatusCode, apiResponse.ReasonPhrase)));
}
int statusCode = (int)apiResponse.StatusCode;
response.StatusCode = statusCode;
if (429 == statusCode) {
response.AddException(new Exception("Rate limit hit"));
}
if (null != apiResponse) {
response.Data = await apiResponse.Content.ReadAsByteArrayAsync();
}
}
return response;
}
#endif
#if UNITY // within Unity or UWP build from Unity
public static Response FromWebResponse(IAsyncRequest request, UnityWebRequest apiResponse, Exception apiEx)
{
Response response = new Response();
response.Request = request;
if (null != apiEx)
{
response.AddException(apiEx);
}
// additional string.empty check for apiResponse.error:
// on UWP isNetworkError is sometimes set to true despite all being well
if (apiResponse.isNetworkError && !string.IsNullOrEmpty(apiResponse.error))
{
response.AddException(new Exception(apiResponse.error));
}
if (request.RequestType != HttpRequestType.Head)
{
if (null == apiResponse.downloadHandler.data)
{
response.AddException(new Exception("Response has no data."));
}
}
#if NETFX_CORE
StringComparison stringComp = StringComparison.OrdinalIgnoreCase;
#elif WINDOWS_UWP
StringComparison stringComp = StringComparison.OrdinalIgnoreCase;
#else
StringComparison stringComp = StringComparison.InvariantCultureIgnoreCase;
#endif
Dictionary<string, string> apiHeaders = apiResponse.GetResponseHeaders();
if (null != apiHeaders)
{
response.Headers = new Dictionary<string, string>();
foreach (var apiHdr in apiHeaders)
{
string key = apiHdr.Key;
string val = apiHdr.Value;
response.Headers.Add(key, val);
if (key.Equals("X-Rate-Limit-Interval", stringComp))
{
int limitInterval;
if (int.TryParse(val, out limitInterval)) { response.XRateLimitInterval = limitInterval; }
}
else if (key.Equals("X-Rate-Limit-Limit", stringComp))
{
long limitLimit;
if (long.TryParse(val, out limitLimit)) { response.XRateLimitLimit = limitLimit; }
}
else if (key.Equals("X-Rate-Limit-Reset", stringComp))
{
double unixTimestamp;
if (double.TryParse(val, out unixTimestamp))
{
response.XRateLimitReset = UnixTimestampUtils.From(unixTimestamp);
}
}
else if (key.Equals("Content-Type", stringComp))
{
response.ContentType = val;
}
}
}
int statusCode = (int)apiResponse.responseCode;
response.StatusCode = statusCode;
if (statusCode != 200)
{
response.AddException(new Exception(string.Format("Status Code {0}", apiResponse.responseCode)));
}
if (429 == statusCode)
{
response.AddException(new Exception("Rate limit hit"));
}
if (request.RequestType != HttpRequestType.Head)
{
response.Data = apiResponse.downloadHandler.data;
}
return response;
}
#endif
}
}