net/http/#Transport.CancelRequest
func (t *Transport) CancelRequest(req *Request)
CancelRequest cancels an in-flight request by closing its connection. CancelRequest should only be called after RoundTrip has returned.
Deprecated: Use Request.Cancel instead. CancelRequest can not cancel HTTP/2 requests.
久しぶりにガッツリGoを書いていて気づいたのですが、Transport.CancelRequest()メソッドがDeprecatedになっていました。代わりにRequest.Cancelを使えとのこと。Request.CancelはGo1.5で追加されたらしい1。
type Request struct {
// Cancel is an optional channel whose closure indicates that the client
// request should be regarded as canceled. Not all implementations of
// RoundTripper may support Cancel.
//
// For server requests, this field is not applicable.
Cancel <-chan struct{}
}
Request構造体のドキュメントを見てみると確かにCancelフィールドが追加されています。Request.Cancelにchan struct{}
型のチャネルを代入しておいて、そのチャネルに送信するか閉じるかすればリクエストがキャンセルされるということのようです。
package main
import (
"log"
"net/http"
)
func main() {
done := make(chan struct{})
req, _ := http.NewRequest("GET", "http://www.google.com/", nil)
req.Cancel = done
go func() {
close(done)
}()
_, err := http.DefaultClient.Do(req)
log.Println(err) # Get http://www.google.com/: net/http: request canceled while waiting for connection
}
以前だとRequestをキャンセルするのに一々Client.Transportへの参照が必要だったのですが、その必要がなくなったおかげでClientのモックがやりやすくなりました。ありがたいです。
さて、このRequest.Cancelですが一つ面白い使い方があります。Go 1.5 Release Notesにもあるとおり、Request.Cancelフィールドの型はgolang.org/x/net/contextのContext.Done()の戻り値と互換性があります。つまりこのようなことができます。
package main
import (
"log"
"net/http"
"time"
"golang.org/x/net/context"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()
req, _ := http.NewRequest("GET", "http://www.google.com/", nil)
req.Cancel = ctx.Done()
_, err := http.DefaultClient.Do(req)
log.Println(err) # Get http://www.google.com/: net/http: request canceled while waiting for connection
}
便利ですね。
ちなみに、Go1.7ではgolang.org/x/net/contextが標準パッケージに組み込まれるそうです2。