Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
202 lines (160 sloc) 5.55 KB

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
}

myUrldelay 都是全局变量,因此它们能从代码的任意位置访问。另外,有一个名为 wsync..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.Println("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 := contedxt.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 服务器获得了响应——第一和第二个命令都超时了!

You can’t perform that action at this time.