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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 03:43:20