🔩 ねじき教室 Go と Web の教室

フレームワーク echo

読了目安 約3分

ここまでの標準ライブラリの知識がそのまま通用することを、echo で同じアプリを書き直して確かめます。

この章の目次

ここまで 5 章で書いた一式を短く書けるようにまとめたライブラリが Web フレームワークです。 数ある中から echo を選ぶのは、近年の ISUCON で配布される Go の初期実装にたびたび採用されているからです。

go get で導入する

echo は標準ライブラリではないので、外部パッケージとして取得します。 新しいディレクトリで次を実行してください。

シェル
mkdir echo-hello
cd echo-hello
go mod init example.com/echo-hello
go get github.com/labstack/echo/v4

go get はパッケージをダウンロードし、依存していることを go.mod に記録します。

同じアプリを echo で書く

次の内容を main.go に保存してください。

Go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

type Post struct {
	ID   int    `json:"id"`
	Body string `json:"body"`
}

func main() {
	e := echo.New()
	e.Use(middleware.Logger()) // アクセスログのミドルウェア

	e.GET("/hello", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello!")
	})

	e.GET("/users/:id", func(c echo.Context) error {
		id := c.Param("id")
		return c.JSON(http.StatusOK, map[string]string{"id": id})
	})

	e.POST("/posts", func(c echo.Context) error {
		body := c.FormValue("body")
		if body == "" {
			return c.String(http.StatusBadRequest, "本文 body が空です")
		}
		return c.JSON(http.StatusCreated, Post{ID: 1, Body: body})
	})

	e.Logger.Fatal(e.Start(":8080"))
}

起動の前に依存関係を確定させます。

シェル
go mod tidy
go run main.go

go mod tidy は、実際に import しているパッケージに合わせて go.modgo.sum(依存の記録ファイル)を整えるコマンドです。 省くと go runmissing go.sum entry というエラーで止まります。 外部パッケージを使うコードを書き換えたら go mod tidy、と覚えてください。

起動したら、別のターミナルから叩きます。

シェル
curl http://localhost:8080/hello
curl http://localhost:8080/users/42
curl -X POST -d "body=はじめての投稿" http://localhost:8080/posts

サーバー側には、リクエストごとに所要時間などを含むログが出ます。 前章で自作したログミドルウェアの既製品が middleware.Logger() で、e.Use は「全ルートをこれで包め」という指定です。

標準ライブラリとの対比

やりたいことごとに並べると、一対一に対応しています。

やりたいこと標準ライブラリecho
ルート登録mux.HandleFunc("GET /hello", h)e.GET("/hello", h)
パスパラメータ"GET /users/{id}"r.PathValue("id")"/users/:id"c.Param("id")
クエリ文字列r.URL.Query().Get("q")c.QueryParam("q")
フォームの値r.FormValue("body")c.FormValue("body")
JSON を返すContent-Type を設定して json.NewEncoder(w).Encode(v)c.JSON(http.StatusOK, v)
ミドルウェアハンドラを包む自作関数e.Use(middleware.Logger())
起動http.ListenAndServe(":8080", mux)e.Start(":8080")

ハンドラの形だけは見た目が変わります。

Go
func(c echo.Context) error

別々の引数だった wr が、c という 1 つの入れ物にまとまっています。 失敗のたびに http.Error を呼ぶ代わりに、ハンドラが error を返せば echo がエラーレスポンスへの変換を引き受けます。

記法が {id} ではなく :id なのは、標準ライブラリにパスパラメータがなかった時代に、フレームワークが各自の記法を先に定めた名残です(ルーティングの章の注)。

フレームワークの下では同じことが起きている

echo は net/http を置き換えるものではなく、その上に載っています。 e.Start は内部で net/http のサーバーを起動し、リクエストごとに goroutine が走ります。 echo のルーター自体が http.Handler インターフェースを実装して、振り分けを担います。

だから、ISUCON で echo の初期実装を渡されても、e.GET の並びはルーティング、c.Param はパスパラメータ、e.Use はミドルウェアと、この Part の語彙で読み下せます。

ただし、投稿の内容はまだ変数の中にしかなく、サーバーを再起動すれば消えます。 データの仕舞い方は、次の Part 4 のデータベースと SQL で扱います。