271 lines
9.7 KiB
C#
271 lines
9.7 KiB
C#
using CoreGraphics;
|
|
using Foundation;
|
|
|
|
namespace Blahblah.Library.Network;
|
|
|
|
partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate
|
|
{
|
|
static bool IsResponseSuccess(NSHttpUrlResponse response)
|
|
{
|
|
return (int)response.StatusCode is >= 200 and < 300;
|
|
}
|
|
|
|
static NSMutableUrlRequest CreateUrlRequest(string url, string accept, string? referrer = null)
|
|
{
|
|
var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url));
|
|
var request = new NSMutableUrlRequest(uri)
|
|
{
|
|
[AcceptHeader] = accept
|
|
};
|
|
if (!string.IsNullOrEmpty(referrer))
|
|
{
|
|
request[ReferrerHeader] = referrer;
|
|
}
|
|
return request;
|
|
}
|
|
|
|
NSUrlSession? urlSession;
|
|
|
|
readonly Dictionary<NSUrlSessionTask, NetworkTask> tasks = new();
|
|
|
|
private NetworkHelper(int timeout, bool useCookie, bool waitsConnectivity = true, Dictionary<string, string>? additionalHeaders = null)
|
|
{
|
|
var configuration = NSUrlSessionConfiguration.EphemeralSessionConfiguration;
|
|
configuration.WaitsForConnectivity = waitsConnectivity;
|
|
configuration.RequestCachePolicy = NSUrlRequestCachePolicy.UseProtocolCachePolicy;
|
|
configuration.TimeoutIntervalForRequest = timeout;
|
|
if (additionalHeaders != null)
|
|
{
|
|
configuration.HttpAdditionalHeaders = NSDictionary.FromObjectsAndKeys(
|
|
additionalHeaders.Values.Select(v => FromObject(v)).ToArray(),
|
|
additionalHeaders.Keys.Select(k => FromObject(k)).ToArray());
|
|
}
|
|
if (useCookie)
|
|
{
|
|
configuration.HttpCookieStorage = NSHttpCookieStorage.SharedStorage;
|
|
}
|
|
else
|
|
{
|
|
configuration.HttpShouldSetCookies = false;
|
|
}
|
|
if (proxyHost != null && proxyPort != null)
|
|
{
|
|
configuration.StrongConnectionProxyDictionary = new ProxyConfigurationDictionary
|
|
{
|
|
HttpEnable = true,
|
|
HttpsProxyHost = proxyHost,
|
|
HttpsProxyPort = proxyPort,
|
|
HttpProxyHost = proxyHost,
|
|
HttpProxyPort = proxyPort
|
|
};
|
|
}
|
|
urlSession = NSUrlSession.FromConfiguration(configuration, this, null);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
lock (sync)
|
|
{
|
|
foreach (var task in tasks.Values)
|
|
{
|
|
task.Dispose();
|
|
}
|
|
}
|
|
if (urlSession != null)
|
|
{
|
|
urlSession.FinishTasksAndInvalidate();
|
|
urlSession.Dispose();
|
|
urlSession = null;
|
|
}
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public partial Task<NetworkResult<string>> GetContentAsync(string url, StringHandler? process, CancellationToken token)
|
|
{
|
|
var request = CreateUrlRequest(url, Accept ?? AcceptHttp, Referrer);
|
|
|
|
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
|
var taskSource = new TaskCompletionSource<NetworkResult<string>>();
|
|
var stringTask = new StringTask(url, taskSource, token)
|
|
{
|
|
Process = process
|
|
};
|
|
lock (sync)
|
|
{
|
|
tasks.Add(task, stringTask);
|
|
}
|
|
task.Resume();
|
|
return taskSource.Task;
|
|
}
|
|
|
|
public Task<NetworkResult<bool>> GetImageAsync(string url, string filePath, StepHandler<CGImage>? step = null, CancellationToken token = default)
|
|
{
|
|
var request = CreateUrlRequest(url, Accept ?? AcceptImage, Referrer);
|
|
|
|
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
|
var taskSource = new TaskCompletionSource<NetworkResult<bool>>();
|
|
var imageTask = new ImageTask(url, filePath, step != null, taskSource, token)
|
|
{
|
|
Step = step
|
|
};
|
|
lock (sync)
|
|
{
|
|
tasks.Add(task, imageTask);
|
|
}
|
|
task.Resume();
|
|
return taskSource.Task;
|
|
}
|
|
|
|
public Task<NetworkResult<string>> PostAsync(string url, string data, string contentType = ContentJson, CancellationToken token = default)
|
|
{
|
|
return RequestAsync(url, request =>
|
|
{
|
|
request[ContentTypeHeader] = contentType;
|
|
request.Body = NSData.FromString(data, NSStringEncoding.UTF8);
|
|
}, token);
|
|
}
|
|
|
|
public Task<NetworkResult<string>> PostAsync(string url, MultipartFormData[] forms, CancellationToken token = default)
|
|
{
|
|
return RequestAsync(url, request =>
|
|
{
|
|
var body = new NSMultipartFormData();
|
|
request[ContentTypeHeader] = body.ContentType;
|
|
foreach (var form in forms)
|
|
{
|
|
if (form is FieldFormData field)
|
|
{
|
|
if (!string.IsNullOrEmpty(field.Value))
|
|
{
|
|
body.AddTextField(field.Name, field.Value);
|
|
}
|
|
}
|
|
else if (form is FileFormData file)
|
|
{
|
|
body.AddDataField(file.Name, file.Stream, file.FileName, file.MimeType);
|
|
}
|
|
}
|
|
body.AddEnd();
|
|
request.Body = body.Data;
|
|
}, token);
|
|
}
|
|
|
|
public Task<NetworkResult<string>> RequestAsync(string url, Action<NSMutableUrlRequest>? prepare = null, CancellationToken token = default)
|
|
{
|
|
var request = CreateUrlRequest(url, Accept ?? AcceptJson, Referrer);
|
|
request.HttpMethod = "POST";
|
|
prepare?.Invoke(request);
|
|
|
|
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
|
var taskSource = new TaskCompletionSource<NetworkResult<string>>();
|
|
var stringTask = new StringTask(url, taskSource, token);
|
|
lock (sync)
|
|
{
|
|
tasks.Add(task, stringTask);
|
|
}
|
|
task.Resume();
|
|
return taskSource.Task;
|
|
}
|
|
|
|
bool TryGetTask(NSUrlSessionTask task, out NetworkTask? networkTask)
|
|
{
|
|
bool got;
|
|
lock (sync)
|
|
{
|
|
got = tasks.TryGetValue(task, out networkTask);
|
|
}
|
|
return got;
|
|
}
|
|
|
|
[Export("URLSession:dataTask:didReceiveResponse:completionHandler:")]
|
|
public void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
|
|
{
|
|
if (!TryGetTask(dataTask, out NetworkTask? networkTask) || networkTask == null)
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Cancel);
|
|
return;
|
|
}
|
|
if (networkTask.IsCancelled)
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Cancel);
|
|
networkTask.SetException(new TaskCanceledException($"Cancelled on response received: {dataTask.CurrentRequest?.Url}", null, networkTask.Token));
|
|
return;
|
|
}
|
|
if (response is NSHttpUrlResponse httpResponse)
|
|
{
|
|
if (IsResponseSuccess(httpResponse))
|
|
{
|
|
if (networkTask.OnResponded(httpResponse))
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Allow);
|
|
}
|
|
else
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Cancel);
|
|
networkTask.SetException(new TaskCanceledException($"Cancelled on task responded: {dataTask.CurrentRequest?.Url}", null, networkTask.Token));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Cancel);
|
|
networkTask.SetException(new HttpResponseException((int)httpResponse.StatusCode, httpResponse.Url.AbsoluteString ?? networkTask.Url));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
completionHandler(NSUrlSessionResponseDisposition.Cancel);
|
|
networkTask.SetException(new InvalidDataException($"Response is not instance of <NSHttpUrlResponse> but <{response?.GetType()}>"));
|
|
}
|
|
}
|
|
|
|
[Export("URLSession:dataTask:didReceiveData:")]
|
|
public void DidReceiveData(NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
|
|
{
|
|
if (!TryGetTask(dataTask, out NetworkTask? networkTask) || networkTask == null)
|
|
{
|
|
return;
|
|
}
|
|
if (networkTask.IsCancelled)
|
|
{
|
|
networkTask.SetException(new TaskCanceledException($"Cancelled on data received: {dataTask.CurrentRequest?.Url}", null, networkTask.Token));
|
|
return;
|
|
}
|
|
if (networkTask.Data == null)
|
|
{
|
|
networkTask.SetException(new InvalidDataException("<NSMutableData> is null"));
|
|
return;
|
|
}
|
|
networkTask.Data.AppendData(data);
|
|
networkTask.OnReceived((int)data.Length);
|
|
}
|
|
|
|
[Export("URLSession:task:didCompleteWithError:")]
|
|
public void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
|
|
{
|
|
if (!TryGetTask(task, out NetworkTask? networkTask) || networkTask == null)
|
|
{
|
|
return;
|
|
}
|
|
if (networkTask.IsCancelled)
|
|
{
|
|
networkTask.SetException(new TaskCanceledException($"Cancelled on completed: {task.CurrentRequest?.Url}", null, networkTask.Token));
|
|
return;
|
|
}
|
|
if (networkTask.Data == null)
|
|
{
|
|
networkTask.SetException(new InvalidDataException("<NSMutableData> is null"));
|
|
return;
|
|
}
|
|
if (error != null)
|
|
{
|
|
networkTask.SetException(new Exception(error.ToString()));
|
|
return;
|
|
}
|
|
networkTask.SetCompleted(task.Response as NSHttpUrlResponse);
|
|
}
|
|
}
|
|
|