Goは標準パッケージが充実しているのが特徴の1つだが、net/http
も例外ではなくHTTPクライアントの処理が簡単に書ける
GET
- 例えばGETでHTTPリクエストを行う処理を実装したい場合、3通りの方法がある
- http.Get 関数を実行する
- Client 型の Get(url) メソッドを実行する
- Client 型の Do(request) メソッドを実行する
- 単純なアクセスなら1で良い
一番単純な http.Get(url)によるGET
-
url.Values でクエリを組み立てて、
Get
関数で指定したURLの末尾にEncode
結果をパラメータとして付加すればOK-
url.Values
はクエリパラメータをkey-value形式で保持する型。ペアを追加するAdd
や上書きするSet
等のメソッドが提供されている - 組み立てたクエリは
Encode
メソッドを呼び出す事で?key1=value1&keyN=valueN
形式に変換することが出来る
-
values := url.Values{}
values.Add([クエリのキー], [値]
resp, err := http.Get([アクセス先URL] + "?" + values.Encode())
if err != nil {
fmt.Println(err)
return
}
// 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
defer resp.Body.Close()
// Responseの内容を使用して後続処理を行う
execute(resp)
Client 型
- 2, 3のようにClient型のメソッド経由でのアクセスは、接続時の細かい設定を行いたい場合に用いる
- Cookieを使いたい - Clientの
Jar
フィールドをいじる - タイムアウトを設定したい - Clientの
Timeout
フィールドをいじる - リクエスト時の各種属性値を設定したい -
Request
型を引数に取るDo(request)
メソッドを使う
- Cookieを使いたい - Clientの
- デフォルト実装として
DefaultClient
が用意されている- 内部では
var DefaultClient = &Client{}
と定義されている。要は空(ゼロ値)のClient -
http.Get(url)
関数はDefaultClient.Get(url)
のラッパー
- 内部では
- HTTPリクエストを処理する際は、後述する
RoundTripper
インタフェースのデフォルト実装であるTransport
が内部で使われている- DefaultClientは、Transportのデフォルト実装
DefaultTransport
を内部で使っている
- DefaultClientは、Transportのデフォルト実装
- 例えば「タイムアウトを10秒に設定したい」という場合は↓こんな感じのClientを生成する
client := &http.Client{Timeout: time.Duration(10) * time.Second}
Client.Do(Request) メソッド
- Clientを生成した所で、上記2 or 3のいずれの方法でGETを投げるか?を考える。通常は 2 で良い
- 一方 3 についてだが、引数で使用する Request 型について調べてみるとこんな特徴があった
- Method種別をフィールドに保持している
- HTTPプロトコルの情報をフィールドに保持している
- formデータをフィールドに保持している
- リクエストヘッダをフィールドに保持している = BASIC認証情報やUser-Agentを設定する事が出来る
- アクセス先のURLを net.url.URL 型でフィールドに保持している
- この型はscheme, 生のクエリ文字列, etcを内部に保持している
- Request で保持しているこれらフィールドに内容を設定することで、より細かいアクセス制御が可能になる
Client.Do(Request)メソッドでGET実装を試す
- まずクエリパラメータを組み立てる
// クエリを組み立て
values := url.Values{} // url.Valuesオブジェクト生成
values.Add([クエリのキー], [値]) // key-valueを追加
- 次にRequest型のオブジェクトを http.NewRequest 関数で生成する
// Request を生成
req, err := http.NewRequest("GET", [アクセス先URL], nil)
if err != nil {
fmt.Println(err)
return
}
- 組み立てたクエリをURLに付加する
-
Encode()
メソッドを呼んでクエリ文字列を取得する - Requestで保持している
net.url.URL
型フィールドが内部にRawQuery
という名前で生クエリを保持しているので、取得したクエリ文字列をこのフィールドに設定する
-
// 組み立てたクエリを生クエリ文字列に変換して設定
req.URL.RawQuery = values.Encode()
- リクエストヘッダを設定したい場合(例えばUser-Agentとか)は、Request内の
Header
フィールドに設定する- Values同様に
Set()
あるいはAdd()
メソッドでkey-valueを設定すればOK
- Values同様に
// User-Agentを設定
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; MAFSJS; rv:11.0) like Gecko")
- (おまけ) BASIC認証が必要なら Request.SetBasicAuth メソッドで設定する
req.SetBasicAuth([USER], [PASS})
- 最後に
Do(req)
メソッドで実際にリクエストを投げる-
client.Do(req)
によるリクエスト結果は Response 型で返ってくる- レスポンスbodyは
Body
フィールドに保持しており、この内容を使って後続処理を行う
- レスポンスbodyは
-
// Doメソッドでリクエストを投げる
// http.Response型のポインタ(とerror)が返ってくる
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
// 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
defer resp.Body.Close()
// Responseの内容を使用して後続処理を行う
execute(resp)
func execute(resp *http.Response) {
// response bodyを文字列で取得するサンプル
// ioutil.ReadAllを使う
b, err := ioutil.ReadAll(resp.Body)
if err == nil {
fmt.Println(string(b))
}
}
POST
- POSTの場合も、5通りの方法がある
- http.Post 関数を実行する
- http.PostForm(url, data) 関数を実行する
- Client 型の Post(url, bodyType, body) メソッドを実行する
- Client 型の PostForm(url, data) メソッドを実行する
- Client 型の Do(request) メソッドを実行する
- 使い分けの指針についてはGETと同様
一番単純な http.PostForm(url, data)によるPOST
- POSTなら 2 の
http.PostForm(url, data)
が一番楽 -
url.Values
でクエリを組み立てて、PostForm
関数の第2引数にそのまま渡せばOK
values := url.Values{}
values.Add([クエリのキー], [値]
resp, err := http.PostForm([アクセス先URL], values)
if err != nil {
fmt.Println(err)
return
}
:
:
Client.Do(Request)メソッドでPOST実装を試す
- GETと同じく、最も細かく設定を制御することが出来る
Client.Do(Request)
メソッドを使ったPOSTアクセスを実装してみる - まずパラメータ(Formの)を組み立てる ※GETと同じく
url.Values
を使う
// パラメータを組み立て
values := url.Values{} // url.Valuesオブジェクト生成
values.Add([クエリのキー], [値]) // key-valueを追加
- Request型のオブジェクトを http.NewRequest 関数で生成するが、 第3引数のbodyを先ほど組み立てたパラメータを使って設定する
- エンコードしたパラメータを引数にして strings.NewReader 関数を実行し、取得した
Reader
を設定する
- エンコードしたパラメータを引数にして strings.NewReader 関数を実行し、取得した
// Request を生成
req, err := http.NewRequest("POST", [アクセス先URL], strings.NewReader(values.Encode()))
if err != nil {
fmt.Println(err)
return
}
-
Content-Type
リクエストヘッダをapplication/x-www-form-urlencoded
に設定する
// Content-Typeを設定
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
他のリクエストヘッダやBASIC認証を使いたければ適宜
req.Header.Add
して追加する -
最後に
Do(req)
メソッドで実際にリクエストを投げる
// Doメソッドでリクエストを投げる
// http.Response型のポインタ(とerror)が返ってくる
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
// 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
defer resp.Body.Close()
// Responseの内容を使用して後続処理を行う
execute(resp)
作ったスニペット
※補足
RoundTripperとTransport
-
http.RoundTripper インタフェース - 単一のHTTPトランザクションを実行し、与えられたリクエストに対するレスポンスを取得する
-
RoundTrip(*Request)
というリクエストを実行する為のメソッドが定義されている -
デフォルト実装として
DefaultTransport
が提供されている-
DefaultClient
の内部で使われている
var DefaultTransport RoundTripper = &Transport{ Proxy: ProxyFromEnvironment, // proxyの設定は環境変数 $HTTP_PROXY, $NO_PROXY の内容を引き継ぐ Dial: (&net.Dialer{ Timeout: 30 * time.Second, // 接続タイムアウト時間 KeepAlive: 30 * time.Second, // 1TCP接続あたりの持続時間=keeyAlive }).Dial, TLSHandshakeTimeout: 10 * time.Second, }
-
-
-
http.Transport - RountTripperインタフェースを実装している構造体型
-
Client
と比較してより低レベル層の設定を行う- proxyの設定
- TLSの設定
- keep-alives設定
- データ圧縮の設定
- etc...
- HTTPリクエストは、実装済の RoundTrip(*Request) メソッド経由で投げる
- Transportを介して確立した接続は、効率を考慮してキャッシュ・再利用されるようになっている
-
ほとんどのHTTPリクエスト実装は
Client
やhttp.Get, Post
関数のように、Transportが内部にラップされている機能を使えば事足りる が、以下のようなケースでTransportを使う- file:/// のように http:// 以外のプロトコルでアクセスしたい場合
-
http.NewFileTransport(http.Dir("/")
関数でfileプロトコル用のTransportを生成可能
-
- file:/// のように http:// 以外のプロトコルでアクセスしたい場合
-
-
Client, Transport共にスレッドセーフ(ゴルーチンセーフ)である