GRPC里的一个“坑”
@ 归零 | 星期四,九月 29 日,2022 年 | 1 分钟阅读 | 更新于 星期四,九月 29 日,2022 年

最近,在使用grpc,grpc-gateway时,遇到一个奇怪的问题,返回结果中一个参数在proto中定义的类型是int64,但通过grpc-gateway返回的json结果数据中确变成了string类型,为啥返回的类型和proto定义的类型不一样呢?难道是grpc-gateway做了手脚,于是一通狂搜和翻阅源码,终于解决了心中的疑惑。

问题复现

定义的proto文件

message Response {
	int64 total = 1;
    ......
}

grpc-gateway返回的json数据

{
	"total": "1000",
    ......
}

我们看到total字段在proto中的定义是int64类型,但返回的json数据类型是string,通过查proto的文档,发现官方已经定义了某些类型以及对应的返回值,其中int64类型对应的json序列化的类型是string,如下图:

查看grpc的代码,找到实现如下:

// marshalSingular marshals the given non-repeated field value. This includes
// all scalar types, enums, messages, and groups.
func (e encoder) marshalSingular(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
	if !val.IsValid() {
		e.WriteNull()
		return nil
	}

	switch kind := fd.Kind(); kind {
	case protoreflect.BoolKind:
		e.WriteBool(val.Bool())

	case protoreflect.StringKind:
		if e.WriteString(val.String()) != nil {
			return errors.InvalidUTF8(string(fd.FullName()))
		}

	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
		e.WriteInt(val.Int())

	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
		e.WriteUint(val.Uint())

	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,
		protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:
		// 64-bit integers are written out as JSON string.
		e.WriteString(val.String())

	default:
		panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
	}
	return nil
}

看上边这段代码发现,grpc中在序列化protobuf时,将int64、unsigned int64类型序列化时转换成string, 而int32,unsigned int32还是序列化成原有类型。搜索发现github issus上有一个相关解释,说和javascript中number的实现有关,javascript中的number的最大精度是2的52次方,如果超过该数, 就会缺少精度。因此,grpc的处理方式是将int64处理成string类型。

解决方案

知道了原因,解决方案就很简单了,将proto中定义的int64类型改为int32就可以了。

© 2014 - 2022 Lionel's Blog

Powered by Hugo with theme Dream.