写给Go开发者的Tars教程-context/status
本篇为【写给go开发者的Tars教程】系列第五篇
第一篇:Tars协议基础
第二篇:通信模式
第三篇:拦截器
第四篇:错误处理
第五篇:context/status
本系列将持续更新,欢迎关注👏获取实时通知
导语
和在普通HTTP
请求中一样,TarsGo提供了在每一次RPC中携带上下文的结构:context
和status
。在Go语言中,它与context.Context
紧密结合,帮助我们实现服务端与客户端之间互相传递信息。其中status
主要用于框架底层传递一些框架特性相关的数据。
什么是 context
?
TarsGo 的 context
和status
的简单理解,就是 HTTP Header
中的 key-value 对
context
和status
都是以key-value
的形式存储数据的,其中 key 和 value都是 string 类型。context
和status
使得 client 和 server 能够为对方提供关于本次调用的一些信息,就像一次HTTP请求的Request Header
和Response Header
一样HTTP Header
的生命周期是一次 HTTP 请求,那么context
和status
的生命周期就是一次 RPC 调用
context
和status
创建
🌲 直接使用make即可:
context := make(map[string]string)
status := make(map[string]string)
🌲 直接使用map初始化方式:
context := map[string]string{"key1":"value1","key2":"value2"}
status := map[string]string{"key1":"value1","key2":"value2"}
context
和status
发送和接受
让我们再次回顾下tars文件和生成出来的client与server端的接口
module order {
struct Order {
1 require string id;
2 optional vector<string> items;
3 optional string description;
4 require float price;
5 optional string destination;
};
interface OrderManagement {
Order getOrder(string orderId);
};
};
type OrderManagement struct {
servant m.Servant
}
func (obj *OrderManagement) GetOrder(orderId string, opts ...map[string]string) (Order, error) {
return obj.GetOrderWithContext(context.Background(), orderId, opts...)
}
func (obj *OrderManagement) GetOrderWithContext(tarsCtx context.Context, orderId string, opts ...map[string]string) (ret Order, err error) {
// ......
return ret, nil
}
func (obj *OrderManagement) GetOrderOneWayWithContext(tarsCtx context.Context, orderId string, opts ...map[string]string) (ret Order, err error) {
// ......
return ret, nil
}
type OrderManagementServant interface {
GetOrder(orderId string) (ret Order, err error)
}
type OrderManagementServantWithContext interface {
GetOrder(tarsCtx context.Context, orderId string) (ret Order, err error)
}
可以看到相比tars中的接口定义,生成出来的Go代码除了增加了error
返回值,还多了context.Context
调用参数。
和错误处理类似,TarsGo 中的context.Context
也符合Go语言的使用习惯:通常情况下我们在函数首个参数放置context.Context
用来传递一次RPC中有关的上下文,借助context.WithValue()
或ctx.Value()
往context
添加变量或读取变量
context
和status
就是TarsGo中可以传递的上下文信息之一,所以TarsGo的使用方式就是:context
和status
记录到context
,从context
读取context
和status
出来。
Clinet发送Server接收
client
发送context
和status
,那就是把context
和status
通过rpc方法的最后两个参数进行传递
server
接收context
和status
,就是从contex.Context
中读取context
和status
Clinet 发送 context
和status
把context
和status
放到最后两个参数如下:
ctx := current.ContextWithTarsCurrent(context.Background())
ctx = current.ContextWithClientCurrent(ctx)
mcontext := map[string]string{"client-key1":"client-value1","client-key2":"client-value2"}
status := map[string]string{"client-key1":"client-value1","client-key2":"client-value2"}
order, err := client.GetOrderWithContext(ctx, "1", mcontext, status)
if err != nil {
panic(err)
}
Server 接收 context
和status
从contex.Context
中获取context
和status
方法如下:
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
mcontext, mok := current.GetRequestContext(tarsCtx) // get context
status, sok := current.GetRequestStatus(tarsCtx) // get status
// do something with context and status
// ...
}
Server发送Clinet接收
Server 发送 context
和status
把context
和status
放到contex.Context
的方法如下:
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
m := map[string]string{"server-key1": "server-value1", "server-key2": "server-value2"}
s := map[string]string{"server-key1": "server-value1", "server-key2": "server-value2"}
current.SetResponseContext(tarsCtx, m) // set context
current.SetResponseStatus(tarsCtx, s) // set status
// ...
}
Client 接收 context
和status
从最后两个map[string]string
类型的参数中获取context
和status
方法如下:
mcontext := make(map[string]string)
status := make(map[string]string)
order, err := client.GetOrderWithContext(ctx, "1", mcontext, status)
if err != nil {
panic(err)
}
使用场景
既然我们把context
和status
类比成HTTP Header
,那么context
和status
的使用场景也可以借鉴HTTP
的Header
。如传递用户token
进行用户认证,传递trace
进行链路追踪等
拦截器中的context
和status
在拦截器中,我们不但可以获取或修改接收到的context
和status
,甚至还可以截取并修改要发送出去的context
和status
还记得拦截器如何实现么?如果已经忘了快快回顾一下吧:
🌰 举个例子:
我们在客户端拦截器中从要发送给服务端的context
和status
中读取一个时间戳字段,如果没有则补充这个时间戳字段
func orderClientFilter(next tars.ClientFilter) tars.ClientFilter {
return func(ctx context.Context, msg *tars.Message, invoke tars.Invoke, timeout time.Duration) (err error) {
if msg.Req.Context == nil {
msg.Req.Context = make(map[string]string)
}
var (
s string
ok bool
)
if s, ok = msg.Req.Context["time"]; !ok {
s = "inter" + strconv.FormatInt(time.Now().UnixNano(), 10)
msg.Req.Context["time"] = s
}
log.Printf("call timestamp: %s", s)
// Invoking the remote method
err = next(ctx, msg, invoke, timeout)
return err
}
}
func main() {
comm := tars.GetCommunicator()
client := new(order.OrderManagement)
obj := "Test.OrderServer.OrderObj@tcp -h 127.0.0.1 -p 8080 -t 60000"
comm.StringToProxy(obj, client)
tars.UseClientFilterMiddleware(orderClientFilter)
ctxTimeCall(client)
}
func ctxTimeCall(client *order.OrderManagement) {
ctx := current.ContextWithTarsCurrent(context.Background())
ctx = current.ContextWithClientCurrent(ctx)
mcontext := map[string]string{
"time": "raw" + strconv.FormatInt(time.Now().UnixNano(), 10),
}
order, err := client.GetOrderWithContext(ctx, "1", mcontext)
if err != nil {
panic(err)
}
fmt.Printf("ctx: %+v\n", order)
}
以上的思路在server同样适用。基于以上原理我们可以实现链路追踪、用户认证等功能
总结
经过上面的讲解详细读者已经对在TarsGo中如何使用context
和status
有了全面的了解,相应在今后开发中可以如鱼得水。