Go Context


原文链接: Go Context

Go context

参考文章列表:

  1. golang服务器开发利器 context用法详解
  2. Go语言并发模型:使用 context
  3. 使用Golang的Context管理上下文
    Golang Context | King`s博客
    快速掌握 Golang context 包,简单示例 | Deepzz's Blog
    go context包源码分析 - 简书

    阅读前提

  4. refer 1中,本文主要基于官方文档Go Concurrency Patterns: Context以及视频(Advanced Go Concurrency Patterns)[https://www.youtube.com/watch?v=QDDwwePbDtw]的学习而得。

  5. refer 2 中涉及: 本文内容涉及到了 done channel,如果你不了解这个概念,那么请先阅读 "Go语言并发模型:像Unix Pipe那样使用channel"。 Go语言并发模型:像Unix Pipe那样使用channel

使用场景

  1. HTTP 中用在传递 数据库连接 或者 session

context.WithValue should only be used when actually necessary and shouldn't just be used to pass anything through http handlers, especially things such as database connections or session values, etc.

context 上下文:


context不仅可以控制并发逻辑,而且本身也可以携带变量,类似于Map。并且提供Value方法用于获取指定Key的Value值.
context包可以轻松地将 请求范围的值,取消信号 以及 截止时间 跨越API边界传递到处理请求所涉及的所有参数
Context之间是具有父子关系的,新的Context往往从已有的Context中创建,因此,最终所有的context组成一颗树状的结构.
context包中提供一个创建初始Context的方法: Context.Backgraund()就是所有context树的根。
WithCancel和WithTimeout两个方法用于在已有的context上创建新的context,同时从新的context中可以获取到旧的context中保存的Key,Value

context可以被多个并发的Go routine使用,对context的访问是并发安全的

c.Abort()只是设置了一些内部标志,标志着上下文以某种方式表明异常终止。 但是在后续中间件中可以根据Isabort() 判断,从而进行 C.Json(500,"") 返回一些error信息

gin 从panic中恢复: 加middleware避免的panic导致的进程中止 RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.

HTTP Context

Go 1.7 添加了 context 包,用于传递数据和做超时、取消等处理。*http.Request 添加了 r.Context()r.WithContext() 来操作请求过程需要的 context.Context 对象。

传递数据

context 可以在 http.HandleFunc 之间传递数据:

func handle1(w http.ResponseWriter, r *http.Request) {
	ctx := context.WithValue(r.Context(), "abc", "xyz123") // 写入 string 到 req.context
	handle2(w, r.WithContext(ctx))                         // 传递给下一个 handleFunc
}

func handle2(w http.ResponseWriter, r *http.Request) {
	str, ok := r.Context().Value("abc").(string) // 取出的 interface 需要推断到 string
	if !ok {
		str = "not string"
	}
	w.Write([]byte("context.abc = " + str))
}

func main() {
	http.HandleFunc("/", handle1)
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}
处理超时的请求

利用 context.WithTimeout 可以创建会超时结束的 context,用来处理业务超时的情况:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	ctx, cancelFn := context.WithTimeout(r.Context(), 1*time.Second)

	// cancelFn 关掉 WithTimeout 里的计时器
	// 如果 ctx 超时,计时器会自动关闭,但是如果没有超时就执行到 <-resCh,就需要手动关掉
	defer cancelFn()

	// 把业务放到 goroutine 执行, resCh 获取结果
	resCh := make(chan string, 1)
	go func() {
        // 故意写业务超时
		time.Sleep(5 * time.Second)
		resCh <- r.FormValue("abc")
	}()

	// 看 ctx 超时还是 resCh 的结果先到达
	select {
	case <-ctx.Done():
		w.WriteHeader(http.StatusGatewayTimeout)
		w.Write([]byte("http handle is timeout:" + ctx.Err().Error()))
	case r := <-resCh:
		w.Write([]byte("get: abc = " + r))
	}
}
带 context 的中间件

Go 的很多 HTTP 框架使用 context 或者自己定义的 Context 结果作为 http.Handler 中间件之间数据传递的媒介,比如 xhandler:

import(
	"context"
	"github.com/rs/xhandler"
	
)

type myMiddleware struct {
    next xhandler.HandlerC
}

func (h myMiddleware) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    ctx = context.WithValue(ctx, "test", "World")
    h.next.ServeHTTPC(ctx, w, r)
}

func main() {
    c := xhandler.Chain{}
	c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC {
        return myMiddleware{next: next}
    })
	xh := xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
        value := ctx.Value("test").(string) // 使用 context 传递的数据
        w.Write([]byte("Hello " + value))
    })
	http.Handle("/", c.Handler(xh)) // 将 xhandler.Handler 转化为 http.Handler
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

xhandler 封装 ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) 用于类似 http.HandlerServeHTTP(w http.ResponseWriter, r *http.Request) 的行为,处理 HTTP 的过程。

`