什么是context?
Context是Go语言中用来在一系列的API调用之间传递请求范围数据的一种机制,它允许Go工程师在处理每一个请求的过程中,将请求特定的值、请求的元数据以及一些相关的信息有条件的附加到请求的域中,进而实现请求范围的跟踪、限制以及取消操作。
创建context对象
在Go语言中,context是一个接口类型,它包含了如下的两个方法:
Done():当调用Done()方法时,返回一个只读的管道,可以通过这个管道来获取context是否被取消或截止日期已过期的消息。
Err():返回一个error类型,表明context失败的原因。
我们可以通过三种方式创建context对象:
Background():Background返回一个空的Context,这个Context在任何父级Context被取消或超时之前,它的Done管道永远不会被关闭。
TODO():TODO返回一个非nil的可空Context,用于在函数中作为占位符,表示此处需要context,但此时程序无法创建一个具体的上下文。
WithValue():WithValue创建一个带有key-value对的新的context,这些key-value对是不可变的、线程安全的。
示例:
package main
import (
"context"
"fmt"
)
func main() {
// 创建一个简单的context对象
ctx := context.Background()
// 创建一个占位符context对象
ctx2 := context.TODO()
// 创建一个带有键值对的context对象
ctx3 := context.WithValue(ctx, "request_id", 123)
fmt.Println(ctx) // 返回:context.Background
fmt.Println(ctx2) // 返回:context.TODO
fmt.Println(ctx3) // 返回:context.WithValue(parent context.Context, key interface{}, val interface{})
}
使用context传递请求参数
在一些大型的程序中,我们经常需要将一些处理器之间的请求参数跨越多个函数进行传递,为了解决这些问题,Go语言中引入了context机制,可以让我们在处理器之间链式地传递请求参数,这样我们就可以确保每一个处理器都可以获取到下一个处理器所需要的请求参数,而不必在每一个处理器之间手动传递。
在使用context传递请求参数之前,我们需要从http.Request中获取到context对象,并在处理器中新增context参数,示例如下:
func requestHandler(w http.ResponseWriter, r *http.Request) {
// 从http.Request对象中获取到context.Context对象
ctx := r.Context()
// 为处理器增加一个context参数
handlerFunc(ctx, w, r)
}
在上述示例中,我们将http.Request对象中的context对象通过r.Context()获取到,并将该context对象传入到handlerFunc()函数中作为其第一个参数,这样在处理器handlerFunc()中就可以使用该context对象,示例如下:
func handlerFunc(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// 获取请求ID
requestId := ctx.Value("request_id")
fmt.Fprintf(w, "Request ID: %v", requestId)
}
在上述示例中,我们使用ctx.Value("request_id")函数从context对象中获取到了之前添加进去的request_id键值对的值,并将其输出到了http.ResponseWriter对象中。这样,我们就可以在处理器之间轻松的传递请求参数了。
使用context实现请求取消
context机制的另一个用途是请求范围的取消,当我们需要在发生某些错误时,取消请求范围内的所有操作,可以通过context的机制轻松的实现这一功能。
在Go语言的context中,context对象可以通过WithCancel()方法实现请求的取消。该方法返回一个带有Done()和Cancel()方法的派生context,当调用Cancel()方法时,该派生context的Done()方法将被关闭,并且可以通过select语句接收该Done()管道的消息,如下所示:
func requestHandler(w http.ResponseWriter, r *http.Request) {
// 从http.Request对象中获取到context.Context对象
ctx := r.Context()
// 创建一个子context,并定时取消它
subContext, cancel := context.WithCancel(ctx)
go func() {
time.Sleep(time.Second * 10)
cancel()
}()
select {
case <-subContext.Done():
fmt.Fprintf(w, "Request is cancelled")
return
default:
}
// 处理请求
handlerFunc(subContext, w, r)
}
在上述示例中,我们使用了context.WithCancel()方法获取到一个派生的子context并在其中开启了一个goroutine,该goroutine会在10秒之后调用context的Cancel()方法,从而取消该subContext的请求范围操作。在http.Request请求处理函数中,我们通过select语句监听Done()管道,以检测该请求是否已取消,如果已取消,则我们在http.ResponseWriter对象中返回一个包含请求取消信息的消息。
使用context实现请求超时
context机制除了能够实现请求取消之外,还可以通过withTimeout()方法实现请求超时,即能在规定的时间内结束一个处理器的操作,并让该处理器快速返回错误信息。
在Go语言的context中,我们可以调用withTimeout()方法来实现请求的超时,该方法返回一个附带超时时间的子context和一个取消函数。子context的Done()管道在超时时间内可以被关闭,我们可以在select语句中等待Done()管道并处理返回信息,示例如下:
func requestHandler(w http.ResponseWriter, r *http.Request) {
// 从http.Request对象中获取到context.Context对象
ctx := r.Context()
// 设置请求超时时间5秒钟
subContext, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
select {
case <-subContext.Done():
fmt.Fprintf(w, "Request is timeouted")
return
default:
}
// 处理请求
handlerFunc(subContext, w, r)
}
在上述示例中,我们使用了context.WithTimeout()方法获取到了一个附带5秒超时时间的子context,并在其中通过select语句监听Done()管道,以判断该子context是否超时,如果超时,则在http.ResponseWriter对象中返回一个包含请求超时信息的消息。
总结
我们在本文中简单介绍了,如何在Go语言中使用context机制来传递请求参数、实现请求取消和超时,context机制是Go语言中非常重要的一部分,熟练使用context机制可以为我们编写高效的Go语言应用程序提供帮助。