.NET6中multipart/x-mixed-replace转发摄像头流异常问题排查
控制器通过HttpClient请求摄像头RTSP流并提供给API消费者。.NET Core 2环境下所有系统运行正常,但升级到.NET6后,仅部分同虚拟机上的系统能正常显示流,其余系统无法正常工作。
- 禁用认证/SSL以便跟踪流
- 用Wireshark捕获所有请求:
- 流无法工作的系统中,中止请求后调试控制台输出的数据格式正常
- 异常系统曾捕获到
IOException: The stream was too long,说明摄像头HttpClient到输出流的复制存在缓冲问题,但仅部分系统出现该异常
- 添加
EnableRangeProcessing配置,无效果
控制器代码
[ResponseCache(NoStore = true)] public async Task<IActionResult> ImageStream(int cameraId, int maxfps = 30) { var stream = await _cameraService.GetCameraStreamAsync(cameraId, credentials, maxfps); return new FileStreamResult( stream, "multipart/x-mixed-replace; boundary=\"myBoundary\""); }
CameraService代码
public async Task<Stream> GetCameraStreamAsync(int cameraId, CameraCredentialsDto credentials, int maxFps) { var client = new HttpClient { BaseAddress = new Uri($"http://{credentials.IPaddress}") }; client.SetBasicAuthentication(credentials.Username, credentials.Password); client.DefaultRequestHeaders.ExpectContinue = false; string relativeUrl = $"cameraSpecificURL"; return await client.GetStreamAsync(relativeUrl); }
- 对比正常与异常系统的请求头,客户端请求无差异:
GET /appapi/v1/camera/1003/stream?maxfps=30&token=redacted HTTP/1.1
Host: ipaddress:5001
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,nl;q=0.8
- 返回数据格式正确,新段前带有双连字符
- 异常系统直接请求摄像头流时,数据正常
1. 手动复制流到响应体,避免直接返回HttpClient流
.NET6中HttpClient返回的流行为与.NET Core 2存在差异,直接返回可能引发缓冲问题。修改控制器代码:
[ResponseCache(NoStore = true)] public async Task<IActionResult> ImageStream(int cameraId, int maxfps = 30) { using var cameraStream = await _cameraService.GetCameraStreamAsync(cameraId, credentials, maxfps); Response.ContentType = "multipart/x-mixed-replace; boundary=\"myBoundary\""; Response.Headers.CacheControl = "no-cache, no-store"; await cameraStream.CopyToAsync(Response.Body); return new EmptyResult(); }
同时在Program.cs中开启响应体自动刷新:
app.Use(async (context, next) => { context.Response.BodyWriter.AutoFlush = true; await next(); });
2. 复用HttpClient实例,避免频繁创建
当前每次请求都新建HttpClient会导致连接池耗尽,引发部分系统异常。修改CameraService使用单例HttpClient:
// Program.cs注册单例HttpClient builder.Services.AddHttpClient<ICameraService, CameraService>(); // 修改CameraService构造函数注入 private readonly HttpClient _client; public CameraService(HttpClient client) { _client = client; } public async Task<Stream> GetCameraStreamAsync(int cameraId, CameraCredentialsDto credentials, int maxFps) { _client.BaseAddress = new Uri($"http://{credentials.IPaddress}"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{credentials.Username}:{credentials.Password}"))); _client.DefaultRequestHeaders.ExpectContinue = false; string relativeUrl = $"cameraSpecificURL"; return await _client.GetStreamAsync(relativeUrl); }
如果多摄像头共用HttpClient,需注意每次请求前重置BaseAddress和授权头,或为每个摄像头创建命名HttpClient。
3. 禁用响应压缩
.NET6默认启用的响应压缩不适用于multipart流,会导致缓冲溢出。在Program.cs中禁用:
// 移除响应压缩中间件 // app.UseResponseCompression(); // 或针对multipart类型禁用压缩 builder.Services.Configure<ResponseCompressionOptions>(options => { options.MimeTypes = options.MimeTypes.Where(m => m != "multipart/x-mixed-replace").ToList(); });
4. 检查虚拟机资源限制
部分虚拟机可能存在CPU、内存或网络带宽限制,导致流处理时缓冲溢出。检查异常系统的资源使用率,确保分配足够的系统资源。
内容的提问来源于stack exchange,提问作者impeeNL

