VanguardReMaster/client/Assets/Vanguard/Runtime/Api/ApiClient.cs

148 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}