gRPC header问题
问题概述
在开发过程中,无意间升级了grpc 的版本到1.42.0,之后所有的接口就开始抛出这个错误
而且非常奇怪的是服务端这里没有任何额外信息,只有一行请求被拒绝的日志
经过各种尝试我发现如果请求时不带Connection: keep-alive
这个header,就可以正常访问接口
花费了大量时间,查阅了各种资料后,在官方github仓库的 grpc-go 1.42.0 release note 我找到了答案
问题原因
在grpc升级到1.42.0后,grpc go server会把connection header认为是 malformed header 而拒绝
而如果项目使用了grpc-gateway来将Http请求反代到gRPC server,grpc-gateway会把合法的http header加上自己的prefix后透传给grpc server用来区分http header
显而易见 http connection header 被认为是不合法的,所以 如果通过grpc-gateway接收的http请求包含 Connection: keep-alive
这个header,服务器会直接拒绝该请求
解决办法
替换grpc-gateway默认的headerMatcher, 将connect header加上prefix即可
// new http mux server时,加入headerMatcher中间件
gwMux := runtime.NewServeMux(
// 加入自定义的header matcher
runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {
return myDefaultHeaderMatcher(s)
}),
runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) {
return s, true
}),
runtime.WithForwardResponseOption(httpResponseModifier),
runtime.WithMarshalerOption(runtime.MIMEWildcard, &gateway.JSONPb{
OrigName: true,
EmitDefaults: true,
}),
)
// myDefaultHeaderMatcher,修改自github.com/grpc-ecosystem/grpc-gateway/runtime.WithHeaderMatcher中的函数
// replacement for default HeaderMatcher
// same as runtime.WithHeaderMatcher
// did some modification for header matcher
// see https://github.com/grpc-ecosystem/grpc-gateway/issues/2447
myDefaultHeaderMatcher := func(key string) (string, bool) {
key = textproto.CanonicalMIMEHeaderKey(key)
// use modifier http header function
if isPermanentHTTPHeader(key) {
return runtime.MetadataPrefix + key, true
} else if strings.HasPrefix(key, runtime.MetadataHeaderPrefix) {
return key[len(runtime.MetadataPrefix):], true
}
// return header no matter what
return key, true
}
// 自定义的http header判断函数, 加入connection header
func isPermanentHTTPHeader(hdr string) bool {
// after google.golang.org/grpc v1.42.0, grpc will reject connection header
// we add it to header modifier manually
// see https://github.com/grpc/grpc-go/issues/5175
switch hdr {
case
"Accept",
"Accept-Charset",
"Accept-Language",
"Accept-Ranges",
"Authorization",
"Cache-Control",
"Content-Type",
"Cookie",
"Date",
"Expect",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Schedule-Tag-Match",
"If-Unmodified-Since",
"Max-Forwards",
"Origin",
"Pragma",
"Referer",
"User-Agent",
"Via",
"Warning",
// add connection header
"Connection":
return true
}
return false
}
参考