YARP转发HTTP1.1到gRPC端点时出现'Incomplete message'错误
问题分析与解决方案
核心原因
gRPC协议要求请求体必须包含5字节的消息前缀(1字节压缩标志+4字节大端格式的消息长度),同时需要特定请求头支持。而Angular HttpClient直接发送的请求通常缺少这个前缀,YARP默认不会自动处理gRPC消息格式转换,导致后端gRPC服务无法解析完整消息,抛出"Incomplete message"错误。
具体解决方案
1. 补充必要的gRPC请求头
修改YARP路由配置,添加te: trailers请求头(gRPC要求服务器返回trailer响应),同时确保content-type头正确设置:
"video-provider-grpc": { "Transforms": [ { "PathRemovePrefix": "/bff/grpc/video-provider" }, { "RequestHeader": "content-type", "Set": "application/grpc" }, { "RequestHeader": "te", "Set": "trailers" }, { "RequestHeader": "grpc-accept-encoding", "Set": "identity,gzip" } ], // 其他配置保持不变 }
2. 添加gRPC消息前缀转换中间件
由于Angular无法自动添加gRPC要求的消息前缀,需要在YARP中添加自定义转换逻辑,为请求体补充前缀:
public class GrpcMessagePrefixTransform : HttpTransform { public override async ValueTask ApplyRequestTransformAsync(HttpContext context, HttpRequestMessage proxyRequest) { if (proxyRequest.Content?.Headers.ContentType?.MediaType != "application/grpc") return; var contentBytes = await proxyRequest.Content.ReadAsByteArrayAsync(); var prefix = new byte[5]; // 1字节压缩标志:0表示不压缩 prefix[0] = 0; // 4字节大端格式的消息长度 var lengthBytes = BitConverter.GetBytes(contentBytes.Length); if (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes); // 转换为大端字节序 lengthBytes.CopyTo(prefix, 1); // 合并前缀与原始请求体 var combinedContent = prefix.Concat(contentBytes).ToArray(); proxyRequest.Content = new ByteArrayContent(combinedContent); proxyRequest.Content.Headers.ContentLength = combinedContent.Length; } }
在Program.cs中注册该转换:
builder.Services.AddReverseProxy() .LoadFromConfiguration(builder.Configuration) .AddTransforms<GrpcMessagePrefixTransform>();
3. 验证路径转换正确性
确保YARP路径转换后,最终转发到gRPC服务的路径与原生调用一致。例如:
- Angular请求路径:
/bff/grpc/video-provider/<redactedNamespace>.LocationGrpcService/GetLocations - 经过
PathRemovePrefix: "/bff/grpc/video-provider"转换后,路径应为:<redactedNamespace>.LocationGrpcService/GetLocations,与gRPC服务的预期路径匹配。
4. 确认gRPC服务的HTTP2配置
确保gRPC服务的Kestrel明确支持HTTP2(因为HTTP下默认不开启):
builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(64868, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); });
5. Angular端请求格式校验
确保Angular使用protobuf序列化消息(而非JSON),并设置正确的请求头:
import { HttpClient } from '@angular/common/http'; import { encode } from 'protobufjs'; // 使用protobufjs序列化 // 假设已生成LocationRequest的protobuf类型定义 const request = LocationRequest.create({ /* 请求参数 */ }); const requestBytes = LocationRequest.encode(request).finish(); this.http.post('/bff/grpc/video-provider/<redactedNamespace>.LocationGrpcService/GetLocations', requestBytes, { headers: { 'Content-Type': 'application/grpc', }, responseType: 'blob' // gRPC响应为二进制流 }).subscribe(response => { // 处理响应(需要解析gRPC消息前缀和trailer) });
额外排查点
- 检查Duende BFF是否对gRPC请求的请求体或头进行了修改,可通过中间件打印转发前后的请求内容确认。
- 确保YARP和Duende.BFF的包版本为最新稳定版,避免已知的兼容性问题。
内容的提问来源于stack exchange,提问作者ExtremeSwat
相关产品推荐
相关产品推荐

