.NET 4.5控制台应用如何全局触发自定义代码处理所有出站HTTP请求?
全局捕获.NET 4.5控制台应用的出站HTTP请求
当然可以实现,针对.NET 4.5控制台应用中常见的两种HTTP请求方式,分别提供无需修改原有调用代码的全局拦截方案:
方案1:针对HttpWebRequest(传统HTTP请求方式)
.NET 4.5允许通过WebRequest.RegisterPrefix注册自定义请求处理程序,拦截所有HTTP/HTTPS请求:
- 创建自定义请求类与流包装类
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); }
- 在应用启动时注册自定义请求创建器
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实现全局拦截:
- 创建日志处理程序
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 // }); // } } }
- 全局配置
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
相关产品推荐
相关产品推荐

