🔰
Go 標準パッケージ理解 net/http編
前置き
- 自分の理解用
- 学習兼記事進捗メモ
- httpクライアント 〇
- httpサーバー 未
- コンテキスト 未
- jsonエンコードデコード △
- io.Reader △
- ミドルウェアとか...etc 未
net/httpとは
- Goで用意されている標準パッケージの1つ
- HTTP関連の型や関数を提供する
- 製品レベルのHTTP/2クライアントとサーバーが含まれている
HTTPクライアント
構造体http.Client
が定義されており、HTTPリクエストの生成とレスポンスの受信ができる。
デフォルトクライアント
デフォルトのクライアント(net/httpに含まれている)では、タイムアウトが無いため、本番環境での利用は推奨されていない。
http.Get関数もDefaultClientを利用している(GetはDefautlClient.Getのラッパー)
// デフォルトのクライアントを使ったHTTP応答
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type DogResponse struct {
Message string `json:"message"`
Status string `json:"status"`
}
resp, err := http.DefaultClient.Get("https://dog.ceo/api/breeds/image/random")
// resp, err := http.Get("https://dog.ceo/api/breeds/image/random")
if err != nil {
// エラーハンドリング
}
defer resp.Body.Close()
var dogResponse DogResponse
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&dogResponse); err != nil {
// エラーハンドリング
}
fmt.Println(dogResponse.Message)
開発環境はデフォルトのクライアントを使うという考え方もあるが、個人的には独自のクライアントインスタンスを作った方がいいと思う。
- 書き方に慣れる
- 本番環境と開発環境のコードの差異が少なくなる
以下では、明示的にクライアントのインスタンスを作成している。
プログラム全体でhttp.Client
をひとつだけ作成する。
client := http.Client{
Timeout: 30 * time.Second, // タイムリミットの設定 30秒
}
リクエスト
*http.Request
インスタンスの作成
リクエストを送りたい時には、http.NewRequestWithContext
関数を使って新しい*http.Request
のインスタンスを作成する
-
*http.Request
= 構造体Request
へのアクセス -
コンテキスト、メソッド、接続先URLを渡す
-
PUT
、POST
、PATCH
のいずれかのリクエストの場合、最後の引数をio.Reader
としてリクエストのBody
を指定する-
Body
が無い場合はnil
を指定する(つまりGet
の時)
-
-
context.Background()
はまだコンテキスト未勉強。勉強したら加筆するかも -
http.MethodGet
は定数 -
url
はそのまんま変数url
-
nil
は上記でも記述したように、Body
が無い(Get
)ためnil
としている-
id.Reader
でBody
を指定する - ぶっちゃけ
id.Reader
はよく分かってない。勉強したら加筆するかも。
-
url := "https://dog.ceo/api/breeds/image/random"
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
fmt.Printf("リクエスト%d回目", i)
fmt.Printf("リクエストの作成に失敗しました: %v", err)
}
ヘッダーの設定、リクエストの送信と結果格納
*http.Request
のインスタンスを作成したら、http.Request
を使ってhttp.Client
を呼び出す
-
http.Client
のメソッドはDo
を指定する -
http.Response
に結果が返る(func (*Client)Do
の(*Response, error)
の部分)
ヘッダー追加すると何ができるか、何が嬉しいのかは未勉強。
認証、キャッシュ、クッキー用のヘッダーとかかなぁと今のところイメージしてる。
req.Header.Add("X-My-Client", "Go-Learn") // リクエストのヘッダーに追加
res, err := client.Do(req) // リクエストを送信して結果を格納
if err != nil {
fmt.Printf("リクエストの作成に失敗しました: %v", err)
}
レスポンスの確認、jsonのデコード
- レスポンスのステータスコードはフィールド
StatusCode
に格納される - レスポンスのテキストは
Status
に格納される - レスポンスヘッダーは
Header
に格納される
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
fmt.Printf("APIからエラーステータスが返されました: %s", res.Status)
}
fmt.Println(res.Header.Get("Content-Type")) // 結果確認用。不要ならコメントアウト
var data Data
type Data struct {
Message string `json:"message"`
Status string `json:"status"`
}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
fmt.Printf("JSONのデコードに失敗しました: %v", err)
}
fmt.Printf("%+v\n", data)
理解するためのメモ
-
Body
はio.ReadCloser
型のフィールドBody
に格納される -
json
パッケージのデコーダを使うことで、簡単にレスポンスを処理することができる -
defer res.Body.Close()
- 接続のクローズ。
defer
なのでプログラムの最後(return
)のタイミングで実行される - ループ文を処理するときは
defer
ステートメントを使わず、関数が終了する直前に実行するようにすべきとのこと(他の関数に処理を切り出すとdefer
はいい感じに使えるとのこと)- 接続がスタックして閉じない状態になり、接続数が跳ね上がりやすくなる
- 接続のクローズ。
-
if res.StatusCode != http.StatusOK
- レスポンスのステータスコードがステータスOK(200)じゃなければエラー出力を行う
-
StatusOK
は定数
-
json
タグは可読性を向上させる(ほかにも理由があるらしいがまだ詳しくは見れてない)- フィールドは
int
型、bool
型など色々指定可能var data Data type Data struct { Message string `json:"message"` Status string `json:"status"` }
- フィールドは
-
err = json.NewDecoder(res.Body).Decode(&data)
-
json
パッケージのNewDecoder
メソッドにres.Body
を引数として渡しつつ、Decode
メソッドに&data
を引数として渡している・・・という理解-
NewDecoder
:json
のデコーダを作成 -
Decode
:作成されたjson
デコーダに対してデコードを実施 -
NewDecoder
でJson
を読み取るためのデコーダ(解読器)を作成し、デコーダのDecode
メソッドでGo内で作った構造体に格納しているって感じ?
-
-
結果
application/json
{Message:https://images.dog.ceo/breeds/pembroke/n02113023_5985.jpg Status:success}
Discussion