165 lines
6.5 KiB
C#
165 lines
6.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Net;
|
||
using System.Net.Http;
|
||
using System.Net.Http.Headers;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Web;
|
||
|
||
namespace AudioWallpaper.Tools {
|
||
public class HttpHelper {
|
||
// 使用静态HttpClient实例(推荐)
|
||
private static readonly HttpClient _sharedClient = new HttpClient(
|
||
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }
|
||
);
|
||
|
||
private readonly HttpClient _httpClient;
|
||
private TimeSpan _timeout = TimeSpan.FromSeconds(100);
|
||
|
||
/// <summary>
|
||
/// 使用共享的静态HttpClient实例
|
||
/// </summary>
|
||
public HttpHelper() => _httpClient = _sharedClient;
|
||
|
||
/// <summary>
|
||
/// 使用自定义HttpClient实例
|
||
/// </summary>
|
||
public HttpHelper(HttpClient httpClient) => _httpClient = httpClient;
|
||
|
||
/// <summary>
|
||
/// 设置全局请求超时(默认100秒)
|
||
/// </summary>
|
||
public TimeSpan Timeout {
|
||
get => _timeout;
|
||
set {
|
||
_timeout = value;
|
||
_httpClient.Timeout = _timeout;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步发送GET请求
|
||
/// </summary>
|
||
/// <param name="url">请求地址</param>
|
||
/// <param name="queryParams">查询参数</param>
|
||
/// <param name="headers">请求头</param>
|
||
/// <param name="cancellationToken">取消令牌</param>
|
||
/// <returns>响应字符串</returns>
|
||
public async Task<string> GetAsync(
|
||
string url,
|
||
Dictionary<string, string>? queryParams = null,
|
||
Dictionary<string, string>? headers = null,
|
||
CancellationToken cancellationToken = default) {
|
||
var requestUrl = BuildUrlWithQuery(url, queryParams);
|
||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
|
||
AddHeaders(request, headers);
|
||
|
||
using var response = await SendWithTimeoutAsync(request, cancellationToken);
|
||
return await HandleResponse(response);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步发送POST请求
|
||
/// </summary>
|
||
/// <param name="url">请求地址</param>
|
||
/// <param name="content">请求内容</param>
|
||
/// <param name="contentType">内容类型(默认为application/json)</param>
|
||
/// <param name="headers">请求头</param>
|
||
/// <param name="cancellationToken">取消令牌</param>
|
||
/// <returns>响应字符串</returns>
|
||
public async Task<string> PostAsync(
|
||
string url,
|
||
object content,
|
||
string contentType = "application/json",
|
||
Dictionary<string, string>? headers = null,
|
||
CancellationToken cancellationToken = default) {
|
||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||
AddHeaders(request, headers);
|
||
request.Content = CreateHttpContent(content, contentType);
|
||
|
||
using var response = await SendWithTimeoutAsync(request, cancellationToken);
|
||
return await HandleResponse(response);
|
||
}
|
||
|
||
private async Task<HttpResponseMessage> SendWithTimeoutAsync(
|
||
HttpRequestMessage request,
|
||
CancellationToken cancellationToken) {
|
||
try {
|
||
// 使用CancellationTokenSource实现超时控制
|
||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||
cts.CancelAfter(_timeout);
|
||
|
||
return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||
} catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested) {
|
||
throw new TimeoutException($"请求超时 ({_timeout.TotalSeconds}秒)");
|
||
}
|
||
}
|
||
|
||
private static string BuildUrlWithQuery(string url, Dictionary<string, string>? queryParams) {
|
||
if (queryParams == null || queryParams.Count == 0) return url;
|
||
|
||
var uriBuilder = new UriBuilder(url);
|
||
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||
|
||
foreach (var param in queryParams) {
|
||
query[param.Key] = param.Value;
|
||
}
|
||
|
||
uriBuilder.Query = query.ToString();
|
||
return uriBuilder.ToString();
|
||
}
|
||
|
||
private static void AddHeaders(HttpRequestMessage request, Dictionary<string, string>? headers) {
|
||
if (headers == null) return;
|
||
|
||
foreach (var header in headers) {
|
||
if (header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) {
|
||
if (request.Content != null) {
|
||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(header.Value);
|
||
}
|
||
} else {
|
||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static HttpContent CreateHttpContent(object content, string contentType) {
|
||
return contentType.ToLower() switch {
|
||
"application/json" => new StringContent(
|
||
JsonSerializer.Serialize(content),
|
||
Encoding.UTF8,
|
||
"application/json"),
|
||
|
||
"application/x-www-form-urlencoded" => new FormUrlEncodedContent(
|
||
content as IEnumerable<KeyValuePair<string, string>>
|
||
?? ConvertToFormData(content)),
|
||
|
||
_ => throw new NotSupportedException($"不支持的Content-Type: {contentType}")
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<KeyValuePair<string, string>> ConvertToFormData(object data) {
|
||
var formData = new List<KeyValuePair<string, string>>();
|
||
var properties = data.GetType().GetProperties();
|
||
|
||
foreach (var prop in properties) {
|
||
var value = prop.GetValue(data)?.ToString() ?? "";
|
||
formData.Add(new KeyValuePair<string, string>(prop.Name, value));
|
||
}
|
||
|
||
return formData;
|
||
}
|
||
|
||
private static async Task<string> HandleResponse(HttpResponseMessage response) {
|
||
response.EnsureSuccessStatusCode();
|
||
|
||
using var stream = await response.Content.ReadAsStreamAsync();
|
||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||
return await reader.ReadToEndAsync();
|
||
}
|
||
}
|
||
} |