Dubbo-go 提供了两种在 RPC 请求和响应 body 之外传递 metadata 的方式:
context 的 constant.AttachmentKey 下,常用于 filter、路由、链路追踪或业务代码传递请求级 key-value 数据。对于 Triple 调用,请求 attachment 会以 HTTP metadata header 的形式传输;对于 Dubbo 协议,attachment 会编码在协议体的固定位置。http.Header 的形式读写,适合应用代码直接处理请求 metadata 或响应 header/trailer。示例源码:
Attachment 传递流程:
┌───── Consumer side ──────────┐
│ │
│ ┌──────────────────────┐ │
│ │ context.Context │ │
│ │ AttachmentKey │ │
│ │ map[string]any │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ RPCInvocation │ │
│ │ Attachments() │ │
│ └──────────┬───────────┘ │
│ │ │
└─────────────┼────────────────┘
│
▼
┌────────────────┐
│ Protocol │
│ metadata │
│ │
│ Triple: header │
│ Dubbo : body │
└───────┬────────┘
│
│ network
▼
┌──────────────┼─── Provider side ────┐
│ ▼ │
│ ┌────────────────┐ │
│ │ Protocol │ │
│ │ metadata │ │
│ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ RPCInvocation │ │
│ │ Attachments() │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ context.Context │ │
│ │ AttachmentKey │ │
│ │ map[string]any │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Filter / Service │ │
│ │ implementation │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────┘
string 或 []string。http.Header 方式使用 Get 和 Values。本文档先介绍如何通过 context 传递请求 attachment,然后介绍如何使用 Triple 请求 metadata、响应 header 和响应 trailer。
在客户端中,可以通过 constant.AttachmentKey 传递字段,即 "attachment":
ctx := context.Background()
ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
"key1": "user defined value 1",
"key2": "user defined value 2",
})
在服务端中,可以从 constant.AttachmentKey 读取字段。对于 Triple 请求,header value 会以 []string 的形式保存:
attachments := ctx.Value(constant.AttachmentKey).(map[string]interface{})
if value1, ok := attachments["key1"]; ok {
logger.Infof("Dubbo attachment key1 = %s", value1.([]string)[0])
}
if value2, ok := attachments["key2"]; ok {
logger.Infof("Dubbo attachment key2 = %s", value2.([]string)[0])
}
源文件路径:dubbo-go-sample/context/proto/greet.proto
syntax = "proto3";
package greet;
option go_package = "github.com/apache/dubbo-go-samples/context/proto;greet";
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
源文件路径:dubbo-go-sample/context/go-server/main.go
package main
import (
"context"
"dubbo.apache.org/dubbo-go/v3/common/constant"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"dubbo.apache.org/dubbo-go/v3/protocol"
"dubbo.apache.org/dubbo-go/v3/server"
greet "github.com/apache/dubbo-go-samples/context/proto"
"github.com/dubbogo/gost/log/logger"
)
type GreetTripleServer struct {
}
func (srv *GreetTripleServer) Greet(ctx context.Context, req *greet.GreetRequest) (*greet.GreetResponse, error) {
attachments := ctx.Value(constant.AttachmentKey).(map[string]interface{})
if value1, ok := attachments["key1"]; ok {
logger.Infof("Dubbo attachment key1 = %s", value1.([]string)[0])
}
if value2, ok := attachments["key2"]; ok {
logger.Infof("Dubbo attachment key2 = %s", value2.([]string)[0])
}
resp := &greet.GreetResponse{Greeting: req.Name}
return resp, nil
}
func main() {
srv, err := server.NewServer(
server.WithServerProtocol(
protocol.WithPort(20000),
protocol.WithTriple(),
),
)
if err != nil {
panic(err)
}
if err := greet.RegisterGreetServiceHandler(srv, &GreetTripleServer{}); err != nil {
panic(err)
}
if err := srv.Serve(); err != nil {
logger.Error(err)
}
}
客户端client文件,创建客户端,在context写入变量,发起调用并打印结果
源文件路径:dubbo-go-sample/context/go-client/main.go
package main
import (
"context"
"dubbo.apache.org/dubbo-go/v3/client"
"dubbo.apache.org/dubbo-go/v3/common/constant"
_ "dubbo.apache.org/dubbo-go/v3/imports"
greet "github.com/apache/dubbo-go-samples/context/proto"
"github.com/dubbogo/gost/log/logger"
)
func main() {
cli, err := client.NewClient(
client.WithClientURL("127.0.0.1:20000"),
)
if err != nil {
panic(err)
}
svc, err := greet.NewGreetService(cli)
if err != nil {
panic(err)
}
ctx := context.Background()
ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
"key1": "user defined value 1",
"key2": "user defined value 2",
})
resp, err := svc.Greet(ctx, &greet.GreetRequest{Name: "hello world"})
if err != nil {
logger.Error(err)
}
logger.Infof("Greet response: %s", resp.Greeting)
}
先启动服务端,再启动客户端,可以观察到服务端打印了客户端通过context传递的参数值,说明参数被成功传递并获取
2024-02-26 11:13:14 INFO logger/logging.go:42 Dubbo attachment key1 = [user defined value 1]
2024-02-26 11:13:14 INFO logger/logging.go:42 Dubbo attachment key2 = [user defined value 2]
对于 Triple 调用,Dubbo-go 也会通过 http.Header 向应用层暴露请求和响应 metadata。metadata key 语义上大小写不敏感,应用代码可以按标准 http.Header 方式使用 Get 和 Values 读取。
客户端可以在发起调用前写入请求 metadata:
import (
"context"
"net/http"
triple "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
)
ctx := triple.NewOutgoingContext(context.Background(), http.Header{
"X-Sample-Token": []string{"token"},
})
ctx = triple.AppendToOutgoingContext(ctx, "X-Sample-Mode", "metadata")
resp, err := svc.Greet(ctx, &greet.GreetRequest{Name: "hello"})
服务端可以从请求 context 中读取这些 metadata:
headers, ok := triple.FromIncomingContext(ctx)
if ok {
token := headers.Get("X-Sample-Token")
mode := headers.Get("X-Sample-Mode")
_ = token
_ = mode
}
生成的流式接口也可以通过 stream 对象读取请求 metadata:
token := stream.RequestHeader().Get("X-Sample-Token")
对于生成的 Unary 调用,服务端可以通过 triple.SetHeader 和 triple.SetTrailer 写入响应 metadata:
func (srv *GreetTripleServer) Greet(ctx context.Context, req *greet.GreetRequest) (*greet.GreetResponse, error) {
if err := triple.SetHeader(ctx, http.Header{
"X-Response-Token": []string{"value"},
}); err != nil {
return nil, err
}
if err := triple.SetTrailer(ctx, http.Header{
"X-Response-Trailer": []string{"done"},
}); err != nil {
return nil, err
}
return &greet.GreetResponse{Greeting: req.Name}, nil
}
客户端可以通过 call option 捕获本次调用的响应 header 和 trailer:
var responseHeader http.Header
var responseTrailer http.Header
resp, err := svc.Greet(
ctx,
&greet.GreetRequest{Name: "hello"},
client.WithResponseHeader(&responseHeader),
client.WithResponseTrailer(&responseTrailer),
)
if err != nil {
return err
}
headerValue := responseHeader.Get("X-Response-Token")
trailerValue := responseTrailer.Get("X-Response-Trailer")
_ = resp
_ = headerValue
_ = trailerValue
对于生成的流式接口,响应 metadata 暴露在 stream 对象上。服务端通过 ResponseHeader 和 ResponseTrailer 写入响应 header 和 trailer:
func (srv *GreetTripleServer) GreetStream(stream greet.GreetService_GreetStreamServer) error {
stream.ResponseHeader().Set("X-Stream-Response", "value")
stream.ResponseTrailer().Set("X-Stream-Trailer", "done")
// 处理 stream 消息
return nil
}
客户端可以从生成的 stream 对象读取响应 metadata:
stream, err := svc.GreetStream(ctx)
if err != nil {
return err
}
headerValue := stream.ResponseHeader().Get("X-Stream-Response")
trailerValue := stream.ResponseTrailer().Get("X-Stream-Trailer")
_ = headerValue
_ = trailerValue
完整可运行示例可参考 dubbo-go-samples/triple_header_trailer。