Step Up Gin! gin.Contextの中身を覗いてみよう!
GolangでGinを使ってAPIサーバーの開発をしていると必ず目にするのがgin.Contextという構造体。
実装されているコードに書かれているgin.Context説明を訳すとこのように紹介されています。
Context is the most important part of gin. It allows us to pass variables between middleware, manage the flow, validate the JSON of a request and render a JSON response for example.
Contextはginのフレームワークの中でも最も重要な部品の一つです。
gin.Contextを使うことで、ミドルウェア間での変数の受け渡しや、処理の管理、JSONリクエストのバリデーション、JSONレスポンスの出力等が可能になります。
APIサーバーを立てるとなると、メタ情報の管理やリクエストのデータの受け渡しが必要になる場面が出てくるので、
その核となるgin.Contextについて勉強しておくのは百害あって一利なしという感じですよね!
今回の記事では、Context構造体内で定義されているパブリックフィールドが、それぞれどんな用途に使われているのかを一緒に理解していきましょう!
実装を覗いてみる
ということで早速gin.Contextの実装を覗いていきましょう。
なお、gin.Context構造体にはプライベートフィールドとパブリックフィールドの両方が定義されています。
プライベートフィールドに関しては、値の取得等の処理のためにキャッシュされたデータを保持していて、内部的に参照されるものが多いため今回の記事では割愛させていただいています。
今後別のタイミングで記事にさせていただければと🙏
下記gin.Contextの実装(パブリックフィールドのみ抜粋)です。
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
Request *http.Request
Writer ResponseWriter
Params Params
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]any
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
}
Request *http.Request
標準のnet/httpのライブラリにも登場する、http.Requestのポインタを保持するフィールドです。
ちなみにWebフレームワークを入れずにAPIサーバーを立てるときは下記のようにルーターにメソッドを登録しますよね。
http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
someFunc()
}
ginもGolangの他のWebサーバーフレームワークと同様に、内部的にはnet/httpパッケージによるリクエストの受け取りとレスポンスを送る処理を内包しているため、
リクエストで受け取ったデータをRequestフィールドに保持している形となっています。
Writer ResponseWriter
プライベートフィールドのwritermemのゲッターとなっています。
そしてswritermemの型を見てみるとresponseWriter
となっており、構造体の定義を見てみると下記の通りhttp.ResponseWriter
と一緒にレスポンスのサイズとステータスも保持するようになっています。
type responseWriter struct {
http.ResponseWriter
size int
status int
}
ResponseWriter自体はインターフェースとなっていて、上記のサイズ・ステータスへのアクセスを行ったり、レスポンスへの書き込み等の処理を行うようにさせています。
Params Params
URL内のパスパラメータが格納されます。
なおParamsおよびParamの構造体の定義は以下の通り。
type Param struct {
Key string
Value string
}
type Params []Param
用途としては、例えばrouter.GET("/users/:id", GetUser)
のような感じでユーザー情報をIDで指定して取得するエンドポイントを用意した際に、
c.Param("id")
を実行することで、/users/:id
の:id
に該当する箇所の文字列を取得できます。
Keys map[string]any
リクエスト単位で後続の処理やハンドラに渡したい値を保持させることができます。
例として、func (c *Context) Set(key string, value any)
を実行することで、c.KeysにKeyとValueの組み合わせがmapで保存され、
func (c *Context) Get(key string) (value any, exists bool)
によってKeyに紐づくValueを取得することが可能です。
Errors errorMsgs
errorMsgsの実態としては[]*Error
として定義されており、
1つのリクエストの中でgin.Contextが通過したハンドラーやミドルウェア内のエラーを、このErrorsフィールドにストックしていくイメージとなっています。
ginが推奨している使い方としては、エラーが発生するタイミングごとにfunc (c *Context) c.Error(err error) *Error
という関数を実行することで、
Errorsフィールドにエラーを詰めていって、データベースに保存するなり、ログを吐かせるなり、HTTPレスポンスとして返すといい、とのことです。
Accepted []string
クライアントからのコンテンツネゴシエーションにおいて受け付けるHeaderを格納しておくフィールドです。
コンテンツネゴシエーションというのを簡単に説明すると、
同じURIに対してリクエストを投げる時に、返却されるデータのJSONやXMLなどの形式をヘッダーのAccept
に指定することで、サーバーがその形式にデータを詰めてレスポンスしてあげる仕組みのこと等を言います。(他にはAccept-Languague
に言語を指定することで日本語なり英語なりの言語でレスポンスをもらう、という使われ方もします。)
実際の使われ方や経緯については説明が長くなりそうなので今回の記事では割愛させていただきますが、
こちらのissueにてコンテンツネゴシエーションのやり取りを見ることができますので、興味のある方は是非参考にしてみてください。
まとめ
というわけでさっくりとはありますがginフレームワークにおけるContext構造体のパブリックフィールドについて用途などを説明させていただきました!
自分自身この記事の執筆を通して、ginのドキュメントやソースを深く読み進めていくことでフレームワークへの理解が深まったので、今後もドキュメントの切り抜き的な記事を書いていければと思っています。
意見や感想などあれば気軽にコメントいただけると嬉しいです!