- 微服务
- 后端
- RPC框架
【免费下载链接】kit
A standard library for microservices.
JSON-RPC 是一种"轻量级远程过程调用协议",它以人类可读的 JSON 报文完成跨服务方法调用,天然适合微服务之间或前后端之间的 RPC 风格 API。go-kit 的transport/http/jsonrpc包把 JSON-RPC 2.0 绑定到了标准 go-kit Endpoint 之上:服务端以一个http.Handler承载全部方法,客户端则把远程方法封装成可复用、可组合的endpoint.Endpoint。读完本文,你将掌握用EndpointCodec注册与路由 JSON-RPC 方法、用解码器/编码器处理params与result、定制错误对象与错误码、配置服务端/客户端钩子,以及通过客户端封装直接调用远程 JSON-RPC 服务的完整实战方案。
JSON-RPC 与 go-kit 的结合方式
在 go-kit 的架构中,transport/http/jsonrpc位于 transport/http/jsonrpc 目录,它把 JSON-RPC 2.0 协议封装为一种传输绑定(binding)。从整体设计上看:
- 服务端是一个 HTTP Handler:JSON-RPC server 实现
net/http.Handler,接收所有发往特定 URL(例如/rpc)的 POST 请求。它读取 Request Object 中的method属性,据此把请求路由到对应的处理代码(见 server.go 的Server类型声明)。 - 每个 JSON-RPC 方法是一个
EndpointCodec:一个 go-kitEndpoint(定义于 endpoint/endpoint.go),前后夹着解码器和编码器。解码器从 JSON-RPC 请求的params中拆出领域对象交给 Endpoint;编码器接收 Endpoint 的输出,编码为 JSON-RPC 的result字段。 - 协议版本与媒体类型固定:包内常量
Version = "2.0"、ContentType = "application/json; charset=utf-8",定义于 request_response_types.go。
这一"端点 + 编解码"的抽象意味着:你只关心业务逻辑(Endpoint)、请求形状(Decoder)、响应形状(Encoder),传输层细节全部由 jsonrpc 包接管。
完整示例:构建一个 Add(求和)服务
原文档以"两个整数相加"的服务为例,将其暴露在http://localhost/rpc。对sum方法的请求是发往http://localhost/rpc的 POST,请求体如下:
{ "id": 123, "jsonrpc": "2.0", "method": "sum", "params": { "A": 2, "B": 2 } }下面按"路由表 → 解码器 → 编码器 → 装配服务端"的次序逐步实现。
1. EndpointCodecMap:方法路由表
服务端把"方法名 → 处理单元"的映射表称为EndpointCodecMap。它的 key 是 JSON-RPC 方法名,value 是EndpointCodec(结构定义见 encode_decode.go)。这里我们把sum方法路由到sumEndpoint及其编解码函数:
jsonrpc.EndpointCodecMap{ "sum": jsonrpc.EndpointCodec{ Endpoint: sumEndpoint, Decode: decodeSumRequest, Encode: encodeSumResponse, }, }2. Decoder:从 params 提取领域对象
解码器的类型签名是DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)(见 encode_decode.go)。注意:它拿到的只是 Request Object 中params属性的原始 JSON,不是整个请求对象,返回值将作为 Endpoint 的输入。对本例,输出应当是SumRequest:
type SumRequest struct { A, B int } func decodeSumRequest(ctx context.Context, msg json.RawMessage) (interface{}, error) { var req SumRequest err := json.Unmarshal(msg, &req) if err != nil { return nil, err } return req, nil }SumRequest接下来会被传入 Endpoint。端点完成业务计算后,控制权交给编码器。
3. Encoder:把结果编码为 result 字段
编码器类型签名是EncodeResponseFunc func(context.Context, interface{}) (response json.RawMessage, err error)(见 encode_decode.go)。它接收 Endpoint 的输出,构建将写入 Response Object 的result字段的原始 JSON。本例的求和结果是一个普通int:
func encodeSumResponse(ctx context.Context, result interface{}) (json.RawMessage, error) { sum, ok := result.(int) if !ok { return nil, errors.New("result is not an int") } b, err := json.Marshal(sum) if err != nil { return nil, err } return b, nil }4. 端点的实现
按上述解码器与编码器的约定,sumEndpoint需要接收SumRequest并返回int:
func sumEndpoint(ctx context.Context, request interface{}) (interface{}, error) { sumReq, ok := request.(SumRequest) if !ok { return nil, errors.New("request is not a SumRequest") } return sumReq.A + sumReq.B, nil }这正是标准 go-kit Endpoint 的形态:func(ctx context.Context, request interface{}) (response interface{}, err error)。
5. 装配 Server 并启动
将 EndpointCodec(解码器 + 端点 + 编码器)组装完成后,用jsonrpc.NewServer构造服务端,再注册到标准库的 HTTP 路由上:
handler := jsonrpc.NewServer(jsonrpc.EndpointCodecMap{ "sum": jsonrpc.EndpointCodec{ Endpoint: sumEndpoint, Decode: decodeSumRequest, Encode: encodeSumResponse, }, }) http.Handle("/rpc", handler) http.ListenAndServe(":80", nil)NewServer的完整签名与默认行为见 server.go:默认使用DefaultErrorEncoder处理错误、使用log.NewNopLogger()(不记录日志),可通过ServerOption覆盖。
完成上述全部代码后,开篇的示例请求会得到如下响应:
{ "jsonrpc": "2.0", "result": 4 }服务端源码解析:ServeHTTP 的处理流水线
Server.ServeHTTP(server.go)完整展示了请求的生命周期,这也是理解 jsonrpc 包的关键:
- HTTP 方法校验:非 POST 请求直接返回
405 must POST(对应测试TestCanRejectNonPostRequest,见 server_test.go)。 - 执行
ServerBefore钩子:在请求体解码前,对原始http.Request做加工,返回值注入 context。 - 解析 Request Object:
json.NewDecoder(r.Body).Decode(&req),解析失败则返回ParseError(-32700)错误响应。 - 注入上下文:把请求 ID 存入
requestIDKey、把方法名存入ContextKeyRequestMethod(见 request_response_types.go),后续编解码器可通过 context 读取。 - 执行
ServerBeforeCodec钩子:此时 JSON 请求体已解码为Request结构,但方法解码器尚未调用——这是检查 RPC 请求内容的最后机会。 - 按 method 查路由表:
s.ecm[req.Method]查不到时返回MethodNotFoundError(-32601)。 - 调用解码器:
ecm.Decode(ctx, req.Params),失败则走错误编码。 - 调用 Endpoint:
ecm.Endpoint(ctx, reqParams),失败同样走错误编码。 - 执行
ServerAfter钩子:在响应写入客户端前对http.ResponseWriter做加工。 - 调用编码器并写回响应:组装
Response{ID, JSONRPC: Version, Result},设置Content-Type后 JSON 编码写出。
值得注意的一点:即便业务出错,HTTP 状态码默认仍是200 OK(见DefaultErrorEncoder中的w.WriteHeader(http.StatusOK)),错误信息通过 JSON-RPCerror对象携带。这一设计与 JSON-RPC 规范"错误是响应对象的一部分"保持一致。
服务端可选项:ServerOption 一览
NewServer的第二个参数是变长的ServerOption,全部定义于 server.go:
| 选项 | 作用 |
|---|---|
ServerBefore(...httptransport.RequestFunc) | 在请求体解码之前对http.Request执行钩子函数,常用于鉴权、提取 Header 等 |
ServerBeforeCodec(...RequestFunc) | JSON 请求体已解码为Request之后、方法解码器调用之前执行,可检查method/id/params等 RPC 内容 |
ServerAfter(...httptransport.ServerResponseFunc) | 端点调用之后、任何内容写入客户端之前执行 |
ServerErrorEncoder(ee httptransport.ErrorEncoder) | 自定义错误编码器,可自行控制错误格式与 HTTP 状态码 |
ServerErrorLogger(logger log.Logger) | 记录非致命错误;默认不记录任何日志(NopLogger) |
ServerFinalizer(f httptransport.ServerFinalizerFunc) | 每次 HTTP 请求结束时执行,适合统计、审计;默认不注册 |
ServerBeforeCodec与ServerBefore的区别正是 jsonrpc 绑定相对普通 HTTP 传输多出的能力:前者可以访问已解析的 JSON-RPCRequest(类型为RequestFunc func(context.Context, *http.Request, Request) context.Context),而后者只能操作原始 HTTP 请求。
错误处理:Error 对象、标准错误码与自定义编码
jsonrpc 包把 JSON-RPC 规范中的 Error Object 建模为Error结构(error.go):
type Error struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` }标准错误码
包内预定义了 JSON-RPC 2.0 规范的标准错误码常量(error.go):
| 常量 | 取值 | 含义 |
|---|---|---|
ParseError | -32700 | 服务端解析 JSON 文本失败 |
InvalidRequestError | -32600 | 收到的 JSON 不是合法的 Request 对象 |
MethodNotFoundError | -32601 | 方法不存在或不可用 |
InvalidParamsError | -32602 | 方法参数非法 |
InternalError | -32603 | 服务端内部错误 |
每个错误码还配有规范默认消息(errorMessagemap,error.go),可通过ErrorMessage(code)查询;Error.Error()在Message为空时回退到默认消息(error.go)。
默认错误编码器的行为
DefaultErrorEncoder(server.go)负责把 Go error 转成 JSON-RPC 错误响应,其行为规则:
- 始终设置
Content-Type: application/json; charset=utf-8; - 若错误实现了
httptransport.Headerer,则复制其自定义 Header; - 默认使用
InternalError(-32603)作为错误码; - 若错误实现了
ErrorCoder接口(即具有ErrorCode() int方法),则使用其返回的错误码; - HTTP 状态码固定为
200 OK,错误以 JSON-RPCerror字段返回,并回显请求 ID。
ErrorCoder接口定义于 server.go。包内五种内部错误类型(parseError、invalidRequestError、methodNotFoundError、invalidParamsError、internalError)都已实现该接口(error.go),因此服务端内部错误会自动携带正确的错误码。
自定义错误编码器
业务上若想让特定错误映射到特定 HTTP 状态码,可覆盖ServerErrorEncoder。测试TestServerErrorEncoder(server_test.go)演示了将errors.New("teapot")映射为 HTTP 418(http.StatusTeapot)的用法:
handler := jsonrpc.NewServer( ecm, jsonrpc.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }), )客户端:把远程方法封装成 Endpoint
除了服务端,jsonrpc 包还提供客户端绑定(client.go),用于调用远程 JSON-RPC 方法。Client封装了目标 URL、方法名与编解码函数,其Endpoint()方法返回一个可直接参与 go-kit 组合的endpoint.Endpoint。
最小客户端用法
u, _ := url.Parse("http://localhost/rpc") client := jsonrpc.NewClient(u, "sum") sumEndpoint := client.Endpoint() // 可像普通 Endpoint 一样使用 resp, err := sumEndpoint(ctx, SumRequest{A: 2, B: 2})NewClient的默认行为(client.go):
- 使用
http.DefaultClient发起请求; - 请求体编码用
DefaultRequestEncoder(直接json.Marshal请求对象); - 响应解码用
DefaultResponseDecoder(若响应含Error则返回该错误,否则把result反序列化为interface{}); - 请求 ID 由
NewAutoIncrementID(0)生成自增整数(基于atomic.AddUint64,见 client.go)。
ClientOption 一览
| 选项 | 作用 |
|---|---|
SetClient(c httptransport.HTTPClient) | 替换底层 HTTP 客户端,默认http.DefaultClient |
ClientBefore(...httptransport.RequestFunc) | 请求发出前对http.Request加工(如加 Header),对应测试见 client_test.go |
ClientAfter(...httptransport.ClientResponseFunc) | 收到响应后、解码前执行,可把响应信息放入 context 供解码器读取 |
ClientFinalizer(f httptransport.ClientFinalizerFunc) | 每次请求结束(含出错)时执行,常用于错误日志 |
ClientRequestEncoder(enc EncodeRequestFunc) | 自定义请求参数编码 |
ClientResponseDecoder(dec DecodeResponseFunc) | 自定义响应解码,可自行决定响应中的error是否上抛 |
ClientRequestIDGenerator(g RequestIDGenerator) | 自定义请求 ID 生成器(实现Generate() interface{}) |
BufferedStream(buffered bool) | 置为 true 时不关闭响应 Body,便于以缓冲流方式传输大文件 |
客户端发送的请求体结构由clientRequest定义(client.go):jsonrpc、method、params、id四字段,符合 JSON-RPC 2.0 Request Object 规范。测试TestClientHappyPath(client_test.go)端到端验证了客户端请求在服务端被正确解析:ID、JSONRPC版本、params与发送的请求对象完全一致,且before/after/finalizer钩子均被调用。
测试验证:行为即规范
jsonrpc 包的测试文件为本示例的每一环节提供了可运行的验证依据:
- 服务端错误路径:
TestServerBadDecode、TestServerBadEndpoint、TestServerBadEncode(server_test.go)分别验证解码、端点、编码失败时返回InternalError且保持 HTTP 200、回显请求 ID。 - 非法请求拒绝:
TestCanRejectNonPostRequest验证非 POST 返回 405;TestCanRejectInvalidJSON验证非法 JSON 返回ParseError且 ID 为空(server_test.go)。 - 未注册方法:
TestServerUnregisteredMethod验证未注册方法返回MethodNotFoundError(server_test.go)。 - 客户端钩子:
TestBeforeAfterFuncs验证客户端 before/after/finalizer 在各种响应(空 body、500、错误对象)下均被调用(client_test.go);TestCanUseDefaults验证客户端可完全使用默认编解码。 - 错误语义:
TestError与TestErrorsSatisfyError(error_test.go)验证Error的消息覆盖逻辑、错误消息映射,以及所有内部错误类型都同时满足error与ErrorCoder接口。
小结
go-kit 的 jsonrpc 包把"协议规范"与"业务代码"做了干净切割:服务端只需提供EndpointCodecMap(方法名 → EndpointCodec),客户端只需提供目标 URL 与方法名,即可获得符合 JSON-RPC 2.0 规范的完整通信能力。实际落地时建议:
- 用
EndpointCodecMap统一管理全部方法路由,保持"一方法一编解码"的清晰映射; - 通过
ServerBeforeCodec在解码前统一校验请求内容,通过ServerFinalizer做全量请求审计; - 业务错误实现
ErrorCoder接口以携带语义化错误码,必要时用自定义ServerErrorEncoder控制 HTTP 状态; - 客户端侧用
ClientResponseDecoder把响应直接解码为领域类型,避免interface{}的层层断言。
掌握了这套 EndpointCodec 模式,你便能在 go-kit 微服务体系中快速接入标准 JSON-RPC 2.0 接口,并让服务端与客户端共享同一套方法语义。
- 微服务
- 后端
- RPC框架
【免费下载链接】kit
A standard library for microservices.
相关推荐
用 SeaORM 与 jsonrpsee 构建 Rust JSON-RPC 服务:jsonrpsee_example 实战指南
用 SeaORM 与 jsonrpsee 构建 Rust JSON RPC 服务:jsonrpsee_example 实战指南 导读 本文以 examples/
后端数据库ORM开源KVM软件 Input Leap 真的好用吗?一文讲透它的核心玩法
开源KVM软件 Input Leap 真的好用吗?一文讲透它的核心玩法 每天在台式机和笔记本之间来回切换,你是不是也烦透了?手里握着鼠标,眼睛盯着两台屏幕,人却
桌面应用jsonschema与JSON-RPC集成:构建类型安全的RPC服务终极指南
jsonschema与JSON RPC集成:构建类型安全的RPC服务终极指南 在现代分布式系统中,JSON RPC作为一种轻量级的远程过程调用协议,以其简洁的J
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考