2024-04-22 15:52:51 +01:00
//-----------------------------------------------------------------------
// <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
2024-12-10 16:07:25 +00:00
if ( apiResponse . result == UnityWebRequest . Result . ConnectionError && ! string . IsNullOrEmpty ( apiResponse . error ))
2024-04-22 15:52:51 +01:00
{
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
}
}