context使用的高级示例
使用 useContext.go
程序代码将更好,更深入的说明 context
包的功能,该代码分为五部分来介绍。
这个例子中,您将创建一个快速响应的 HTTP 客户端,这是一个很常见的需求。事实上,几乎所有的 HTTP 客户端都支持这个功能,您将学习另一个 HTTP 请求超时的技巧,在第12章(Go网络编程基础)。
useContext.go
程序需要两个命令行参数:要连接的服务器 URL 和应该等待的延迟。如果该程序只有一个命名行参数,那么延迟将是 5 秒。
useContext.go
的第一段代码如下:
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"sync"
"time"
)
var (
myUrl string
delay int = 5
w sync.WaitGroup
)
type myData struct {
r *http.Response
err error
}
myUrl
和 delay
都是全局变量,因此它们能从代码的任意位置访问。另外,有一个名为 w
的 sync..WaitGroup
变量也是全局的,还有一个名为 myData
的结构体用于把 web 服务器的响应和一个错误变量绑在一起。
useContext.go
的第二部分代码如下:
func connect(c context.Context) error {
defer w.Done()
data := make(chan myData, 1)
tr := &http.Transport{}
httpClient := &http.Client{Transport: tr}
req, _ := http.NewRequest("GET", myUrl, nil)
上面的代码处理 HTTP 连接。
您会了解更多关于开发 web 服务器和客户端的内容,在第12章(Go网络编程基础)。
useContext.go
的第三段代码如下:
go func() {
response, err := httpClient.Do(req)
if err != nil {
fmt.Println(err)
data <- myData{nil, err}
return
} else {
pack := myData{response, err}
data <- pack
}
}()
useContext.go
的第四段代码如下:
select {
case <-c.Done():
tr.CancelRequest(req)
<-data
fmt.Println("The request was cancelled!")
return c.Err()
case ok := <-data:
err := ok.err
resp := ok.r
if err != nil {
fmt.Println("Error select:", err)
return err
}
defer resp.Body.Close()
realHTTPData, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error select:", err)
return err
}
fmt.Printf("Server Response: %s\n", realHTTPData)
}
return nil
}
useContext.go
的其余代码实现了 main()
函数,如下:
func main() {
if len(os.Args) == 1 {
fmt.Println("Need a URL and a delay!")
return
}
myUrl = os.Args[1]
if len(os.Args) == 3 {
t. err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Println(err)
return
}
delay = t
}
fmt.Println("Delay:", delay)
c := context.Background()
c, cancel := context.WithTimeout(c, time.Duration(delay)*time.Second)
defer cancel()
fmt.Printf("Connecting to %s \n", myUrl)
w.Add(1)
go connect(c)
w.Wait()
fmt.Println("Exiting...")
}
context.WithTimeout()
方法定义了超时期限。connect()
函数作为一个 goroutine 执行,将会正常终止或 cancel()
函数执行时终止。
尽管没必要知道服务端的操作,但看一下 Go 版本的随机减速的 web 服务器也是好的;一个随机数生成器决定您的 web 服务器有多慢。slowWWW.go
的源码内容如下:
package main
import (
"fmt"
"math/rand"
"net/http"
"os"
"time"
)
func random(min, max int) int {
return rand.Intn(max-min) + min
}
func myHandler(w http.ResponseWriter, r *http.Request) {
delay := random(0, 15)
time.Sleep(time.Duration(delay) * time.Second)
fmt.Fprintf(w, "Servring: %s\n", r.URL.Path)
fmt.Fprintf(w, "Dealy: %d\n", delay)
fmt.Printf("Served: %s\n", r.Host)
}
func main() {
seed := time.Now().Unix()
rand.Seed(seed)
PORT := ":8001"
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Using default port nubmer: ", PORT)
} else {
PORT = ":" + arguments[1]
}
http.HandleFunc("/", myHandler)
err := http.ListenAndServer(PORT, nil)
if err != nil {
fmt.Println(err)
os.Exit(10)
}
}
如您所见,您不需要在 slowWWW.go
文件中使用 context
包,因为那是 web 客户端的工作,它会决定需要多长时间等待响应。
myHandler()
函数的代码负责 web 服务器的减速处理。如介绍的那样,延迟可以由 random(0,15)
函数在 0 到 14 秒随机产生。
如果您使用如 wget(1)
的工具访问 slowWWW.go
服务器的话,会收到如下输出:
$wget -qO- http://localhost:8001/
Serving: /
Delay: 4
$wget -qO- http://localhost:8001/
Serving: /
Delay: 13
这是因为 wget(1)
的默认超时时间比较大。
当 slowWWW.go
已经在另一个 Unix shell 里运行后,用方便的 time(1)
工具处理执行 useContext.go
的输出如下:
这个输出显示只有第三个命令确实从 HTTP 服务器获得了响应——第一和第二个命令都超时了!