using System;
using System.Collections;
using System.Threading;
using System.Threading.Tasks;
using Best.HTTP.Shared;
using UnityEngine;
#if WITH_UNITASK
using Cysharp.Threading.Tasks;
#endif
namespace Best.HTTP
{
///
/// Represents an exception thrown during or as a result of a Task-based asynchronous HTTP operations.
///
public sealed class AsyncHTTPException : Exception
{
///
/// Gets the status code of the server's response.
///
public readonly int StatusCode;
///
/// Gets the content sent by the server. This is usually an error page for 4xx or 5xx responses.
///
public readonly string Content;
public AsyncHTTPException(string message)
: base(message)
{
}
public AsyncHTTPException(string message, Exception innerException)
: base(message, innerException)
{
}
public AsyncHTTPException(int statusCode, string message, string content)
:base(message)
{
this.StatusCode = statusCode;
this.Content = content;
}
public override string ToString()
{
return string.Format("StatusCode: {0}, Message: {1}, Content: {2}, StackTrace: {3}", this.StatusCode, this.Message, this.Content, this.StackTrace);
}
}
///
/// A collection of extension methods for working with HTTP requests asynchronously using .
///
public static class HTTPRequestAsyncExtensions
{
///
/// Asynchronously sends an HTTP request and retrieves the response as an .
///
/// The to send.
/// A cancellation token that can be used to cancel the operation.
///
/// A Task that represents the asynchronous operation. The Task will complete with the retrieved AssetBundle
/// if the request succeeds. If the request fails or is canceled, the Task will complete with an exception.
///
#if WITH_UNITASK
public static UniTask GetAssetBundleAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetAssetBundleAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return CreateTask(request, token,
#if UNITY_2023_1_OR_NEWER
async
#endif
(req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
var bundleLoadOp = AssetBundle.LoadFromMemoryAsync(resp.Data);
#if !UNITY_2023_1_OR_NEWER
HTTPUpdateDelegator.Instance.StartCoroutine(BundleLoader(bundleLoadOp, tcs));
#else
await Awaitable.FromAsyncOperation(bundleLoadOp);
tcs.TrySetResult(bundleLoadOp.assetBundle);
#endif
}
else
GenericResponseHandler(req, resp, tcs);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
#if !UNITY_2023_1_OR_NEWER
#if WITH_UNITASK
static IEnumerator BundleLoader(AssetBundleCreateRequest req, UniTaskCompletionSource tcs)
#else
static IEnumerator BundleLoader(AssetBundleCreateRequest req, TaskCompletionSource tcs)
#endif
{
yield return req;
tcs.TrySetResult(req.assetBundle);
}
#endif
///
/// Asynchronously sends an HTTP request and retrieves the raw .
///
///
/// This method is particularly useful when you want to access the raw response without any specific processing
/// like converting the data into a string, texture, or other formats. It provides flexibility in handling
/// the response for custom or advanced use cases.
///
/// The to send.
/// An optional that can be used to cancel the operation.
///
/// A that represents the asynchronous operation. The value of TResult is the raw .
/// If the request completes successfully, the task will return the HTTPResponse. If there's an error during the request or if
/// the request gets canceled, the task will throw an exception, which can be caught and processed by the calling method.
///
/// Thrown if there's an error in the request or if the server returns an error status code.
#if WITH_UNITASK
public static UniTask GetHTTPResponseAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetHTTPResponseAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return CreateTask(request, token, (req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
tcs.TrySetResult(resp);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
///
/// Asynchronously sends an and retrieves the response content as a string.
///
/// The to send.
/// A cancellation token that can be used to cancel the operation.
///
/// A Task that represents the asynchronous operation. The Task will complete with the retrieved string content
/// if the request succeeds. If the request fails or is canceled, the Task will complete with an exception.
///
#if WITH_UNITASK
public static UniTask GetAsStringAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetAsStringAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return CreateTask(request, token, (req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
tcs.TrySetResult(resp.DataAsText);
else
GenericResponseHandler(req, resp, tcs);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
///
/// Asynchronously sends an and retrieves the response content as a .
///
/// The to send.
/// A cancellation token that can be used to cancel the operation.
///
/// A Task that represents the asynchronous operation. The Task will complete with the retrieved
/// if the request succeeds. If the request fails or is canceled, the Task will complete with an exception.
///
#if WITH_UNITASK
public static UniTask GetAsTexture2DAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetAsTexture2DAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return CreateTask(request, token, (req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
tcs.TrySetResult(resp.DataAsTexture2D);
else
GenericResponseHandler(req, resp, tcs);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
///
/// Asynchronously sends an and retrieves the response content as a byte[].
///
/// The to send.
/// A cancellation token that can be used to cancel the operation.
///
/// A Task that represents the asynchronous operation. The Task will complete with the retrieved byte[]
/// if the request succeeds. If the request fails or is canceled, the Task will complete with an exception.
///
#if WITH_UNITASK
public static UniTask GetRawDataAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetRawDataAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return CreateTask(request, token, (req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
tcs.TrySetResult(resp.Data);
else
GenericResponseHandler(req, resp, tcs);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
///
/// Asynchronously sends an and deserializes the response content into an object of type T using JSON deserialization.
///
/// The type to deserialize the JSON content into.
/// The to send.
/// A cancellation token that can be used to cancel the operation.
///
/// A Task that represents the asynchronous operation. The Task will complete with the deserialized object
/// if the request succeeds and the response content can be deserialized. If the request fails, is canceled, or
/// the response cannot be deserialized, the Task will complete with an exception.
///
#if WITH_UNITASK
public static UniTask GetFromJsonResultAsync(this HTTPRequest request, CancellationToken token = default)
#else
public static Task GetFromJsonResultAsync(this HTTPRequest request, CancellationToken token = default)
#endif
{
return HTTPRequestAsyncExtensions.CreateTask(request, token, (req, resp, tcs) =>
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
try
{
tcs.TrySetResult(Best.HTTP.JSON.LitJson.JsonMapper.ToObject(resp.DataAsText));
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
else
GenericResponseHandler(req, resp, tcs);
break;
default:
GenericResponseHandler(req, resp, tcs);
break;
}
});
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
#if WITH_UNITASK
public static UniTask CreateTask(HTTPRequest request, CancellationToken token, Action> callback)
#else
public static Task CreateTask(HTTPRequest request, CancellationToken token, Action> callback)
#endif
{
HTTPManager.Setup();
#if WITH_UNITASK
var tcs = new UniTaskCompletionSource();
#else
var tcs = new TaskCompletionSource();
#endif
request.Callback = (req, resp) =>
{
if (token.IsCancellationRequested)
tcs.TrySetCanceled();
else
callback(req, resp, tcs);
};
if (token.CanBeCanceled)
token.Register((state) => (state as HTTPRequest)?.Abort(), request);
if (request.State == HTTPRequestStates.Initial)
request.Send();
return tcs.Task;
}
#if WITH_UNITASK
public static void GenericResponseHandler(HTTPRequest req, HTTPResponse resp, UniTaskCompletionSource tcs)
#else
public static void GenericResponseHandler(HTTPRequest req, HTTPResponse resp, TaskCompletionSource tcs)
#endif
{
switch (req.State)
{
// The request finished without any problem.
case HTTPRequestStates.Finished:
if (!resp.IsSuccess)
tcs.TrySetException(CreateException($"Request finished Successfully, but the server sent an error ({resp.StatusCode} - '{resp.Message}').", resp));
break;
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
case HTTPRequestStates.Error:
Log(req, $"Request Finished with Error! {req.Exception?.Message} - {req.Exception?.StackTrace}");
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
break;
// The request aborted, initiated by the user.
case HTTPRequestStates.Aborted:
Log(req, "Request Aborted!");
tcs.TrySetCanceled();
break;
// Connecting to the server is timed out.
case HTTPRequestStates.ConnectionTimedOut:
Log(req, "Connection Timed Out!");
tcs.TrySetException(CreateException("Connection Timed Out!"));
break;
// The request didn't finished in the given time.
case HTTPRequestStates.TimedOut:
Log(req, "Processing the request Timed Out!");
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
break;
}
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static void Log(HTTPRequest request, string str)
{
HTTPManager.Logger.Verbose(nameof(HTTPRequestAsyncExtensions), str, request.Context);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static Exception CreateException(string errorMessage, HTTPResponse resp = null, Exception ex = null)
{
if (resp != null)
return new AsyncHTTPException(resp.StatusCode, resp.Message, resp.DataAsText);
else if (ex != null)
return new AsyncHTTPException(ex.Message, ex);
else
return new AsyncHTTPException(errorMessage);
}
}
}