最近、FiberというWeb Application FrameworkでWebアプリを作っていますが、そこでGenericsを使ってWebリクエストを処理する「お約束」みたいな共通処理をまとめてやっています。
Fiberじゃなくても使えると思いますが、Fiberを例にしています。なお、Fiberは現在v3がベータ版ですが、以下はv2のコードです。
リクエストごとに型を作る
最初にリクエストごとの型を作ります。
type TestGetApiReq struct {
Name string `json:"name" query:"name"`
}
type TestPostApiReq struct {
Country string `json:"country"`
}
GETのほうは、QUERY_STRINGとして渡されることを想定しています(json:"name"
は、レスポンスに使うためなので、普通は不要です)。POSTの方は、RequestBodyにJSONがくる想定です。
Fiberでは、QueryParser
やBodyParser
でパースするために必要なので、作るのに違和感はないと思います(他のWAFでも、似たようなことはするでしょう)。
Requestの型がRequestWrapperというinterfaceを満たすようにする
RequestWrapperというinterfaceを作ります。以下のようなもので、特に何もしません。
type RequestWrapper interface {
CanWrap()
}
先程の型に実装します。
func (TestGetApiReq) CanWrap() {}
func (TestPostApiReq) CanWrap() {}
これは、Genericsとして、Requestwrapperを渡したいだけなので、特に意味はないです。
CustomContextを実装するなどして、Cc() *context.CustomContext
を返すようなものを作ると、より有意義かと思います。
WrapGetRequest/WrapPostRequestという型を作る
*fiber.Ctx
と RequestWrpper
interfaceを満たしている型を受け取れる型です。
type WrapGetRequest[T RequestWrapper] struct {
c *fiber.Ctx
req *T
}
type WrapJsonRequest[T RequestWrapper] struct {
c *fiber.Ctx
req *T
}
newも作りましょう。
func NewWrapGetRequest[T RequestWrapper](c *fiber.Ctx, req *T) *WrapGetRequest[T] {
return &WrapGetRequest[T]{c, req}
}
func NewWrapJsonRequest[T RequestWrapper](c *fiber.Ctx, req *T) *WrapJsonRequest[T] {
return &WrapJsonRequest[T]{c, req}
}
GET/POSTごとの共通処理をPreProcessに書く
GETの場合は、QueryParser
でリクエストを処理します。POSTの場合は、BodyParser
でリクエストを処理します。
今回のは、サンプルなので、それ以外何もしませんが、例えば、WrapGetRequest
やWrapPostRequest
にカスタムコンテキストを持たせたることもできるでしょうし、その他の共通処理を書くこともできます。
func (w *WrapGetRequest[T]) PreProcess() (error, bool) {
err := w.c.QueryParser(w.req)
if err != nil {
return w.c.Status(400).SendString(err.Error()), false
}
return nil, true
}
func (w *WrapJsonRequest[T]) PreProcess() (error, bool) {
err := w.c.BodyParser(w.req)
if err != nil {
return w.c.Status(400).SendString(err.Error()), false
}
return nil, true
}
実際の処理を書く
サンプルなので、mainの中に実装しています。
GET/POSTの2つのAPIを作りましたが、リクエストでもらったものをJSONで返すだけです。
func main() {
// Initialize a new Fiber app
app := fiber.New()
// Define a route for the GET method on the root path '/'
app.Get("/name", func(c *fiber.Ctx) error {
req := new(TestGetApiReq)
w := NewWrapGetRequest(c, req)
err, ok := w.PreProcess()
if !ok {
return err
}
return c.JSON(req)
})
app.Post("/country", func(c *fiber.Ctx) error {
req := new(TestPostApiReq)
w := NewWrapJsonRequest(c, req)
err, ok := w.PreProcess()
if !ok {
return err
}
return c.JSON(req)
})
app.Listen(":3000")
}
サーバ動かすと動作確認がめんどくさいので、テストでレスポンスを表示するものを書いたものをGistにおいておきました。
動かすと、以下のような結果が出力されます。
2024/10/19 21:23:41 Status: 200 OK, Body:{"name":"ktat"}
2024/10/19 21:23:41 Status: 200 OK, Body:{"country":"Japan"}
2024/10/19 21:23:41 Status: 400 Bad Request, Body:unexpected end of JSON input
最後のは、壊れたJSONを送ったものです。
終わり
これで、Webにまつわるお約束みたいな前処理を共通処理できるようになりますね。