こんにちわ、ワカルのアドベントカレンダー2日目を担当する包です。
最近はGoばかり書いているので、Goネタです。
外部への http アクセスをする時に構造を理解しておくと便利な、http.RoundTripper
について書きます。
http.RoundTripper とは
Go で、外部にhttpアクセスするときには、 net/http パッケージにある、 http.Client
を使います。
また、いろいろなAPIのクライアントライブラリの中でも殆どの場合 http.Client
が使われていて、定義は以下のようになっています。(一部コメント省略)
type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
Transport
というプロパティとして、http.RoundTripper
を持っています。 nil
のとき(デフォルト) には、 http.DefaultTransport
を使います。
ローレベルのHTTP通信の実態は、こいつが担っていることがわかります。
http.RoundTripper
の定義をみてみると
// RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request.
//
// A RoundTripper must be safe for concurrent use by multiple
// goroutines.
type RoundTripper interface {
// RoundTrip executes a single HTTP transaction, returning
// the Response for the request req. RoundTrip should not
// attempt to interpret the response. In particular,
// RoundTrip must return err == nil if it obtained a response,
// regardless of the response's HTTP status code. A non-nil
// err should be reserved for failure to obtain a response.
// Similarly, RoundTrip should not attempt to handle
// higher-level protocol details such as redirects,
// authentication, or cookies.
//
// RoundTrip should not modify the request, except for
// consuming and closing the Body, including on errors. The
// request's URL and Header fields are guaranteed to be
// initialized.
RoundTrip(*Request) (*Response, error)
}
と、 RoundTrip
という1つのメソッドを持つインターフェイスになっています。
コメントを要約すると、
- RoundTripper は、複数のgorutineから呼ばれても大丈夫なようにしておく
- レスポンスを解釈するべきではない
- 高レベルの処理、認証やクッキーの操作などもすべきではない
- (とはいえ、後述の oauth2.Transport など、認証機構をもつものもあるので、ケースバイケースだと思います)
- レスポンスのステータスコードにかかわらず、レスポンスを取得すること自体の失敗のみに err を返しましょう
- リクエストボディの読み込み、クローズ以外には、リクエストをさわらないようにしましょう
ということですかね。
巷の http.RoundTripper の例
http.fileTransport
標準パッケージに入っている、file://
プロトコルを扱うための実装
oauth2.Transport
oauth2 の認証周りを制御する実装。
実際のプロダクトでの使いどころ
現在、ワカルのサービス AIアナリスト では、以下の様な用途で独自の http.RoundTripper
の実装を使っています。
- 外部APIへのRateLimit制御 (秒間リクエスト数や、同時リクエスト数)
- 外部API呼び出しに失敗した時の、リトライ処理 ( Exponential backoff 処理)
- 一部レスポンスのキャッシュ
実装の基本形
一通り概観がつかめたら、自分の RoundTripper
を実装をしてみましょう。何回か実装すると、ベースにするひな形が見えてきます。
以下はの例は、リクエストがされたかログするだけの実装です
package main
import (
"log"
"net/http"
)
type LogTransport struct {
Transport http.RoundTripper
}
func (lt *LogTransport) transport() http.RoundTripper {
if lt.Transport == nil {
return http.DefaultTransport
}
return lt.Transport
}
func (lt *LogTransport) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
if cr, ok := lt.transport().(canceler); ok {
cr.CancelRequest(req)
}
}
func (lt *LogTransport) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := lt.transport().RoundTrip(req)
log.Printf("%d\t%s\t%s\n", res.StatusCode, req.Method, req.URL.String())
return res, err
}
func main() {
client := &http.Client{
Transport: &LogTransport{},
}
if _, err := client.Get("http://goo.gl/hY1H85"); err != nil {
log.Fatal(err)
}
}
実行すると、以下の様なログがでると思います。
2015/12/02 00:38:00 301 GET http://goo.gl/hY1H85
2015/12/02 00:38:01 200 GET http://wacul.co.jp/
一回リダイレクトされて、 http://wacul.co.jp にアクセスしたようです。
抑えておきたいポイントは以下の2つかなと思います。
親の RoundTripper を設定できるようにする
実際のリクエスト処理を行う、親のRoundTripperを設定できるようにしておきましょう。多段に組み合わせて使うことができます。
また、設定されなかった場合には、http.DefaultTransport を使うようにしておきましょう。
CancelRequest
を実装する
http.Client に Timeout を指定した場合などには、Transport の CancelRequest メソッドが実装されているかをチェックし、実装されていればが呼ばれることになっています。
自前でTransport を作る場合、親のTransportが、CancelRequest を実装していればそれを呼び出すように実装しておくと良いです。
まとめ
http 関係をGoで実装するときには、http.RoundTripper
について知っておいて損はないです。
大抵の Rest API クライアントや、 http 通信を行うライブラリは、外部から何らかの形で http.RoundTripper
を指定できるようになっているので、応用がききます。
宣伝: ワカルでは、 Goを書きたいプログラマーを募集 しています!