context使用的高级示例

使用 useContext.go 程序代码将更好,更深入的说明 context 包的功能,该代码分为五部分来介绍。

这个例子中,您将创建一个快速响应的 HTTP 客户端,这是一个很常见的需求。事实上,几乎所有的 HTTP 客户端都支持这个功能,您将学习另一个 HTTP 请求超时的技巧,在第12章(Go网络编程基础)。

useContext.go 程序需要两个命令行参数:要连接的服务器 URL 和应该等待的延迟。如果该程序只有一个命名行参数,那么延迟将是 5 秒。

useContext.go 的第一段代码如下:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "os"
  8. "strconv"
  9. "sync"
  10. "time"
  11. )
  12. var (
  13. myUrl string
  14. delay int = 5
  15. w sync.WaitGroup
  16. )
  17. type myData struct {
  18. r *http.Response
  19. err error
  20. }

myUrldelay 都是全局变量,因此它们能从代码的任意位置访问。另外,有一个名为 wsync..WaitGroup 变量也是全局的,还有一个名为 myData 的结构体用于把 web 服务器的响应和一个错误变量绑在一起。

useContext.go 的第二部分代码如下:

  1. func connect(c context.Context) error {
  2. defer w.Done()
  3. data := make(chan myData, 1)
  4. tr := &http.Transport{}
  5. httpClient := &http.Client{Transport: tr}
  6. req, _ := http.NewRequest("GET", myUrl, nil)

上面的代码处理 HTTP 连接。

您会了解更多关于开发 web 服务器和客户端的内容,在第12章(Go网络编程基础)。

useContext.go 的第三段代码如下:

  1. go func() {
  2. response, err := httpClient.Do(req)
  3. if err != nil {
  4. fmt.Println(err)
  5. data <- myData{nil, err}
  6. return
  7. } else {
  8. pack := myData{response, err}
  9. data <- pack
  10. }
  11. }()

useContext.go 的第四段代码如下:

  1. select {
  2. case <-c.Done():
  3. tr.CancelRequest(req)
  4. <-data
  5. fmt.Println("The request was cancelled!")
  6. return c.Err()
  7. case ok := <-data:
  8. err := ok.err
  9. resp := ok.r
  10. if err != nil {
  11. fmt.Println("Error select:", err)
  12. return err
  13. }
  14. defer resp.Body.Close()
  15. realHTTPData, err := ioutil.ReadAll(resp.Body)
  16. if err != nil {
  17. fmt.Println("Error select:", err)
  18. return err
  19. }
  20. fmt.Printf("Server Response: %s\n", realHTTPData)
  21. }
  22. return nil
  23. }

useContext.go 的其余代码实现了 main() 函数,如下:

  1. func main() {
  2. if len(os.Args) == 1 {
  3. fmt.Println("Need a URL and a delay!")
  4. return
  5. }
  6. myUrl = os.Args[1]
  7. if len(os.Args) == 3 {
  8. t. err := strconv.Atoi(os.Args[2])
  9. if err != nil {
  10. fmt.Println(err)
  11. return
  12. }
  13. delay = t
  14. }
  15. fmt.Println("Delay:", delay)
  16. c := context.Background()
  17. c, cancel := context.WithTimeout(c, time.Duration(delay)*time.Second)
  18. defer cancel()
  19. fmt.Printf("Connecting to %s \n", myUrl)
  20. w.Add(1)
  21. go connect(c)
  22. w.Wait()
  23. fmt.Println("Exiting...")
  24. }

context.WithTimeout() 方法定义了超时期限。connect() 函数作为一个 goroutine 执行,将会正常终止或 cancel() 函数执行时终止。

尽管没必要知道服务端的操作,但看一下 Go 版本的随机减速的 web 服务器也是好的;一个随机数生成器决定您的 web 服务器有多慢。slowWWW.go 的源码内容如下:

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "net/http"
  6. "os"
  7. "time"
  8. )
  9. func random(min, max int) int {
  10. return rand.Intn(max-min) + min
  11. }
  12. func myHandler(w http.ResponseWriter, r *http.Request) {
  13. delay := random(0, 15)
  14. time.Sleep(time.Duration(delay) * time.Second)
  15. fmt.Fprintf(w, "Servring: %s\n", r.URL.Path)
  16. fmt.Fprintf(w, "Dealy: %d\n", delay)
  17. fmt.Printf("Served: %s\n", r.Host)
  18. }
  19. func main() {
  20. seed := time.Now().Unix()
  21. rand.Seed(seed)
  22. PORT := ":8001"
  23. arguments := os.Args
  24. if len(arguments) == 1 {
  25. fmt.Println("Using default port nubmer: ", PORT)
  26. } else {
  27. PORT = ":" + arguments[1]
  28. }
  29. http.HandleFunc("/", myHandler)
  30. err := http.ListenAndServer(PORT, nil)
  31. if err != nil {
  32. fmt.Println(err)
  33. os.Exit(10)
  34. }
  35. }

如您所见,您不需要在 slowWWW.go 文件中使用 context 包,因为那是 web 客户端的工作,它会决定需要多长时间等待响应。

myHandler() 函数的代码负责 web 服务器的减速处理。如介绍的那样,延迟可以由 random(0,15) 函数在 0 到 14 秒随机产生。

如果您使用如 wget(1) 的工具访问 slowWWW.go 服务器的话,会收到如下输出:

  1. $wget -qO- http://localhost:8001/
  2. Serving: /
  3. Delay: 4
  4. $wget -qO- http://localhost:8001/
  5. Serving: /
  6. Delay: 13

这是因为 wget(1) 的默认超时时间比较大。

slowWWW.go 已经在另一个 Unix shell 里运行后,用方便的 time(1) 工具处理执行 useContext.go 的输出如下:

""

这个输出显示只有第三个命令确实从 HTTP 服务器获得了响应——第一和第二个命令都超时了!