go の middleware を束ねて http.Handler を返す

はじめに

Goで始めるMiddlewareの通り、go の HTTP Server で middleware を通す場合、入れ子を何回も書く必要があって可読性が落ちてしまいがちです。

記事の中で記載されていますが justinas/aliceを利用するとこで middleware をスタックし http.Handler を吐き出すことができますが、個人的にgo-chi/chiのように1行ずつ middleware をスタックした方が見やすいなぁと思い作ってみました。

github.com

なぜ http.Handler を返したいか

labstack/echoのように project instance に対して route や middleware をスタックし ListenAndServe() を呼ぶスタイルが多いかと思いますが、OpenAPI などの yaml を読み込んで path の mach で振り分けを行うようなアプリケーションを考えると、既存のフレームワークではうまくハマりませんでした。

middleware を束ねて http.Handler を返す

chainという名前でGithubに置いておきますが中身はとてもシンプルです。
func(http.Handler) http.Handler として実装された Middleware を次々 Handler にぶっ込んでいます。

package chain

import (
    "net/http"
)

type Middleware func(http.Handler) http.Handler

type Chains struct {
    Handler http.Handler
}

func NewChain(h http.Handler) *Chains {
    return &Chains{
        Handler: h,
    }
}

func (c *Chains) Chain(middleware Middleware) {
    c.Handler = middleware(c.Handler)
}

chain の使い方

cmd 配下にサンプルを載せています。
以下のように Middleware があるとすると

func ExampleMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware")
        next.ServeHTTP(w, r)
        fmt.Println("[END] middleware")
    })
}

以下のように ServeHTTP() を実装した http.Handler を NewChain の引数に渡して middleware を Chain していくことができます。

func (server *Server) Run() {
    // Chain middleware
    c := chain.NewChain(server)
    c.Chain(middleware.ExampleMiddleware)

    s := &http.Server{
        Addr:         ":8080",
        Handler:      c.Handler,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    if err := s.ListenAndServe(); err != nil {
        fmt.Println("[error] ", err)
    }
}

まとめ

go で HTTP Server の middleware を束ねて http.Handler を返すライブラリを作ってみました。
作ってみたけどあまり必要ないなこれ。