【ASP.NET CORE】5.HttpClientHelper请求封装类

代码

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
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容