Try Golang! EchoでオリジナルのMiddlewareを作ろう!

How to implement Echo custom middleware in Golang.

Takuo
VELTRA Engineering
8 min readJul 3, 2017

--

GoのフレームワークのひとつであるEchoには、Middlewareという機能があります。この機能を使うと、ビジネスロジックの前後に共通して実施したい処理を差し込むことができます。この機能をサポートしている他のフレームワークも多くあり、私が触ったことのあるJavaのフレームワークではFilterと呼ばれていました。今回は、このEchoにおけるMiddlewareの実装方法について記載します。なお、EchoのVer.は3.1.0です。

オリジナルのMiddlewareの書き方

EchoでMiddlewareを作成するには、echo.MiddlewareFuncの型の関数を用意します。シグネチャはfunc(next echo.HandlerFunc) echo.HandlerFuncです。この関数の戻り値であるecho.HandlerFuncは、通常のハンドラと同じ型ですね。

基本的にはecho.HandlerFuncの関数の中を実装すればよく、next(c)と記載することで次のハンドラ(次のMiddleware、または最終的なハンドラ)が呼ばれるので、事前処理はnext(c)の前に、事後処理はnext(c)の後に記載します。

func customMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// before
err := next(c)
// after
return err
}
}

Middlewareの設定方法と実行順序

EchoのMiddlewareを設定するためのメソッドには、PreUseの2つがあります。また、各ルーティングを設定する際にも合わせてMiddlewareの設定が可能です。さらに、複数のルーティングをグループとしてまとめることができ、このグループ単位でもMiddlewareを設定することができます。

以下のコードでは、Pre, Use, Group, Routeの4通りの方法でMiddlewareを設定しています。どのような順番で実行されるか見てみましょう。

package mainimport (
"fmt"
"net/http"
"github.com/labstack/echo"
)
// create middleware function just to output message
func createCustomMiddleware(name string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
defer fmt.Printf("middleware-%s: defer\n", name)
fmt.Printf("middleware-%s: before\n", name)
err := next(c)
fmt.Printf("middleware-%s: after\n", name)
return err
}
}
}
func businessLogic(c echo.Context) error {
defer fmt.Println("logic: defer")
fmt.Println("logic: main")
c.NoContent(http.StatusOK)
return nil
}
// start web-app server after middleware setting
func main() {
e := echo.New()
g := e.Group("", createCustomMiddleware("Group"))
g.GET("/*", businessLogic, createCustomMiddleware("Route"))
e.Use(createCustomMiddleware("Use-1"))
e.Use(createCustomMiddleware("Use-2"))
e.Pre(createCustomMiddleware("Pre "))
e.Logger.Fatal(e.Start(":1323"))
}

アプリケーションを実行し、ひとつのGETリクエストを処理すると、以下のような順でメッセージが出力されます。

middleware-Pre  : before
middleware-Use-1: before
middleware-Use-2: before
middleware-Group: before
middleware-Route: before
logic: main
logic: defer
middleware-Route: after
middleware-Route: defer
middleware-Group: after
middleware-Group: defer
middleware-Use-2: after
middleware-Use-2: defer
middleware-Use-1: after
middleware-Use-1: defer
middleware-Pre : after
middleware-Pre : defer

Middlewareを設定した順番によらず、PreUseGroupRouteの順で実行されることがわかります。また、同じUseで設定された2つについては、先に設定したものから実行されていることがわかりますね。

事前処理の実行順序は上の通りですが、事後処理はちょうど逆の順になります。また、deferが実行されるタイミングは当該Middlewareの事後処理直後であることがわかります。どこかでpanicが発生した場合、それ以降はdeferのみが実行されますよ。

PreとUseの使い分け

GroupRouteを利用してMiddlewareを設定するケースは、全てのルーティング先に適用したくない場合だと容易に理解できます。それでは、PreUseはどのように使い分ければよいでしょうか。Echoのドキュメントには以下のように記載されています。

Echo#Pre() can be used to register a middleware which is executed before router processes the request.

Most of the time you will register a middleware at this level using Echo#Use(). This middleware is executed after router processes the request and has full access to echo.Context API.

基本的にはUseを使え、と。ルーティング処理の前に実行すべき処理(ex.末尾のスラッシュを除く処理)はPreでなければいけませんが、特にそういった必要がなければUseを使いましょう。

ちなみに、Preで設定したMiddlewareの中では、ルーティングパスを取得できません。c.Path()と書いても空文字が返ってくるだけなので、ご注意下さい。

優秀なEchoのMiddleware達

実はオリジナルのMiddlewareを作成しなくても、多くのMiddlewareがEchoに用意されています。自前で作成する前に、Echoで既に用意されていないかどうか、事前に確認しましょう。

例えば、全てのリクエストについてアクセスログを取得したい場合は、LoggerというMiddlewareが利用できます。デフォルトでは出力先は標準出力ですが、ファイルに出力するように設定してあげればそれだけでOK。出力フォーマットもカスタマイズできます。

また、RecoverというMiddlewareを利用すれば、アプリケーションの内部でpanicが発生した場合でも、一律共通のエラーハンドラに処理を飛ばすことができます。

なお、EchoのMiddlewareにはSkipperという設定ができ、特定の条件に合致した場合に処理をスキップすることができます。他にも色々な機能が備わっているので、必要があればEchoのドキュメントを見てみて下さい。

上手く使えば便利なMiddlewareですが、アプリケーション全体に影響するところなので、Middlewareの設定順序やエラーハンドリング等々、気をつけなければいけないポイントはたくさんあります。しっかり確認してテストして、効果的にMiddlewareを利用しましょう!

--

--

Takuo
VELTRA Engineering

Engineer who likes travel, simple code, and something new