Files
XRLib/Assets/Scripts/UVC/Network/HttpRequester.cs
2025-11-11 12:13:15 +09:00

674 lines
32 KiB
C#

using Best.HTTP;
using Best.HTTP.Response;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UVC.Json;
using UVC.Log;
namespace UVC.Network
{
/// <summary>
/// HTTP 요청을 처리하기 위한 유틸리티 클래스
/// </summary>
/// <remarks>
/// 이 클래스는 REST API 호출을 위한 메소드들을 제공합니다.
/// GET, POST 요청 및 파일 다운로드를 지원하며 요청/응답 로깅 기능도 포함합니다.
/// 요청은 비동기(UniTask)로 처리됩니다.
///
/// 기본 사용법:
/// <code>
/// // 도메인 설정
/// HttpRequester.Domain = "https://api.example.com";
///
/// // GET 요청 예제
/// var products = await HttpRequester.RequestGet<List<Product>>("/api/products");
///
/// // POST 요청 예제
/// var loginData = new Dictionary<string, object>
/// {
/// { "username", "user@example.com" },
/// { "password", "password123" }
/// };
/// var response = await HttpRequester.RequestPost<LoginResponse>("/api/login", loginData);
/// </code>
/// </remarks>
public class HttpRequester
{
/// <summary>
/// 기본 도메인 주소입니다.
/// </summary>
public static string Domain { get; set; } = "";
/// <summary>
/// Dictionary 형태의 본문을 가진 사용자 정의 HTTP 메소드로 API 요청을 수행합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="method">HTTP 메소드 (get, post, put 등)</param>
/// <param name="body">요청 본문으로 전송할 Dictionary 데이터 (null 가능)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
/// <example>
/// <code>
/// // PUT 요청 예제
/// public async UniTask<ProductResponse> UpdateProductAsync(int productId, string name, float price)
/// {
/// var body = new Dictionary<string, object>
/// {
/// { "Id", productId },
/// { "name", name },
/// { "price", price }
/// };
///
/// return await HttpRequester.Request<ProductResponse>("/api/products", "put", body);
/// }
/// </code>
/// </example>
public static async UniTask<T> Request<T>(string url, string method, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = false)
{
return await Request_<T>(url, body == null ? null : JsonHelper.ToJson(body), method, header, useAuth);
}
/// <summary>
/// 문자열 형태의 본문을 가진 사용자 정의 HTTP 메소드로 API 요청을 수행합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="method">HTTP 메소드 (get, post, put 등)</param>
/// <param name="body">요청 본문으로 전송할 JSON 문자열 (null 가능)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
/// <example>
/// <code>
/// // DELETE 요청 예제
/// public async UniTask<bool> DeleteResourceAsync(string resourceId)
/// {
/// string jsonBody = $"{{\"Id\": \"{resourceId}\"}}";
///
/// return await HttpRequester.Request<bool>("/api/resources", "delete", jsonBody);
/// }
/// </code>
/// </example>
public static async UniTask<T> Request<T>(string url, string method, string body = null, Dictionary<string, string> header = null, bool useAuth = false)
{
return await Request_<T>(url, body, method, header, useAuth);
}
/// <summary>
/// Dictionary를 JSON body로 변환하여 POST 요청을 수행합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청 본문으로 전송할 Dictionary 데이터 (null 가능)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
/// <example>
/// <code>
/// // 사용자 로그인 요청 예제
/// public async UniTask<LoginResponse> LoginAsync(string username, string password)
/// {
/// var body = new Dictionary<string, object>
/// {
/// { "username", username },
/// { "password", password }
/// };
///
/// return await HttpRequester.RequestPost<LoginResponse>("/api/login", body);
/// }
///
/// // LoginResponse 클래스 예제
/// [Serializable]
/// public class LoginResponse
/// {
/// public string token;
/// public UserInfo userInfo;
/// }
/// </code>
/// </example>
public static async UniTask<T> RequestPost<T>(string url, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = false)
{
return await Request_<T>(url, body == null ? null : JsonHelper.ToJson(body), "post", header, useAuth);
}
/// <summary>
/// 문자열 형태의 JSON을 body로 사용하여 POST 요청을 수행합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청 본문으로 전송할 JSON 문자열</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
/// <example>
/// <code>
/// // 사용자 정의 JSON 문자열로 요청하는 예제
/// public async UniTask<ProductResponse> UpdateProductAsync(int productId, string productName)
/// {
/// string jsonBody = $"{{\"Id\": {productId}, \"name\": \"{productName}\"}}";
///
/// return await HttpRequester.RequestPost<ProductResponse>("/api/products/update", jsonBody);
/// }
/// </code>
/// </example>
public static async UniTask<T> RequestPost<T>(string url, string body, Dictionary<string, string> header = null, bool useAuth = false)
{
return await Request_<T>(url, body, "post", header, useAuth);
}
/// <summary>
/// GET 방식으로 API를 호출합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청에 포함할 파라미터 (URL 쿼리스트링으로 변환)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
/// <example>
/// <code>
/// // 상품 목록을 가져오는 예제
/// public async UniTask<List<Product>> GetProductsAsync(int page = 1, int pageSize = 20)
/// {
/// var queryParams = new Dictionary<string, object>
/// {
/// { "page", page },
/// { "pageSize", pageSize }
/// };
///
/// return await HttpRequester.RequestGet<List<Product>>("/api/products", queryParams);
/// }
///
/// // Product 클래스 예제
/// [Serializable]
/// public class Product
/// {
/// public int Id;
/// public string name;
/// public float price;
/// }
/// </code>
/// </example>
public static async UniTask<T> RequestGet<T>(string url, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = false)
{
return await Request_<T>(url, body == null ? null : JsonHelper.ToJson(body), "get", header, useAuth);
}
/// <summary>
/// HTTP 요청을 처리하는 내부 메소드
/// </summary>
/// <remarks>
/// 이 메소드는 모든 HTTP 요청의 공통 로직을 처리합니다:
/// - URL 구성 (Domain과 상대 경로 결합)
/// - HTTP 메소드 설정
/// - 헤더 설정
/// - 요청 로깅
/// - 응답 처리 및 역직렬화
/// </remarks>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL</param>
/// <param name="body">요청 본문 (JSON 문자열)</param>
/// <param name="methodString">HTTP 메소드 문자열</param>
/// <param name="header">추가할 헤더 정보</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
private static async UniTask<T> Request_<T>(string url, string body = null, string methodString = "post", Dictionary<string, string> header = null, bool useAuth = false)
{
HTTPMethods method = StringToMethod(methodString);
if (!url.Contains("http")) url = $"{Domain}{url}";
var request = SelectHTTPRequest(method, url);
request.DownloadSettings = new Best.HTTP.Request.Settings.DownloadSettings() { ContentStreamMaxBuffered = 1024 * 1024 * 200 };
request.MethodType = method;
request.SetHeader("Content-Type", "application/json; charset=utf-8");
//if (useAuth) request.SetHeader("access-token", AuthService.Instance.Entiti.accessToken);
JObject headerObject = new JObject();
headerObject.Add("Content-Type", "application/json; charset=utf-8");
if (header != null)
{
foreach (var kvp in header)
{
request.SetHeader(kvp.Key, kvp.Value);
headerObject.Add(kvp.Key, kvp.Value);
}
}
if (body != null)
{
request.UploadSettings.UploadStream = new MemoryStream(Encoding.UTF8.GetBytes(body));
}
#if !UNITY_WEBGL || UNITY_EDITOR
HttpLogEntry log = ServerLog.LogHttpRequest(url, methodString, headerObject.ToString(Formatting.None), body, DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
//Debug.Log($"Request APIToken :{AuthService.Instance.Entiti.accessToken}");
bool isMainThread = PlayerLoopHelper.IsMainThread;
#endif
//var response = await request.GetFromJsonResultAsync<T>();
var response = await request.GetAsStringAsync();
#if !UNITY_WEBGL || UNITY_EDITOR
if (!isMainThread) await UniTask.SwitchToThreadPool();
log.ResponseData = response;
log.ResponseDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
ServerLog.LogHttpResponse(log);
#endif
//T가 string이면
if (typeof(T) == typeof(string))
{
return (T)(object)response;
}
else
{
return JsonHelper.FromJson<T>(response);
}
}
/// <summary>
/// Dictionary를 JSON body로 변환하여 POST 요청을 수행하고 처리 시간도 함께 반환합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청 본문으로 전송할 Dictionary 데이터 (null 가능)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터와 요청 처리 시간</returns>
/// <example>
/// <code>
/// // 서버 응답 시간을 측정하는 예제
/// public async UniTask<(UserData, TimeSpan)> GetUserDataWithPerformanceCheck(int userId)
/// {
/// var body = new Dictionary<string, object>
/// {
/// { "userId", userId }
/// };
///
/// var (userData, duration) = await HttpRequester.RequestPostWithDuration<UserData>("/api/users/details", body);
///
/// Debug.Log($"API 응답 시간: {duration.TotalMilliseconds}ms");
/// return (userData, duration);
/// }
/// </code>
/// </example>
public static async UniTask<(T, TimeSpan)> RequestPostWithDuration<T>(string url, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = true)
{
return await RequestWithDuration<T>(url, body == null ? null : JsonHelper.ToJson(body), "post", header, useAuth);
}
/// <summary>
/// 문자열 형태의 JSON을 body로 사용하여 POST 요청을 수행하고 처리 시간도 함께 반환합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청 본문으로 전송할 JSON 문자열</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터와 요청 처리 시간</returns>
/// <example>
/// <code>
/// // 커스텀 JSON으로 요청하고 응답 시간을 측정하는 예제
/// public async UniTask<(APIResponse, TimeSpan)> SendComplexDataWithTiming(string jsonData)
/// {
/// // jsonData는 이미 직렬화된 JSON 문자열
///
/// var (response, duration) = await HttpRequester.RequestPostWithDuration<APIResponse>("/api/complex-operation", jsonData);
///
/// if (duration.TotalSeconds > 1.0)
/// {
/// Debug.LogWarning($"API 응답이 느립니다: {duration.TotalSeconds}초");
/// }
///
/// return (response, duration);
/// }
/// </code>
/// </example>
public static async UniTask<(T, TimeSpan)> RequestPostWithDuration<T>(string url, string body, Dictionary<string, string> header = null, bool useAuth = true)
{
return await RequestWithDuration<T>(url, body, "post", header, useAuth);
}
/// <summary>
/// GET 방식으로 API를 호출하고 처리 시간도 함께 반환합니다.
/// </summary>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL (http가 포함되지 않은 경우 Domain과 결합)</param>
/// <param name="body">요청에 포함할 파라미터 (URL 쿼리스트링으로 변환)</param>
/// <param name="header">추가할 헤더 정보 (null 가능)</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터와 요청 처리 시간</returns>
/// <example>
/// <code>
/// // 서버 상태 확인 및 응답 시간 측정 예제
/// public async UniTask<(ServerStatus, TimeSpan)> CheckServerStatus()
/// {
/// var (status, duration) = await HttpRequester.RequestGetWithDuration<ServerStatus>("/api/server/status");
///
/// // 응답 시간에 따른 서버 상태 평가
/// if (duration.TotalMilliseconds < 100)
/// {
/// Debug.Log("서버 응답 상태: 매우 좋음");
/// }
/// else if (duration.TotalMilliseconds < 500)
/// {
/// Debug.Log("서버 응답 상태: 양호");
/// }
/// else
/// {
/// Debug.Log("서버 응답 상태: 지연됨");
/// }
///
/// return (status, duration);
/// }
/// </code>
/// </example>
public static async UniTask<(T, TimeSpan)> RequestGetWithDuration<T>(string url, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = true)
{
return await RequestWithDuration<T>(url, body == null ? null : JsonHelper.ToJson(body), "get", header, useAuth);
}
/// <summary>
/// HTTP 요청을 처리하고 처리 시간을 측정하는 내부 메소드
/// </summary>
/// <remarks>
/// 이 메소드는 모든 성능 측정이 필요한 HTTP 요청의 공통 로직을 처리합니다:
/// - 요청 처리 시간 측정
/// - URL 구성
/// - HTTP 메소드 설정
/// - 헤더 설정
/// - 요청 로깅
/// - 응답 처리 및 역직렬화
/// </remarks>
/// <typeparam name="T">응답 데이터를 변환할 타입</typeparam>
/// <param name="url">요청할 URL</param>
/// <param name="body">요청 본문 (JSON 문자열)</param>
/// <param name="methodString">HTTP 메소드 문자열</param>
/// <param name="header">추가할 헤더 정보</param>
/// <param name="useAuth">인증 토큰 사용 여부</param>
/// <returns>지정된 타입으로 변환된 응답 데이터와 요청 처리 시간의 튜플</returns>
private static async UniTask<(T, TimeSpan)> RequestWithDuration<T>(string url, string body = null, string methodString = "", Dictionary<string, string> header = null, bool useAuth = true)
{
HTTPMethods method = StringToMethod(methodString);
if (!url.Contains("http")) url = $"{Domain}{url}";
var request = SelectHTTPRequest(method, url);
request.DownloadSettings = new Best.HTTP.Request.Settings.DownloadSettings() { ContentStreamMaxBuffered = 1024 * 1024 * 200 };
request.MethodType = method;
request.SetHeader("Content-Type", "application/json; charset=utf-8");
JObject headerObject = new JObject();
headerObject.Add("Content-Type", "application/json; charset=utf-8");
if (header != null)
{
foreach (var kvp in header)
{
request.SetHeader(kvp.Key, kvp.Value);
headerObject.Add(kvp.Key, kvp.Value);
}
}
//ULog.Debug($"RequestWithDuration APIToken :{AuthService.Instance.Entiti.accessToken}");
//if (useAuth) request.SetHeader("access-token", AuthService.Instance.Entiti.accessToken);
if (body != null)
{
request.UploadSettings.UploadStream =
new MemoryStream(Encoding.UTF8.GetBytes(body));
}
HttpLogEntry log = ServerLog.LogHttpRequest(url, methodString, headerObject.ToString(Formatting.None), body, DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
//var response = await request.GetFromJsonResultAsync<T>();
var now = DateTime.UtcNow;
bool isMainThread = PlayerLoopHelper.IsMainThread;
var response = await request.GetAsStringAsync();
#if !UNITY_WEBGL || UNITY_EDITOR
if (!isMainThread) await UniTask.SwitchToThreadPool();
#endif
var diff = DateTime.UtcNow - now;
log.ResponseData = response;
log.ResponseDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
ServerLog.LogHttpResponse(log);
if (typeof(T) == typeof(string))
{
return ((T)(object)response, diff);
}
else
{
return (JsonHelper.FromJson<T>(response), diff);
}
}
/// <summary>
/// 원격 서버에서 파일을 다운로드합니다.
/// </summary>
/// <param name="url">다운로드할 파일의 URL</param>
/// <param name="savePath">파일을 저장할 로컬 경로</param>
/// <param name="OnComplete">다운로드 완료시 호출될 콜백</param>
/// <param name="OnProgress">다운로드 진행 상태 업데이트 콜백 (progress: 다운로드된 바이트 수, length: 전체 파일 크기)</param>
/// <param name="OnError">오류 발생시 호출될 콜백</param>
/// <returns>발행된 HTTP 요청 객체 (파일이 이미 존재하는 경우 null)</returns>
/// <remarks>
/// 지정된 경로에 파일이 이미 존재하는 경우 다운로드를 수행하지 않고 바로 OnComplete 콜백을 호출합니다.
/// </remarks>
/// <example>
/// <code>
/// // 이미지 파일 다운로드 예제
/// public void DownloadImage(string imageUrl, string fileName)
/// {
/// string savePath = Path.Combine(Application.persistentDataPath, fileName);
///
/// HttpRequester.Download(
/// imageUrl,
/// savePath,
/// () => {
/// Debug.Log($"이미지가 성공적으로 다운로드되었습니다: {savePath}");
/// // 다운로드된 이미지 사용 코드...
/// },
/// (progress, length) => {
/// float percentage = (float)progress / length * 100f;
/// Debug.Log($"다운로드 진행률: {percentage:F1}%");
/// },
/// (error) => {
/// Debug.LogError($"다운로드 실패: {error}");
/// }
/// );
/// }
/// </code>
/// </example>
public static HTTPRequest Download(string url, string savePath, Action OnComplete, Action<long, long> OnProgress = null, Action<string> OnError = null)
{
ULog.Debug($"Download {url}");
if (File.Exists(savePath))
{
OnComplete.Invoke();
return null;
}
OnRequestFinishedDelegate onRequest = (HTTPRequest req, HTTPResponse resp) =>
{
req.DownloadSettings.OnDownloadStarted = null;
req.DownloadSettings.OnDownloadProgress = null;
req.DownloadSettings.DownloadStreamFactory = null;
switch (req.State)
{
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 백그라운드 쓰레드 불가 -> 완료 시 메모리 데이터로 파일 저장
try
{
System.IO.File.WriteAllBytes(savePath, resp.Data);
OnComplete?.Invoke();
}
catch (Exception ex)
{
ULog.Error($"Failed to write file on WebGL: {ex.Message}", ex);
OnError?.Invoke($"Failed to write file: {ex.Message}");
}
#else
//스트리밍 파일 기록은 OnDownloadStarted에서 처리됨
OnComplete.Invoke();
#endif
}
else
{
ULog.Warning($"Server error({resp.StatusCode} - {resp.Message})! ");
OnError?.Invoke($"Server error({resp.StatusCode} - {resp.Message})!");
}
break;
default:
string detailedError = req.Exception != null ? req.Exception.ToString() : (resp != null ? resp.Message : "Unknown error");
string errorMsgDefault = $"Request failed! State: {req.State}, URL: {req.CurrentUri}, Error: {detailedError}";
ULog.Error(errorMsgDefault, req.Exception);
//ULog.Error(req.State.ToString(), req.Exception != null ? req.Exception: new Exception(resp?.Message));
OnError?.Invoke(errorMsgDefault);
break;
}
};
var request = SelectHTTPRequest(HTTPMethods.Get, url, onRequest);
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 쓰레드 기반 스트리밍 미사용. 진행률 콜백만 연결.
request.DownloadSettings.OnDownloadProgress += (HTTPRequest req, long progress, long length) =>
{
ULog.Debug($"Download Progress! progress:{progress} length:{length}");
OnProgress?.Invoke(progress, length);
};
// DownloadStreamFactory/BlockingDownloadContentStream 사용 금지
#else
request.DownloadSettings.OnDownloadStarted += async (HTTPRequest req, HTTPResponse resp, DownloadContentStream stream) =>
{
ULog.Debug("Download Started");
long len = await UniTask.RunOnThreadPool<long>(() => ConsumeDownloadStream(savePath, stream as BlockingDownloadContentStream));
if (len == 0)
OnError?.Invoke($"Download Fail!");
ULog.Debug($"Download finished!");
};
request.DownloadSettings.OnDownloadProgress += (HTTPRequest req, long progress, long length) =>
{
ULog.Debug($"Download Progress! progress:{progress} length:{length}");
OnProgress?.Invoke(progress, length);
};
request.DownloadSettings.DownloadStreamFactory = (req, resp, bufferAvailableHandler)
=> new BlockingDownloadContentStream(resp, req.DownloadSettings.ContentStreamMaxBuffered, bufferAvailableHandler);
#endif
return request.Send();
}
/// <summary>
/// 다운로드 스트림을 처리하여 파일로 저장합니다.
/// </summary>
/// <remarks>
/// 이 메소드는 스트림 데이터를 지정된 파일로 저장하며, 다운로드가 완료되거나 중단될 때까지 블로킹 방식으로 데이터를 처리합니다.
/// 다운로드에 실패한 경우 (길이가 0인 경우) 빈 파일은 삭제됩니다.
/// </remarks>
/// <param name="savePath">다운로드한 데이터를 저장할 파일 경로</param>
/// <param name="blockingStream">다운로드 콘텐츠 스트림</param>
/// <returns>저장된 파일의 크기(바이트)</returns>
/// <example>
/// <code>
/// // 내부적으로 Download 메소드에서 사용됩니다
/// long fileSize = await UniTask.RunOnThreadPool<long>(() =>
/// ConsumeDownloadStream(savePath, downloadStream as BlockingDownloadContentStream));
/// </code>
/// </example>
private static long ConsumeDownloadStream(string savePath, BlockingDownloadContentStream blockingStream)
{
long len = 0;
using (FileStream fs = new FileStream(savePath, System.IO.FileMode.Create))
{
try
{
//ULog.Debug($"ConsumeDownloadStream blockingStream:{blockingStream == null}");
while (blockingStream != null && !blockingStream.IsCompleted)
{
if (blockingStream.TryTake(out var buffer))
{
try
{
//ULog.Debug($"ConsumeDownloadStream buffer.Data:{buffer.Data.Length}, Offset:{buffer.Offset}, Count:{buffer.Count}");
fs.Write(buffer.Data, buffer.Offset, buffer.Count);
}
finally
{
BufferPool.Release(buffer);
}
}
}
}
finally
{
if (blockingStream != null) blockingStream.Dispose();
len = fs.Length;
}
}
if (len == 0) File.Delete(savePath);
return len;
}
/// <summary>
/// HTTP 메소드와 URL을 기반으로 적절한 HTTPRequest 객체를 생성합니다.
/// </summary>
/// <param name="methods">요청에 사용할 HTTP 메소드</param>
/// <param name="url">요청할 URL</param>
/// <param name="onRequest">요청 완료 시 호출될 콜백 (선택 사항)</param>
/// <returns>생성된 HTTPRequest 객체</returns>
/// <example>
/// <code>
/// // 내부 사용 예제 (직접 사용하지 않음)
/// HTTPRequest request = SelectHTTPRequest(HTTPMethods.Get, "https://api.example.com/data");
/// request.SetHeader("Custom-Header", "HeaderValue");
/// request.Send();
/// </code>
/// </example>
private static HTTPRequest SelectHTTPRequest(HTTPMethods methods, string url, OnRequestFinishedDelegate onRequest = null)
{
switch (methods)
{
case HTTPMethods.Get:
if (onRequest != null)
return HTTPRequest.CreateGet(url, onRequest);
else
return HTTPRequest.CreateGet(url);
case HTTPMethods.Post:
return HTTPRequest.CreatePost(url);
}
return null;
}
/// <summary>
/// 문자열 형태의 HTTP 메소드를 Best.HTTP.HTTPMethods 열거형으로 변환합니다.
/// </summary>
/// <param name="method">변환할 HTTP 메소드 문자열 ("get", "post" 등)</param>
/// <returns>해당하는 HTTPMethods 열거형 값</returns>
/// <exception cref="ArgumentException">지원하지 않는 HTTP 메소드인 경우 발생</exception>
private static HTTPMethods StringToMethod(string method)
{
return method.ToLower() switch
{
"get" => HTTPMethods.Get,
"post" => HTTPMethods.Post,
_ => throw new ArgumentException($"Unsupported HTTP method: {method}"),
};
}
}
}