写给Go开发者的Tars教程-context/status

2024-01-09
3分钟阅读时长

本篇为【写给go开发者的Tars教程】系列第五篇

第一篇:Tars协议基础

第二篇:通信模式

第三篇:拦截器

第四篇:错误处理

第五篇:context/status

本系列将持续更新,欢迎关注👏获取实时通知


导语

和在普通HTTP请求中一样,TarsGo提供了在每一次RPC中携带上下文的结构:contextstatus。在Go语言中,它与context.Context紧密结合,帮助我们实现服务端与客户端之间互相传递信息。其中status主要用于框架底层传递一些框架特性相关的数据。

什么是 context

TarsGo 的 contextstatus的简单理解,就是 HTTP Header 中的 key-value 对

  • contextstatus 都是以 key-value 的形式存储数据的,其中 key 和 value都是 string 类型。
  • contextstatus 使得 client 和 server 能够为对方提供关于本次调用的一些信息,就像一次HTTP请求的Request HeaderResponse Header一样
  • HTTP Header 的生命周期是一次 HTTP 请求,那么 contextstatus 的生命周期就是一次 RPC 调用

contextstatus 创建

🌲 直接使用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"}

contextstatus 发送和接受

让我们再次回顾下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添加变量或读取变量

contextstatus就是TarsGo中可以传递的上下文信息之一,所以TarsGo的使用方式就是:contextstatus记录到context,从context读取contextstatus出来。

image-20240108000142810

Clinet发送Server接收

client发送contextstatus,那就是把contextstatus通过rpc方法的最后两个参数进行传递 server接收contextstatus,就是从contex.Context中读取contextstatus

Clinet 发送 contextstatus

contextstatus放到最后两个参数如下:

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 接收 contextstatus

contex.Context中获取contextstatus方法如下:

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 发送 contextstatus

contextstatus放到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 接收 contextstatus

从最后两个map[string]string类型的参数中获取contextstatus方法如下:

mcontext := make(map[string]string)
status := make(map[string]string)
order, err := client.GetOrderWithContext(ctx, "1", mcontext, status)
if err != nil {
    panic(err)
}

使用场景

既然我们把contextstatus类比成HTTP Header,那么contextstatus的使用场景也可以借鉴HTTPHeader。如传递用户token进行用户认证,传递trace进行链路追踪等

拦截器中的contextstatus

在拦截器中,我们不但可以获取或修改接收到的contextstatus,甚至还可以截取并修改要发送出去的contextstatus

还记得拦截器如何实现么?如果已经忘了快快回顾一下吧:

🌰 举个例子:

我们在客户端拦截器中从要发送给服务端的contextstatus中读取一个时间戳字段,如果没有则补充这个时间戳字段

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中如何使用contextstatus有了全面的了解,相应在今后开发中可以如鱼得水。

关注公众号获得更多精彩文章

公众号:程序员大兵