代码
HttpClientHelper
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace HQ.Common.HttpHelper;
public class HttpClientHelper : IDisposable
{
private readonly HttpClient _client;
private readonly HttpClientOptions _options;
private readonly ILogger<HttpClientHelper> _logger;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public HttpClientHelper(
ILogger<HttpClientHelper> logger,
HttpClientOptions options = null)
{
_logger = logger;
_options = options ?? new HttpClientOptions();
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
MaxConnectionsPerServer = _options.MaxConnectionsPerServer,
ConnectTimeout = TimeSpan.FromSeconds(10)
};
_client = new HttpClient(handler)
{
Timeout = TimeSpan.FromMilliseconds(_options.Timeout)
};
if (!string.IsNullOrEmpty(_options.BaseUrl))
_client.BaseAddress = new Uri(_options.BaseUrl.TrimEnd('/') + '/');
_client.DefaultRequestHeaders.UserAgent.ParseAdd(_options.UserAgent);
_jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
}
public async Task<HttpResult> GetAsync(string url, Dictionary<string, string> headers = null, CancellationToken ct = default)
{
return await ExecuteAsync(HttpMethod.Get, url, null, headers, ct);
}
public async Task<T> GetJsonAsync<T>(string url, Dictionary<string, string> headers = null, CancellationToken ct = default)
{
var result = await GetAsync(url, headers, ct);
return result.As<T>(_jsonOptions);
}
public async Task<HttpResult> PostJsonAsync(string url, object data, Dictionary<string, string> headers = null, CancellationToken ct = default)
{
var json = JsonSerializer.Serialize(data, _jsonOptions);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
return await ExecuteAsync(HttpMethod.Post, url, content, headers, ct);
}
public async Task<HttpResult> PostFormAsync(string url, Dictionary<string, string> formData, Dictionary<string, string> headers = null, CancellationToken ct = default)
{
var content = new FormUrlEncodedContent(formData ?? new Dictionary<string, string>());
return await ExecuteAsync(HttpMethod.Post, url, content, headers, ct);
}
private async Task<HttpResult> ExecuteAsync(HttpMethod method, string url, HttpContent content, Dictionary<string, string> headers, CancellationToken ct)
{
var sw = Stopwatch.StartNew();
var fullUrl = BuildFullUrl(url);
_logger.LogInformation("HTTP请求开始 {Method} {Url}", method, fullUrl);
string requestBody = null;
if (content != null)
{
requestBody = await content.ReadAsStringAsync(ct);
if (requestBody.Length > 1000)
requestBody = requestBody[..1000] + "...";
_logger.LogInformation("HTTP请求入参 {Body}", requestBody);
}
HttpResponseMessage response = null;
try
{
using var request = CreateRequest(method, fullUrl, content, headers);
response = await _client.SendAsync(request, ct);
var result = await BuildResultAsync(response);
sw.Stop();
_logger.LogInformation(
"HTTP请求完成 {Method} {Url} {StatusCode} 耗时:{ElapsedMilliseconds}ms",
method, fullUrl, (int)response.StatusCode, sw.ElapsedMilliseconds);
return result;
}
catch (Exception ex)
{
sw.Stop();
_logger.LogError(ex, "HTTP请求失败 {Method} {Url} 耗时:{ElapsedMilliseconds}ms",
method, fullUrl, sw.ElapsedMilliseconds);
throw;
}
finally
{
response?.Dispose();
}
}
private string BuildFullUrl(string url)
{
if (Uri.TryCreate(url, UriKind.Absolute, out _))
return url;
if (_client.BaseAddress != null)
return new Uri(_client.BaseAddress, url.TrimStart('/')).ToString();
throw new ArgumentException("未配置BaseUrl时,url必须是完整地址");
}
private HttpRequestMessage CreateRequest(HttpMethod method, string url, HttpContent content, Dictionary<string, string> headers)
{
var request = new HttpRequestMessage(method, url);
foreach (var h in _client.DefaultRequestHeaders)
request.Headers.TryAddWithoutValidation(h.Key, h.Value);
if (headers != null)
foreach (var h in headers)
request.Headers.TryAddWithoutValidation(h.Key, h.Value);
if (content != null)
request.Content = content;
return request;
}
private async Task<HttpResult> BuildResultAsync(HttpResponseMessage response)
{
var bytes = await response.Content.ReadAsByteArrayAsync();
Encoding encoding;
try
{
encoding = response.Content.Headers.ContentType?.CharSet != null
? Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet)
: Encoding.UTF8;
}
catch
{
encoding = Encoding.UTF8;
}
return new HttpResult
{
StatusCode = response.StatusCode,
StatusDescription = response.ReasonPhrase,
Content = encoding.GetString(bytes),
ContentBytes = bytes,
Headers = response.Headers.ToDictionary(h => h.Key, h => string.Join(", ", h.Value))
};
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) _client?.Dispose();
_disposed = true;
}
}
HttpClientOptions
namespace HQ.Common.HttpHelper;
public class HttpClientOptions
{
public string BaseUrl { get; set; }
public int Timeout { get; set; } = 30000;
public int MaxConnectionsPerServer { get; set; } = 100;
public string UserAgent { get; set; } = "HQ-HttpClient/1.0";
}
HttpResult
using System.Net;
using System.Text.Json;
namespace HQ.Common.HttpHelper;
public class HttpResult
{
public bool IsSuccess => (int)StatusCode is >= 200 and < 300;
public HttpStatusCode StatusCode { get; set; }
public string StatusDescription { get; set; }
public string Content { get; set; }
public byte[] ContentBytes { get; set; }
public Dictionary<string, string> Headers { get; set; } = new();
public string ErrorMessage { get; set; }
public Exception Exception { get; set; }
public long ElapsedMilliseconds { get; set; }
public T As<T>(JsonSerializerOptions options = null)
{
if (string.IsNullOrEmpty(Content)) return default;
return JsonSerializer.Deserialize<T>(Content, options ?? new() { PropertyNameCaseInsensitive = true });
}
}
使用
// 1. 不传BaseUrl,用完整URL
var http = new HttpClientHelper();
var result = await http.GetAsync("https://api.example.com/users");
// 2. 传BaseUrl,用相对路径
var http2 = new HttpClientHelper(new HttpClientOptions
{
BaseUrl = "https://api.example.com"
});
var result2 = await http2.GetAsync("/users");
// 3. DI注册
builder.Services.AddSingleton(sp => new HttpClientHelper(
new HttpClientOptions { BaseUrl = builder.Configuration["ApiSettings:BaseUrl"] },
sp.GetRequiredService<ILogger<HttpClientHelper>>()
));
GET
// 1. 基础 GET
var http = new HttpClientHelper();
var result = await http.GetAsync("https://api.example.com/users");
Console.WriteLine(result.Content);
// 2. GET 返回对象
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
var user = await http.GetJsonAsync<User>("https://api.example.com/users/1");
Console.WriteLine(user.Name);
// 3. GET 带请求头
var headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer token123",
["X-Request-Id"] = Guid.NewGuid().ToString()
};
var result = await http.GetAsync("/api/users", headers);
POST
// 1. POST JSON
var result = await http.PostJsonAsync(
"https://api.example.com/users",
new { Name = "张三", Age = 25 }
);
// 2. POST JSON 返回对象
var newUser = await http.PostJsonAsync<object, User>(
"/api/users",
new { Name = "张三", Age = 25 }
);
// 3. POST Form 表单
var form = new Dictionary<string, string>
{
["username"] = "admin",
["password"] = "123456"
};
var result = await http.PostFormAsync("/api/login", form);
// 4. POST 带请求头
var headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer token123"
};
var result = await http.PostJsonAsync("/api/orders", new { Amount = 100 }, headers);
指定超时时间
// 单次 10 秒超时
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var result = await http.PostJsonAsync("/api/users", new { Name = "张三" }, null, cts.Token);
// 或 500 毫秒(快速失败)
using var cts = new CancellationTokenSource(500);
var result = await http.PostFormAsync("/api/login", formData, null, cts.Token);
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END









暂无评论内容