如何用System.Net.Http.HttpClient获取解压前的HTTP请求大小以监控用量
如何用System.Net.Http.HttpClient追踪解压前的HTTP数据用量
一、统计发送的HTTP请求总传输大小(含请求头+请求体)
如果你需要统计每次请求实际通过网络发送的字节数(包括请求头和请求体,无论请求体是否压缩),可以通过自定义DelegatingHandler拦截请求并计算大小:
- 实现自定义追踪Handler:
public class RequestSizeTrackingHandler : DelegatingHandler { // 累计已发送的总字节数 public long TotalSentBytes { get; private set; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 计算请求行+请求头的大小 var headerSize = CalculateHeadersSize(request); // 计算请求体大小 long bodySize = 0; if (request.Content != null) { if (request.Content is ByteArrayContent byteContent) { bodySize = byteContent.Headers.ContentLength ?? byteContent.ReadAsByteArrayAsync().Result.Length; } else if (request.Content is StringContent stringContent) { var encoding = stringContent.Headers.ContentType?.CharSet ?? "utf-8"; bodySize = Encoding.GetEncoding(encoding).GetByteCount(await stringContent.ReadAsStringAsync(cancellationToken)); } else { // 其他类型Content先缓冲到内存计算大小,再重新设置回请求 var buffer = await request.Content.ReadAsByteArrayAsync(cancellationToken); bodySize = buffer.Length; request.Content = new ByteArrayContent(buffer); // 复制原请求体头部信息 foreach (var header in request.Content.Headers) { request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } } // 累计总发送量并检查阈值 var requestTotalSize = headerSize + bodySize; TotalSentBytes += requestTotalSize; if (TotalSentBytes >= YourThresholdValue) { throw new OperationCanceledException("已达到数据发送阈值,停止请求"); } return await base.SendAsync(request, cancellationToken); } private long CalculateHeadersSize(HttpRequestMessage request) { // 计算请求行大小:请求方法 + 路径 + HTTP版本 var requestLine = $"{request.Method} {request.RequestUri.PathAndQuery} HTTP/{request.Version}\r\n"; long size = Encoding.ASCII.GetByteCount(requestLine); // 计算所有请求头的大小 foreach (var header in request.Headers) { foreach (var value in header.Value) { size += Encoding.ASCII.GetByteCount($"{header.Key}: {value}\r\n"); } } // 头部末尾的空行 size += Encoding.ASCII.GetByteCount("\r\n"); return size; } }
- 初始化HttpClient并使用:
var trackingHandler = new RequestSizeTrackingHandler(); var httpClient = new HttpClient(trackingHandler) { BaseAddress = new Uri("https://your-api-domain.com/") }; try { while (true) { var response = await httpClient.GetAsync("/target-endpoint"); // 处理响应逻辑... } } catch (OperationCanceledException ex) { Console.WriteLine(ex.Message); }
二、统计接收的HTTP响应压缩前传输大小(网络实际接收字节数)
HttpClient默认会自动解压gzip/deflate格式的响应,若要统计服务器发送的原始压缩大小,有两种实现方式:
方法1:禁用自动解压,直接读取响应头的Content-Length
var httpClient = new HttpClient(); // 清空Accept-Encoding,禁用自动解压 httpClient.DefaultRequestHeaders.AcceptEncoding.Clear(); long TotalReceivedBytes = 0; const long Threshold = 1024 * 1024; // 1MB阈值 while (true) { var response = await httpClient.GetAsync("/target-endpoint"); // 累加响应大小 if (response.Content.Headers.ContentLength.HasValue) { TotalReceivedBytes += response.Content.Headers.ContentLength.Value; } else { // 无Content-Length时,读取整个响应流计算大小 var contentBytes = await response.Content.ReadAsByteArrayAsync(); TotalReceivedBytes += contentBytes.Length; } if (TotalReceivedBytes >= Threshold) { Console.WriteLine("已达到数据接收阈值,停止请求"); break; } // 手动解压响应内容(示例为gzip格式) var decompressedContent = await DecompressGzipContent(response.Content); // 处理解压后内容... } // 手动解压gzip响应的方法 async Task<string> DecompressGzipContent(HttpContent content) { using var stream = await content.ReadAsStreamAsync(); using var gzipStream = new GZipStream(stream, CompressionMode.Decompress); using var reader = new StreamReader(gzipStream); return await reader.ReadToEndAsync(); }
方法2:保留自动解压,用Handler拦截获取原始大小
这种方式不需要禁用自动解压,通过Handler在响应到达时统计原始传输大小:
public class ResponseSizeTrackingHandler : DelegatingHandler { public long TotalReceivedBytes { get; private set; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); // 计算响应原始大小 long responseSize = 0; if (response.Content.Headers.ContentLength.HasValue) { responseSize = response.Content.Headers.ContentLength.Value; } else { var buffer = await response.Content.ReadAsByteArrayAsync(cancellationToken); responseSize = buffer.Length; // 重新设置Content,避免后续处理无内容 response.Content = new ByteArrayContent(buffer); foreach (var header in response.Content.Headers) { response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } TotalReceivedBytes += responseSize; if (TotalReceivedBytes >= YourThresholdValue) { throw new OperationCanceledException("已达到数据接收阈值,停止请求"); } return response; } } // 使用示例 var trackingHandler = new ResponseSizeTrackingHandler(); var httpClient = new HttpClient(trackingHandler) { BaseAddress = new Uri("https://your-api-domain.com/") }; try { while (true) { var response = await httpClient.GetAsync("/target-endpoint"); // HttpClient自动解压,直接处理内容 var content = await response.Content.ReadAsStringAsync(); } } catch (OperationCanceledException ex) { Console.WriteLine(ex.Message); }
三、统计请求体解压前的原始大小(若请求体被压缩发送)
如果你会压缩请求体后再发送,只需在压缩前记录原始内容大小并累计即可:
long TotalRequestRawSize = 0; const long Threshold = 10 * 1024 * 1024; // 10MB阈值 var httpClient = new HttpClient(); while (true) { // 原始请求内容 var rawContent = "待发送的大请求内容"; var rawSize = Encoding.UTF8.GetByteCount(rawContent); TotalRequestRawSize += rawSize; if (TotalRequestRawSize >= Threshold) { Console.WriteLine("已达到请求原始数据阈值,停止请求"); break; } // 压缩请求体 using var memoryStream = new MemoryStream(); using var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress); using var writer = new StreamWriter(gzipStream); await writer.WriteAsync(rawContent); await writer.FlushAsync(); gzipStream.Close(); var compressedContent = new ByteArrayContent(memoryStream.ToArray()); compressedContent.Headers.ContentEncoding.Add("gzip"); compressedContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // 发送请求 var response = await httpClient.PostAsync("/target-endpoint", compressedContent); // 处理响应... }
内容的提问来源于stack exchange,提问作者AlgoRythm
相关产品推荐
相关产品推荐

