今日の酒を旨く呑む

酒は努力した分きっと旨いはず。エンジニア見習いです。

Echoリファレンスの詳しく知りたかったところ1~Context~

先日社内勉強会でGoのWebフレームワーク「Echo」の入門をテーマに発表を行ったのでその際に改めてリファレンスを読み込んでみました。 リファレンスはとても簡潔でざっと一通り読むと使い方がイメージできるようなものです。

ですが厳密にこれってどうなってるんだ?違いは?使う場面は?と考えてしまう部分がいくつかあったので その辺りについて簡単に触れてみようと思います。 日本語での記事が少なく、調べづらかった点も含めて自分なりにゆるく調べた内容を扱います。

Context、Bind、Middleware、静的ファイルの扱い、Loggerなどについて触れる予定です。 自分の為の備忘録にしたいので多分気が乗れば続きます。

Echo について

GoのWebフレームワークで、高性能で拡張可能、最小限の構成になっていて
堅牢かつスケーラブルなRESTfulAPIを構築できることを謳っています。

Echo - High performance, minimalist Go web framework

Context

echo.Contextはリクエスト・レスポンスや、パス、パスパラメータ、データ、登録されたHandler、リクエストとレスポンスに対してのAPIなどをデフォルトで保持します。 またContext自体はInterfaceでしかないので拡張できるとあります。

ContextのInterface定義を見てみると様々なパラメータや、メソッドが定義されていることが分かります。 APIやMiddlewareで使用されるHandlerFunc定義ではecho.Contextを持ち回して処理を行うため、 リクエストで受け取った内容を引き出したり、レスポンスにデータを詰めるなどすることが想定されています。

f:id:dears31:20210213143111p:plain
HandlerFunc定義

単純に受け取ったリクエストからレスポンスを生成するような用途の場合、拡張する必要はないかもしれません。 ですが、例えばContextによってHandlerやMiddlewareで参照したい共通の値などがあった場合、Contextに詰めるのが楽です。 また、Context共通の処理としてメソッドを定義したい場合もあると思います。

そのためにはContextを拡張する必要があります。
基本的な拡張の仕方はリファレンスに記載があるので参考にしてください。

Context | Echo - High performance, minimalist Go web framework

例1. パラメータを付加する
type User struct {
    Name string `json:"name" form:"name" query:"name" validate:"required"`
}

type CustomContext struct {
    echo.Context
    UserId int
}

func NewCustomContext(c echo.Context) *CustomContext {
    return &CustomContext{
        Context: c,
    }
}

func main() {
    e := echo.New()
    // Contextの設定はCustomContextを使用する他のmiddlewareよりも早く行う必要がある
    e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            cc := NewCustomContext(c)
            return next(cc)
        }
    })
    e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            cc := c.(*CustomContext)
            // 何かしらの認証、UserIdを詰める
            cc.UserId = 12345
            return next(cc)
        }
    })
    e.GET("/", func(c echo.Context) error {
        cc := c.(*CustomContext)
        // UserIDの取り出し
        return cc.String(200, strconv.Itoa(cc.UserId))
    })

    // サーバ起動
    e.Logger.Fatal(e.Start(":1323"))
}
例2. 共通の関数を付加する
type User struct {
    Name string `json:"name" form:"name" query:"name" validate:"required"`
}

type CustomContext struct {
    echo.Context
}

func NewCustomContext(c echo.Context) *CustomContext {
    return &CustomContext{
        Context: c,
    }
}

// 付加するメソッド よく使うBindとValidateをまとめた例
func (c *CustomContext) BindAndValidate(i interface{}) error {
    if err := c.Bind(i); err != nil {
        return c.String(http.StatusBadRequest, err.Error())
    }
    if err := c.Validate(i); err != nil {
        return c.String(http.StatusBadRequest, err.Error())
    }
    return nil
}

func main() {
    e := echo.New()
    // コンテキストの設定はCustomContextを使用する他のmiddlewareよりも早く行う必要がある
    e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            cc := NewCustomContext(c)
            return next(cc)
        }
    })
    e.GET("/", func(c echo.Context) error {
        cc := c.(*CustomContext)
        u := User{}
        // 共通の処理
        if err := cc.BindAndValidate(u); err != nil {
            return err
        }
        return cc.String(200, u.Name)
    })

    // サーバ起動
    e.Logger.Fatal(e.Start(":1323"))
}

まとめ

恐らく最も使うことが多いであろう例を2つ記載しました。
サービスを考えると、Middlewareでの認証で作ったデータなどを詰めたりすることが多いのではないでしょうか。
またメソッドを付加する例ではコードを短縮する例を取り上げてみました。
どちらも分かりやすく、もうちょっと工夫することで冗長なコードの削減なども可能だと思います。

Echoはベースがかなりシンプルなので拡張性が高く、 工夫すれば様々な書き方が出来ると思うので何がベストとは定義しづらいですが、 リファレンスだけでなく元の定義をある程度見てみた上で考えていければ模索する余地がありそうだと感じました。

今回は割と内容が簡単なものから扱いました。
次書く気力が湧くように頑張ります。