You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							339 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							339 lines
						
					
					
						
							10 KiB
						
					
					
				| //-----------------------------------------------------------------------
 | |
| // <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-limit-headers </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-limit-headers </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-limit-headers </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-limit-headers
 | |
| 				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-limit-headers
 | |
| 				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
 | |
| 
 | |
| 
 | |
| 
 | |
| 	}
 | |
| }
 | |
| 
 |