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
{
///
/// HTTP/JSON клиент мета-API. UnityWebRequest работает только на главном
/// потоке, поэтому все вызовы должны идти из Unity-кода; continuation после
/// await тоже возвращается на главный поток.
///
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 GetAsync(string path, bool authenticated = true)
{
var body = await SendAsync("GET", path, null, authenticated);
if (string.IsNullOrEmpty(body))
return default;
return JsonConvert.DeserializeObject(body, JsonSettings);
}
public async Task PostAsync(string path, object body, bool authenticated = true)
{
var response = await SendAsync("POST", path, body, authenticated);
if (string.IsNullOrEmpty(response))
return default;
return JsonConvert.DeserializeObject(response, JsonSettings);
}
public Task PostAsync(string path, object body, bool authenticated = true) =>
SendAsync("POST", path, body, authenticated);
private async Task 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();
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(body, JsonSettings);
}
catch (JsonException)
{
// Ниже отдадим общий текст: тело не JSON.
}
if (error == null)
{
return new ApiException(
statusCode,
"http." + statusCode,
"Сервер вернул ошибку " + statusCode + ".");
}
var violations = new List();
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);
}
}
}