このエントリーをはてなブックマークに追加

はじめに

golangのWeb FWのechoを使って独自のmiddle wareを作る方法について書きます。

echoが用意してるmiddleware

echoは標準で様々なmiddlewareを用意してくれています。
詳しくは echoの公式サイト を見てみてください
代表的なものとしては

あたりで、echoを使うならとりあえず入れておいて損はないmiddlewareかと思います。
使い方は、アプリケーションを起動する際にechoのUseを呼ぶだけ、です。

1
2
3
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())

以下、LoggerとRecoverについて簡単に紹介します。

Logger

いわゆるアクセスログのようなリクエスト単位のログを出力してくれます。
フォーマットもいじれて便利です。
デフォルトのフォーマットは以下のように設定されています。
ソースコード

1
2
3
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
	`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
	`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"`

これ以外にもログ項目を追加する方法が提供されています。詳細は ソースコードのコメント を参照するのが良いかと思います。
この中でも特に便利だなと思ったのが、http headerやquery、formのデータをログに出力できることです。
ログ出力の実装は↓のようになっているので、それに合わせてフォーマット定義すればその通りにログが出力されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
switch {
case strings.HasPrefix(tag, "header:"):
	return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
case strings.HasPrefix(tag, "query:"):
	return buf.Write([]byte(c.QueryParam(tag[6:])))
case strings.HasPrefix(tag, "form:"):
	return buf.Write([]byte(c.FormValue(tag[5:])))
case strings.HasPrefix(tag, "cookie:"):
	cookie, err := c.Cookie(tag[7:])
	if err == nil {
		return buf.Write([]byte(cookie.Value))
	}
}

Recover

アプリケーションのどこかで予期せずにpanicを起こしてしまっても、サーバは落とさずにエラーレスポンスを返せるようにリカバリーするmiddlewareです。
nil pointerなどのRuntimeエラーはどうしても発生してしまうので、APIサーバとして使うのであれば必須なmiddlewareかなと思います。
詳細はこちら

middlewareを実装する

公式ドキュメント を参照すると丁寧にmiddlewareの作り方が書かれています。
こちらを参考にmiddlewareを作ってみます。
middlewareは echo.HandlerFunc を返す関数を作ればよいだけ、です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func myMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		log.Println("before action")
		if err := next(c); err != nil {
			c.Error(err)
		}
		log.Println("after action")
		return nil
	}
}

next(c) で実際のアプリの処理が実行されるアプリの処理の前後に処理をはさみたいときには next(c) の前後に処理を書けばよいです。
これを定義した上で、echoを初期化する際に Use してあげれば自分で作ったmiddlewareが有効になります。

1
e.Use(myMiddleware)

処理にもログを仕込みます。

1
2
3
4
5
6
	e.GET("/", hello)
...
func hello(c echo.Context) error {
	log.Println("hello action")
	return c.JSON(http.StatusOK, map[string]string{"hello": "world"})
}

echoを起動して、curlコマンドを実行してみると、以下のログが出力されます。

1
$ curl localhost:1314
1
2
3
4
2019/02/05 23:31:38 before action
2019/02/05 23:31:38 hello action
2019/02/05 23:31:38 after action
{"time":"2019-02-05T23:31:38.618186+09:00","id":"","remote_ip":"::1","host":"localhost:1314","method":"GET","uri":"/","user_agent":"curl/7.54.0","status":200,"error":"","latency":416885,"latency_human":"416.885µs","bytes_in":0,"bytes_out":18}

これでアプリの処理の前後に処理を挟み込むことが出来ました。
Loggerミドルウェアを使っているのでアクセスログも出力されています。

Loggerミドルウェアをカスタマイズする

最後にLoggerミドルウェアを少しカスタマイズしてみます。
e.Use(middleware.Logger()) としていた部分を以下のように書き換えます。

1
2
3
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
	Format: `${time_rfc3339_nano} ${host} ${method} ${uri} ${status} ${header:my-header}` + "\n",
}))

この状態でアプリを起動してcurlを打ってみます。そしたらログには以下のように出力されます。

1
2
3
4
2019/02/05 23:43:17 before action
2019/02/05 23:43:17 hello action
2019/02/05 23:43:17 after action
2019-02-05T23:43:17.211349+09:00 localhost:1314 GET / 200

また、独自のheaderを指定すると、それがログに出力されます。

1
$ curl localhost:1314 -H "my-header:echo sample header"
1
2019-02-05T23:45:24.95393+09:00 localhost:1314 GET / 200 echo sample header

このように、システム全体としてログとして出力したいheader/query/formなどがあればそれをアクセスログ内に残すことが出来ます。
使いようによってはとても便利に使うことが出来るかと思います。

まとめ

今回はechoのmiddlewareを実装してみました。
色々と便利な使い方が出来るので、echoを使う場合は使ってみると良いかと思います。
作ったサンプルは以下のリポジトリにおいています。

https://github.com/ken-aio/go-echo-sample/tree/v3