You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 4.5控制台应用如何全局触发自定义代码处理所有出站HTTP请求?

全局捕获.NET 4.5控制台应用的出站HTTP请求

当然可以实现,针对.NET 4.5控制台应用中常见的两种HTTP请求方式,分别提供无需修改原有调用代码的全局拦截方案:

方案1:针对HttpWebRequest(传统HTTP请求方式)

.NET 4.5允许通过WebRequest.RegisterPrefix注册自定义请求处理程序,拦截所有HTTP/HTTPS请求:

  1. 创建自定义请求类与流包装类
public class LoggingHttpWebRequest : HttpWebRequest
{
    private readonly HttpWebRequest _originalRequest;

    public LoggingHttpWebRequest(Uri uri, HttpWebRequest originalRequest) : base(uri)
    {
        _originalRequest = originalRequest;
        // 复制原始请求的核心属性
        Method = originalRequest.Method;
        ContentType = originalRequest.ContentType;
        Headers = originalRequest.Headers;
    }

    // 重写获取请求流方法,捕获请求体
    public override Stream GetRequestStream()
    {
        var originalStream = _originalRequest.GetRequestStream();
        var loggingStream = new MemoryStream();
        // 记录请求基础信息
        LogRequest(Method, RequestUri, Headers);
        // 返回包装流,同时写入原始流和日志流
        return new TeeStream(originalStream, loggingStream);
    }

    // 重写获取响应方法,捕获响应信息
    public override WebResponse GetResponse()
    {
        var response = _originalRequest.GetResponse();
        var httpResponse = response as HttpWebResponse;
        // 记录响应信息
        LogResponse(response.ResponseUri, response.Headers, httpResponse?.StatusCode ?? HttpStatusCode.OK);
        return response;
    }

    // 自定义数据库日志写入逻辑
    private void LogRequest(string method, Uri uri, WebHeaderCollection headers)
    {
        // 示例:写入请求日志到数据库
        // using(var conn = new SqlConnection("你的连接字符串"))
        // {
        //     conn.Execute(@"INSERT INTO HttpRequestLogs 
        //                   (Method, Url, Headers, CreateTime) 
        //                   VALUES (@Method, @Url, @Headers, @CreateTime)",
        //         new { Method = method, Url = uri.ToString(), Headers = headers.ToString(), CreateTime = DateTime.Now });
        // }
    }

    private void LogResponse(Uri uri, WebHeaderCollection headers, HttpStatusCode statusCode)
    {
        // 示例:写入响应日志到数据库
        // using(var conn = new SqlConnection("你的连接字符串"))
        // {
        //     conn.Execute(@"INSERT INTO HttpResponseLogs 
        //                   (Url, StatusCode, Headers, CreateTime) 
        //                   VALUES (@Url, @StatusCode, @Headers, @CreateTime)",
        //         new { Url = uri.ToString(), StatusCode = (int)statusCode, Headers = headers.ToString(), CreateTime = DateTime.Now });
        // }
    }
}

// 辅助类:同时向两个流写入数据
public class TeeStream : Stream
{
    private readonly Stream _primaryStream;
    private readonly Stream _secondaryStream;

    public TeeStream(Stream primary, Stream secondary)
    {
        _primaryStream = primary;
        _secondaryStream = secondary;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _primaryStream.Write(buffer, offset, count);
        _secondaryStream.Write(buffer, offset, count);
        _secondaryStream.Flush();
    }

    // 其他Stream抽象方法直接委托给原始流
    public override bool CanRead => _primaryStream.CanRead;
    public override bool CanSeek => _primaryStream.CanSeek;
    public override bool CanWrite => _primaryStream.CanWrite;
    public override long Length => _primaryStream.Length;
    public override long Position { get => _primaryStream.Position; set => _primaryStream.Position = value; }
    public override void Flush() => _primaryStream.Flush();
    public override int Read(byte[] buffer, int offset, int count) => _primaryStream.Read(buffer, offset, count);
    public override long Seek(long offset, SeekOrigin origin) => _primaryStream.Seek(offset, origin);
    public override void SetLength(long value) => _primaryStream.SetLength(value);
}
  1. 在应用启动时注册自定义请求创建器
static void Main(string[] args)
{
    // 注册HTTP/HTTPS请求拦截器
    WebRequest.RegisterPrefix("http://", new LoggingWebRequestCreator());
    WebRequest.RegisterPrefix("https://", new LoggingWebRequestCreator());

    // 原有业务代码...
}

public class LoggingWebRequestCreator : IWebRequestCreate
{
    public WebRequest Create(Uri uri)
    {
        var originalRequest = WebRequest.Create(uri) as HttpWebRequest;
        return new LoggingHttpWebRequest(uri, originalRequest);
    }
}

方案2:针对HttpClient(现代HTTP客户端)

如果项目使用HttpClient,可通过自定义DelegatingHandler实现全局拦截:

  1. 创建日志处理程序
public class LoggingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 记录请求信息
        await LogRequestAsync(request);

        // 转发请求并获取响应
        var response = await base.SendAsync(request, cancellationToken);

        // 记录响应信息
        await LogResponseAsync(response);

        return response;
    }

    private async Task LogRequestAsync(HttpRequestMessage request)
    {
        string requestBody = null;
        if (request.Content != null)
        {
            requestBody = await request.Content.ReadAsStringAsync();
            // 重置内容流,避免后续处理无法读取
            request.Content = new StringContent(requestBody, request.Content.Headers.ContentType?.CharSet, request.Content.Headers.ContentType?.MediaType);
        }

        // 写入请求日志到数据库
        // using(var conn = new SqlConnection("你的连接字符串"))
        // {
        //     await conn.ExecuteAsync(@"INSERT INTO HttpRequestLogs 
        //                           (Method, Url, Headers, Body, CreateTime) 
        //                           VALUES (@Method, @Url, @Headers, @Body, @CreateTime)",
        //         new {
        //             Method = request.Method.Method,
        //             Url = request.RequestUri.ToString(),
        //             Headers = string.Join(";", request.Headers.Select(kv => $"{kv.Key}:{string.Join(",", kv.Value)}")),
        //             Body = requestBody,
        //             CreateTime = DateTime.Now
        //         });
        // }
    }

    private async Task LogResponseAsync(HttpResponseMessage response)
    {
        string responseBody = await response.Content.ReadAsStringAsync();
        // 重置响应内容流
        response.Content = new StringContent(responseBody, response.Content.Headers.ContentType?.CharSet, response.Content.Headers.ContentType?.MediaType);

        // 写入响应日志到数据库
        // using(var conn = new SqlConnection("你的连接字符串"))
        // {
        //     await conn.ExecuteAsync(@"INSERT INTO HttpResponseLogs 
        //                           (Url, StatusCode, Headers, Body, CreateTime) 
        //                           VALUES (@Url, @StatusCode, @Headers, @Body, @CreateTime)",
        //         new {
        //             Url = response.RequestMessage.RequestUri.ToString(),
        //             StatusCode = (int)response.StatusCode,
        //             Headers = string.Join(";", response.Headers.Select(kv => $"{kv.Key}:{string.Join(",", kv.Value)}")),
        //             Body = responseBody,
        //             CreateTime = DateTime.Now
        //         });
        // }
    }
}
  1. 全局配置HttpClient
    如果现有代码使用统一的HttpClient实例,直接替换为带日志处理的实例即可:
static void Main(string[] args)
{
    // 创建带日志处理的HttpClient
    var loggingHandler = new LoggingHandler
    {
        InnerHandler = new HttpClientHandler()
    };
    var globalHttpClient = new HttpClient(loggingHandler);

    // 原有业务代码中使用globalHttpClient替代原实例,或修改项目中创建HttpClient的统一逻辑
}

注意事项

  • 若使用HttpWebRequest方案,需确保所有请求都是通过WebRequest.Create创建的,直接new HttpWebRequest的情况无法被拦截,需检查代码并少量调整(或通过反射钩子处理,但风险较高)。
  • 日志写入操作建议异步执行,避免阻塞请求流程影响性能。
  • 务必过滤敏感信息(如Token、密码),禁止写入日志。
  • 测试时需覆盖异常场景(如超时、网络错误),确保日志捕获逻辑稳定。

内容的提问来源于stack exchange,提问作者Yash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:10:29