C# HTTP请求处理:从基础到高级实践


引言

在现代应用开发中,HTTP请求处理是C#开发者必须掌握的核心技能之一。无论是构建Web API、调用第三方服务,还是开发微服务架构应用,高效的HTTP请求处理都至关重要。本文将全面介绍C#中处理HTTP请求的各种方法和技术,从基础的HttpWebRequest到现代的HttpClient,以及相关的最佳实践。

一、HTTP请求处理基础

1.1 HTTP请求核心组件

C#提供了多个处理HTTP请求的类:

  • HttpWebRequest/HttpWebResponse:传统的HTTP处理类
  • WebClient:简化版的HTTP客户端
  • HttpClient:现代HTTP客户端(推荐使用)
  • HttpClientFactory:.NET Core引入的HTTP客户端工厂

1.2 常用HTTP方法

  • GET:获取资源
  • POST:创建资源
  • PUT:更新资源
  • DELETE:删除资源
  • PATCH:部分更新资源

二、传统HTTP请求处理方式

2.1 使用HttpWebRequest

public static string GetWithHttpWebRequest(string url)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "GET";

    try
    {
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        using (Stream stream = response.GetResponseStream())
        using (StreamReader reader = new StreamReader(stream))
        {
            return reader.ReadToEnd();
        }
    }
    catch (WebException ex)
    {
        using (var stream = ex.Response?.GetResponseStream())
        using (var reader = new StreamReader(stream ?? Stream.Null))
        {
            string errorResponse = reader.ReadToEnd();
            throw new HttpRequestException($"请求失败: {ex.Status} - {errorResponse}", ex);
        }
    }
}

public static string PostWithHttpWebRequest(string url, string jsonData)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/json";

    byte[] dataBytes = Encoding.UTF8.GetBytes(jsonData);
    request.ContentLength = dataBytes.Length;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(dataBytes, 0, dataBytes.Length);
    }

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
    {
        return reader.ReadToEnd();
    }
}

2.2 使用WebClient

public static string GetWithWebClient(string url)
{
    using (WebClient client = new WebClient())
    {
        client.Encoding = Encoding.UTF8;
        return client.DownloadString(url);
    }
}

public static string PostWithWebClient(string url, string jsonData)
{
    using (WebClient client = new WebClient())
    {
        client.Headers[HttpRequestHeader.ContentType] = "application/json";
        byte[] responseBytes = client.UploadData(url, "POST", Encoding.UTF8.GetBytes(jsonData));
        return Encoding.UTF8.GetString(responseBytes);
    }
}

三、现代HTTP请求处理(推荐)

3.1 HttpClient基础使用

public static async Task<string> GetWithHttpClientAsync(string url)
{
    using (HttpClient client = new HttpClient())
    {
        try
        {
            HttpResponseMessage response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode(); // 确保状态码为2xx
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"HTTP请求错误: {ex.Message}");
            throw;
        }
    }
}

public static async Task<string> PostWithHttpClientAsync(string url, string jsonData)
{
    using (HttpClient client = new HttpClient())
    {
        var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

        HttpResponseMessage response = await client.PostAsync(url, content);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

3.2 高级HttpClient配置

public static HttpClient CreateConfiguredHttpClient()
{
    var handler = new HttpClientHandler
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
        UseCookies = false,
        AllowAutoRedirect = false,
        // 其他配置...
    };

    var client = new HttpClient(handler)
    {
        Timeout = TimeSpan.FromSeconds(30),
        BaseAddress = new Uri("https://api.example.com")
    };

    // 设置默认请求头
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));

    return client;
}

四、HTTP请求处理最佳实践

4.1 使用HttpClientFactory

// 在Startup.cs中配置
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("DefaultClient", client =>
    {
        client.BaseAddress = new Uri("https://api.example.com");
        client.DefaultRequestHeaders.Add("Accept", "application/json");
    })
    .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    })
    .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // 设置Handler生命周期
}

// 在服务中使用
public class MyApiService
{
    private readonly IHttpClientFactory _clientFactory;

    public MyApiService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<string> GetDataAsync()
    {
        var client = _clientFactory.CreateClient("DefaultClient");
        return await client.GetStringAsync("/api/data");
    }
}

4.2 处理JSON数据

public static async Task<T> GetJsonAsync<T>(string url)
{
    using (HttpClient client = new HttpClient())
    {
        var response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<T>(json);
    }
}

public static async Task<HttpResponseMessage> PostJsonAsync<T>(string url, T data)
{
    using (HttpClient client = new HttpClient())
    {
        var json = JsonSerializer.Serialize(data);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        return await client.PostAsync(url, content);
    }
}

4.3 处理文件上传

public static async Task<string> UploadFileAsync(string url, string filePath)
{
    using (HttpClient client = new HttpClient())
    using (var fileStream = File.OpenRead(filePath))
    using (var content = new MultipartFormDataContent())
    {
        var fileContent = new StreamContent(fileStream);
        fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
        content.Add(fileContent, "file", Path.GetFileName(filePath));

        var response = await client.PostAsync(url, content);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

五、高级HTTP请求处理技术

5.1 处理重试策略

// 使用Polly实现重试策略
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(3, retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

// 使用重试策略
public static async Task<string> GetWithRetryAsync(string url)
{
    var retryPolicy = GetRetryPolicy();

    using (HttpClient client = new HttpClient())
    {
        var response = await retryPolicy.ExecuteAsync(() => client.GetAsync(url));
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

5.2 处理超时

public static async Task<string> GetWithTimeoutAsync(string url, int timeoutSeconds)
{
    using (HttpClient client = new HttpClient())
    {
        client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);

        try
        {
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (TaskCanceledException ex) when (!ex.CancellationToken.IsCancellationRequested)
        {
            throw new TimeoutException($"请求超时: {timeoutSeconds}秒", ex);
        }
    }
}

5.3 处理认证和授权

public static async Task<string> GetWithAuthAsync(string url, string token)
{
    using (HttpClient client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", token);

        var response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

// 使用OAuth2客户端凭证流
public static async Task<string> GetAccessTokenAsync(string tokenUrl, string clientId, string clientSecret)
{
    using (HttpClient client = new HttpClient())
    {
        var requestBody = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "client_credentials"),
            new KeyValuePair<string, string>("client_id", clientId),
            new KeyValuePair<string, string>("client_secret", clientSecret),
            new KeyValuePair<string, string>("scope", "api1")
        });

        var response = await client.PostAsync(tokenUrl, requestBody);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        var tokenResponse = JsonSerializer.Deserialize<OAuthTokenResponse>(json);
        return tokenResponse.AccessToken;
    }
}

六、性能优化与调试

6.1 使用HttpClient高效处理请求

// 复用HttpClient实例(推荐方式)
public class ApiService : IDisposable
{
    private readonly HttpClient _httpClient;

    public ApiService()
    {
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.example.com"),
            Timeout = TimeSpan.FromSeconds(30)
        };
    }

    public async Task<string> GetDataAsync()
    {
        var response = await _httpClient.GetAsync("/api/data");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

6.2 日志记录与诊断

// 添加日志记录
public class LoggingHttpMessageHandler : DelegatingHandler
{
    private readonly ILogger _logger;

    public LoggingHttpMessageHandler(ILogger logger)
    {
        _logger = logger;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Request: {request.Method} {request.RequestUri}");

        var stopwatch = Stopwatch.StartNew();
        var response = await base.SendAsync(request, cancellationToken);
        stopwatch.Stop();

        _logger.LogInformation(
            $"Response: {response.StatusCode} in {stopwatch.ElapsedMilliseconds}ms");

        return response;
    }
}

// 注册带日志的HttpClient
services.AddHttpClient("LoggedClient")
    .AddHttpMessageHandler<LoggingHttpMessageHandler>();

七、常见问题与解决方案

7.1 处理SSL/TLS证书问题

public static HttpClient CreateHttpClientWithCustomSsl()
{
    var handler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
        {
            // 自定义证书验证逻辑
            if (errors == System.Net.Security.SslPolicyErrors.None)
                return true;

            // 特定证书指纹验证
            if (cert?.GetCertHashString() == "THUMBPRINT")
                return true;

            return false;
        }
    };

    return new HttpClient(handler);
}

7.2 处理压缩响应

public static async Task<string> GetCompressedContentAsync(string url)
{
    var handler = new HttpClientHandler
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    };

    using (var client = new HttpClient(handler))
    {
        var request = new HttpRequestMessage(HttpMethod.Get, url);
        request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
        request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

结论

C#提供了多种处理HTTP请求的方式,从传统的HttpWebRequest到现代的HttpClientHttpClientFactory。在实际开发中应:

  1. 优先使用HttpClientFactory:管理HttpClient生命周期,避免Socket耗尽
  2. 实现健壮的错误处理:考虑重试策略、超时和异常情况
  3. 优化性能:复用连接、启用压缩、合理设置超时
  4. 确保安全性:正确处理认证、验证证书、保护敏感数据

通过掌握这些HTTP请求处理技术,开发者可以构建高效、可靠且安全的网络应用,满足现代软件开发的各种需求。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注