148 lines
5.3 KiB
C#
148 lines
5.3 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Serialization;
|
||
using UnityEngine;
|
||
using UnityEngine.Networking;
|
||
using Vanguard.Api.Contracts;
|
||
using Vanguard.App;
|
||
using Vanguard.Auth;
|
||
|
||
namespace Vanguard.Api
|
||
{
|
||
/// <summary>
|
||
/// HTTP/JSON клиент мета-API. UnityWebRequest работает только на главном
|
||
/// потоке, поэтому все вызовы должны идти из Unity-кода; continuation после
|
||
/// await тоже возвращается на главный поток.
|
||
/// </summary>
|
||
public sealed class ApiClient
|
||
{
|
||
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
|
||
{
|
||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||
NullValueHandling = NullValueHandling.Ignore,
|
||
DateParseHandling = DateParseHandling.DateTimeOffset,
|
||
};
|
||
|
||
private readonly AppConfig _config;
|
||
private readonly TokenStore _tokens;
|
||
|
||
public ApiClient(AppConfig config, TokenStore tokens)
|
||
{
|
||
_config = config;
|
||
_tokens = tokens;
|
||
}
|
||
|
||
public string BaseUrl => _config.ServerBaseUrl;
|
||
|
||
public async Task<T> GetAsync<T>(string path, bool authenticated = true)
|
||
{
|
||
var body = await SendAsync("GET", path, null, authenticated);
|
||
if (string.IsNullOrEmpty(body))
|
||
return default;
|
||
|
||
return JsonConvert.DeserializeObject<T>(body, JsonSettings);
|
||
}
|
||
|
||
public async Task<T> PostAsync<T>(string path, object body, bool authenticated = true)
|
||
{
|
||
var response = await SendAsync("POST", path, body, authenticated);
|
||
if (string.IsNullOrEmpty(response))
|
||
return default;
|
||
|
||
return JsonConvert.DeserializeObject<T>(response, JsonSettings);
|
||
}
|
||
|
||
public Task PostAsync(string path, object body, bool authenticated = true) =>
|
||
SendAsync("POST", path, body, authenticated);
|
||
|
||
private async Task<string> SendAsync(
|
||
string method,
|
||
string path,
|
||
object body,
|
||
bool authenticated)
|
||
{
|
||
var url = _config.ServerBaseUrl + path;
|
||
|
||
using (var request = new UnityWebRequest(url, method))
|
||
{
|
||
request.downloadHandler = new DownloadHandlerBuffer();
|
||
request.timeout = Mathf.CeilToInt(_config.RequestTimeoutSeconds);
|
||
request.SetRequestHeader("Accept", "application/json");
|
||
|
||
if (body != null)
|
||
{
|
||
var json = JsonConvert.SerializeObject(body, JsonSettings);
|
||
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
|
||
request.SetRequestHeader("Content-Type", "application/json");
|
||
}
|
||
|
||
if (authenticated && !string.IsNullOrEmpty(_tokens.AccessToken))
|
||
request.SetRequestHeader("Authorization", "Bearer " + _tokens.AccessToken);
|
||
|
||
await SendRequestAsync(request);
|
||
|
||
var text = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
|
||
|
||
if (request.responseCode == 0)
|
||
{
|
||
var reason = string.IsNullOrEmpty(request.error)
|
||
? "Нет связи с сервером."
|
||
: request.error;
|
||
throw new ApiException(0, "network", reason);
|
||
}
|
||
|
||
if (request.responseCode >= 400)
|
||
throw ParseError(request.responseCode, text);
|
||
|
||
return text;
|
||
}
|
||
}
|
||
|
||
private static Task SendRequestAsync(UnityWebRequest request)
|
||
{
|
||
var completion = new TaskCompletionSource<bool>();
|
||
var operation = request.SendWebRequest();
|
||
operation.completed += _ => completion.TrySetResult(true);
|
||
return completion.Task;
|
||
}
|
||
|
||
private static ApiException ParseError(long statusCode, string body)
|
||
{
|
||
ApiErrorDto error = null;
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(body))
|
||
error = JsonConvert.DeserializeObject<ApiErrorDto>(body, JsonSettings);
|
||
}
|
||
catch (JsonException)
|
||
{
|
||
// Ниже отдадим общий текст: тело не JSON.
|
||
}
|
||
|
||
if (error == null)
|
||
{
|
||
return new ApiException(
|
||
statusCode,
|
||
"http." + statusCode,
|
||
"Сервер вернул ошибку " + statusCode + ".");
|
||
}
|
||
|
||
var violations = new List<ApiViolation>();
|
||
if (error.Violations != null)
|
||
{
|
||
foreach (var violation in error.Violations)
|
||
violations.Add(new ApiViolation(violation.Code, violation.Message));
|
||
}
|
||
|
||
var message = string.IsNullOrWhiteSpace(error.Message)
|
||
? "Сервер вернул ошибку " + statusCode + "."
|
||
: error.Message;
|
||
|
||
return new ApiException(statusCode, error.Error, message, violations);
|
||
}
|
||
}
|
||
}
|