Golangとginのリクエストハンドリングについて

最近仕事で使い始めた Golang ( net/http 標準ライブラリ)とWebフレームワーク Gin のリクエストハンドリングについてどのような仕組みで行っているのかをまとめてみます。

前提

動作確認環境は以下の通りです。

  • Ubuntu 18.04.3 LTS
  • go 1.13.6
  • gin v1.5.0

Golang

マルチプレクサとは

GolangではユーザがWebページにアクセスされた際にマルチプレクサというものを使って、どのページを呼び出すかを決めます。 以下のように、内部構造にURLとページ(ハンドラ)を対応づける構造体を持っていてもので、アクセスされた時のURLから呼び出すページ(ハンドラ)が決まります。 f:id:moritamorie:20200113010143j:plain

goデフォルトのマルチプレクサ

実際に以下のようにコードを書いて、ローカルで http://localhost:8080/cart のようにアクセスすると Cart page と表示されるかと思います。

package main

import (
    "fmt"
    "net/http"
)

func defaultHandler(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintf(writer, "Hello World! (default Page)")
}

func cartHandler(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintf(writer, "Cart page")
}

func main() {
    http.HandleFunc("/", defaultHandler)
    http.HandleFunc("/cart", cartHandler)
    http.ListenAndServe(":8080", nil)
}

このように、 http.ListenAndServe()メソッドなどでマルチプレクサが指定されていない(nilの)場合は、マルチプレクサの構造体ServerMuxインスタンスであるDefaultServerMuxがデフォルトで使われ、手前の行のhttp.HandleFunc()で登録されたルーティングが処理されます。

[参照コード] go/server.go at bbbc6589dfbc05be2bfa59f51c20f9eaa8d0c531 · golang/go · GitHub

goカスタムのマルチプレクサ

マルチプレクサは、標準ライブラリであるnet/http以外のものに変更可能です。 標準ライブラリでは、例えば http://localhost:8080/books/100 のように一部がIDになっているような可変のURLの場合に対応できないですが、HttpRouterのような別のマルチプレクサに置き換えることが実現できます。

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
)

func defaultHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
    fmt.Fprintf(writer, "Hello World! (default Page)")
}

func cartHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
    fmt.Fprintf(writer, "Cart page")
}

func main() {
    mux := httprouter.New()
    mux.GET("/", defaultHandler)
    mux.GET("/cart", cartHandler)
    http.ListenAndServe(":8080", mux)
}

ListenAndServe() には、HttpRouterインスタンスが引数に入っています。

Gin

Ginのマルチプレクサ

最後にGolangのWebフレームワークの一つであるGinのマルチプレクサに関してですが、前述のHttpRouterを独自に改良したものが使われているようです。changelogにcustom hand optimized HttpRouter for Ginのような記載があったり、コードの各所のHttpRouterのライセンスの記載がありました。

ginのコードを読んで見ると独自にカスタマイズした engine(マルチプレクサ) を ListenAndServe() の引数に渡しているの読み取れます。

[参照コード] gin/gin.go at master · gin-gonic/gin · GitHub

同様に、こちらも実際にコードを書いてみると

package main

import (
    "github.com/gin-gonic/gin"
)

func defaultHandler(ginctx *gin.Context) {
    ginctx.String(200, "Hello World! (default Page)")
}

func cartHandler(ginctx *gin.Context) {
    ginctx.String(200, "Cart page")
}

func main() {
    mux := gin.Default()
    mux.GET("/", defaultHandler)
    mux.GET("/cart", cartHandler)
    mux.Run(":8080")
}

のようになります。

Ginのマルチプレクサの特徴

HttpRouterとは異なり、ルーティングをグループ化できるという特徴があるようです。(こちらは未検証のため公式ドキュメントのコードを引用します)

func main() {
    router := gin.Default()

    // v1 のグループ
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    // v2 のグループ
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}

細かいルーティングの指定ができて良さそうです。