在Go语言中,使用context可以实现请求转发。context包含了请求相关的上下文信息,例如请求的header、method、URL以及请求体等信息。在一些场景下,我们需要把这些信息传递到下层函数中,以便进行一些处理。同时,context还可以用来控制请求的超时和取消。
1. 创建和使用context
在Go中,创建context可以使用context包中的WithCancel、WithDeadline、WithTimeout、WithValue等方法。其中,WithCancel和WithDeadline的返回值是一个新的context以及一个cancel函数,可以使用cancel函数来取消context;WithTimeout返回值也是一个新的context以及一个cancel函数,不过在超时后会自动调用cancel函数;WithValue则是返回一个包含键值对的context,可以通过context.Value方法来获取value值。
下面的示例展示了如何使用context传递参数:
func handler(ctx context.Context, name string) {
select {
case <-ctx.Done():
fmt.Println("context canceled")
return
default:
fmt.Println("request handler", name)
time.Sleep(2 * time.Second)
fmt.Println("request done")
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Duration(3)*time.Second)
defer cancel()
go handler(ctx, "foo")
time.Sleep(1 * time.Second)
fmt.Println("main done")
}
在上面的例子中,我们仅仅是向handler函数中传递了一个context参数,当上层的context被取消或者超时后,该context参数中的操作也会自动取消。在handler函数中,我们使用select语句进行多路复用等待context.Done()。如果context被取消或者超时,则select语句会执行case <-ctx.Done():分支,否则就会执行default分支,继续执行handler函数的操作。
2. 使用context实现请求转发
下面的代码演示了使用context实现请求“转发”的过程:
func forward(ctx context.Context, url string) ([]byte, error) {
transport := http.DefaultTransport.(*http.Transport).Clone()
client := &http.Client{Transport: transport}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
url := "http://golang.org"
result, err := forward(ctx, url)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("response from", url, ":", string(result))
}
上面的例子中,我们定义了一个名为forward的函数,该函数的作用是请求url所指向的网络地址并返回body信息。在请求时,我们将context信息也一并传递到了request中,以达到在context超时或者取消时,请求也会被取消的效果。
3. Context的超时控制
使用context可以控制请求的超时和取消。在前面的例子中,我们使用WithTimeout方法来控制超时时间,当超过3秒时,该context会自动取消。下面使用一个例子来展示如何使用WithDeadline来控制context的超时,以及如何取消context。
package main
import (
"context"
"fmt"
"time"
)
func main() {
fmt.Println("start")
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Duration(5)*time.Second)
defer cancel()
go func() {
time.Sleep(10 * time.Second)
cancel()
}()
select {
case <-ctx.Done():
fmt.Println("done")
}
fmt.Println("end")
}
在上面的例子中,首先我们调用了context.Background()函数创建了一个默认的context。接着,我们使用WithTimeout方法创建了一个具有5秒超时时间的context,这个context会在超过5秒后自动被取消。在main函数中,我们使用了select来等待ctx.Done()的信号,一旦ctx被取消或者超时后,程序就会自动退出。同时,在另外一个goroutine中,我们也使用了cancel函数来立即取消ctx的执行,这个操作可以让select的分支立刻执行。
4. Context的value传递
除了可以控制请求的超时和取消之外,context还可以用来传递请求相关的value。在Golang中,我们通常使用context来传递一些请求相关的元数据,例如请求ID、请求状态等。
在下面的例子中,我们演示了如何使用context传递请求ID:
package main
import (
"context"
"fmt"
)
func printMsgWithReqID(ctx context.Context, msg string) {
reqID, ok := ctx.Value("reqID").(string)
if !ok {
reqID = "unknown"
}
fmt.Printf("[%v] %v\n", reqID, msg)
}
func main() {
ctx := context.WithValue(context.Background(), "reqID", "12345")
printMsgWithReqID(ctx, "hello world")
}
在上面的例子中,我们演示了如何使用WithValue方法创建一个包含"reqID"键值的context。接着,我们在printMsgWithReqID函数中使用了ctx.Value方法获取"reqID"的值,并在打印函数日志时打印出了请求ID。如果请求ID不存在,则会打印"unknown"字符串。
结论:
在本文中,我们学习了如何在Go中使用context包来控制请求的超时和取消,以及如何使用context传递请求相关的value。通过本文的学习,我们可以更好地理解context在Go语言中的用途和使用方法。在代码实现中,我们需要注意在程序中正确地设置context的id和timeout参数,避免出现一些异常情况,例如context在代码运行的过程中因为资源限制而被取消。同时,在实现中我们还需要注意关注对context的异常处理和退出逻辑,以确保程序的正确性和可靠性。