echo
アドベントカレンダー2日目です。1日目はこちら
echo
でwebサーバーを作ります。
echo
の特徴
ルートを賢く優先する最適化されたHTTPルーター
堅牢でスケーラブルなRESTful APIを構築
APIのグループ化
拡張可能なミドルウェアフレームワーク
ルート、グループ、またはルートレベルでのミドルウェアの定義
JSON、XML、フォームペイロード用のデータバインディング
様々なHTTPレスポンスを送信するための便利な機能
集中化されたHTTPエラー処理
任意のテンプレートエンジンでのテンプレートレンダリング
ロガーのフォーマットを自由に定義
高度にカスタマイズ可能
Let’s Encrypt経由での自動TLS
HTTP/2サポート
echoのインストール
docker-compose exec golang /bin/bash
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4/middleware
mkdir app/day2
touch app/day2/main.go
プログラム
公式のサンプルをお借りしました
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", hello)
e.Logger.Fatal(e.Start(":1323"))
}
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
ローカルとコンテナのポートをつなげる
ローカルからリクエストを送りたいのでportを繋げています。
version: '3'
services:
golang:
build: .
tty: true
volumes:
- ./:/app/
# ↓追記
ports:
- "1323:1323"
起動
$ go run app/day2/main.go
root@8f93343dfdb9:/app# go run app/day2/main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.11.3
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:1323
リクエストを送ってみる
$ curl localhost:1323
Hello, World!
レスポンスをJSON形式にする
レスポンス用の構造体を定義し、c.JSON(http.StatusOK, data)
をhello
関数で返却しています。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type ResponseData struct {
Status int `json:"status"`
Message string `json:"message"`
}
func main() {
e := echo.New()
e.Use(middleware.Logger()) // ログ出力用
e.Use(middleware.Recover()) // 復帰用
e.GET("/", hello)
e.Logger.Fatal(e.Start(":1323"))
}
func hello(c echo.Context) error {
data := ResponseData{
Status: 200,
Message: "Hello, World!",
}
return c.JSON(http.StatusOK, data)
}
POSTリクエストを処理する
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type ResponseData struct {
Status int `json:"status"`
Message string `json:"message"`
}
// リクエストパラメータ用の構造体
type RequestData struct {
Message string `json:"message"`
}
func main() {
e := echo.New()
e.Use(middleware.Logger()) // ログ出力
e.Use(middleware.Recover()) // 復帰用
e.GET("/", hello)
e.POST("/say", say)
e.Logger.Fatal(e.Start(":1323"))
}
func hello(c echo.Context) error {
data := ResponseData{
Status: 200,
Message: "Hello, World!",
}
return c.JSON(http.StatusOK, data)
}
func say(c echo.Context) error {
data := new(RequestData)
if err := c.Bind(&data); err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}
リクエストを送ってみる
$ curl localhost:1323/say -XPOST -d '{"message": "saysaysay"}' -H "Content-Type: application/json"
{"message":"saysaysay"}
e.Use(middleware.Recover())を試す
e.Use(middleware.Recover())
はコード内でパニックが発生した際、サーバーが死なないようにパニックをキャッチしてくれるようです。
本当にリカバリーできるのか試します。
/error
にリクエストが来たらpanicさせて検証します。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type ResponseData struct {
Status int `json:"status"`
Message string `json:"message"`
}
type RequestData struct {
Message string `json:"message"`
}
func main() {
e := echo.New()
e.Use(middleware.Logger()) // ログ出力
e.Use(middleware.Recover()) // 復帰用
e.GET("/", hello)
e.POST("/say", say)
e.POST("error", func (c echo.Context) error {
panic("Panic!") //確定でpanicさせてみる
return nil
})
e.Logger.Fatal(e.Start(":1323"))
}
func hello(c echo.Context) error {
data := ResponseData{
Status: 200,
Message: "Hello, World!",
}
return c.JSON(http.StatusOK, data)
}
func say(c echo.Context) error {
data := new(RequestData)
if err := c.Bind(&data); err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}
e.Use(middleware.Recover())が無効のとき
$ curl localhost:1323/error -X POST -d '{}'
curl: (52) Empty reply from server
ログを見てみます。スタックトレースのみ表示されており、echo
の外側に漏れてしまっているようです。
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1868 +0xb0
panic({0x26eb00?, 0x34ac20?})
/usr/local/go/src/runtime/panic.go:920 +0x26c
main.main.func1({0x1a6fb759000f5968?, 0x0?})
/app/app/day2/main.go:30 +0x2c
github.com/labstack/echo/v4.(*Echo).add.func1({0x351678, 0x40000140a0})
/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:582 +0x50
github.com/labstack/echo/v4/middleware.LoggerWithConfig.func2.1({0x351678, 0x40000140a0})
/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/logger.go:126 +0xa0
github.com/labstack/echo/v4.(*Echo).ServeHTTP(0x40000ea6c0, {0x34dee8?, 0x4000076000}, 0x400010e100)
/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:669 +0x374
net/http.serverHandler.ServeHTTP({0x34d170?}, {0x34dee8?, 0x4000076000?}, 0x6?)
/usr/local/go/src/net/http/server.go:2938 +0xbc
net/http.(*conn).serve(0x40000d82d0, {0x34e6b8, 0x400009dd70})
/usr/local/go/src/net/http/server.go:2009 +0x518
created by net/http.(*Server).Serve in goroutine 1
/usr/local/go/src/net/http/server.go:3086 +0x4cc
e.Use(middleware.Recover())が有効のとき
$ curl localhost:1323/error -X POST -d '{}'
{"message":"Internal Server Error"}
しっかりとロギングできていますね。
{"time":"2023-12-02T14:21:23.892957596Z","level":"-","prefix":"echo","file":"recover.go","line":"120","message":"[PANIC RECOVER] error goroutine 19 [running]:\nmain.main.Recover.RecoverWithConfig.func2.1.1()\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/recover.go:100 +0x128\npanic({0x26eca0?, 0x34b200?})\n\t/usr/local/go/src/runtime/panic.go:914 +0x218\nmain.main.func1({0x40000fb8b8?, 0x0?})\n\t/app/app/day2/main.go:30 +0x2c\ngithub.com/labstack/echo/v4.(*Echo).add.func1({0x351c58, 0x40000a1180})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:582 +0x50\nmain.main.Recover.RecoverWithConfig.func2.1({0x351c58, 0x40000a1180})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/recover.go:131 +0xec\ngithub.com/labstack/echo/v4/middleware.LoggerWithConfig.func2.1({0x351c58, 0x40000a1180})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/logger.go:126 +0xa0\ngithub.com/labstack/echo/v4.(*Echo).ServeHTTP(0x40000ea6c0, {0x34e4c8?, 0x4000136000}, 0x400010e100)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:669 +0x374\nnet/http.serverHandler.ServeHTTP({0x34d750?}, {0x34e4c8?, 0x4000136000?}, 0x6?)\n\t/usr/local/go/src/net/http/server.go:2938 +0xbc\nnet/http.(*conn).serve(0x40000d82d0, {0x34ec98, 0x400009dda0})\n\t/usr/local/go/src/net/http/server.go:2009 +0x518\ncreated by net/http.(*Server).Serve in goroutine 1\n\t/usr/local/go/src/net/http/server.go:3086 +0x4cc\n\ngoroutine 1 [IO wait]:\ninternal/poll.runtime_pollWait(0xffff38d43e98, 0x72)\n\t/usr/local/go/src/runtime/netpoll.go:343 +0xa0\ninternal/poll.(*pollDesc).wait(0x40000e0500?, 0x259e0?, 0x0)\n\t/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x28\ninternal/poll.(*pollDesc).waitRead(...)\n\t/usr/local/go/src/internal/poll/fd_poll_runtime.go:89\ninternal/poll.(*FD).Accept(0x40000e0500)\n\t/usr/local/go/src/internal/poll/fd_unix.go:611 +0x250\nnet.(*netFD).accept(0x40000e0500)\n\t/usr/local/go/src/net/fd_unix.go:172 +0x28\nnet.(*TCPListener).accept(0x40000e4280)\n\t/usr/local/go/src/net/tcpsock_posix.go:152 +0x28\nnet.(*TCPListener).AcceptTCP(0x40000e4280)\n\t/usr/local/go/src/net/tcpsock.go:302 +0x2c\ngithub.com/labstack/echo/v4.tcpKeepAliveListener.Accept({0x40000f9cb8?})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:989 +0x1c\nnet/http.(*Server).Serve(0x4000118000, {0x34e5e8, 0x400009a080})\n\t/usr/local/go/src/net/http/server.go:3056 +0x2b8\ngithub.com/labstack/echo/v4.(*Echo).Start(0x40000ea6c0, {0x2ccbbf, 0x5})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:686 +0xcc\nmain.main()\n\t/app/app/day2/main.go:33 +0x3c0\n\n"}
{"time":"2023-12-02T14:21:23.893116512Z","id":"","remote_ip":"192.168.112.1","host":"localhost:1323","method":"POST","uri":"/error","user_agent":"curl/8.1.2","status":500,"error":"","latency":299708,"latency_human":"299.708µs","bytes_in":2,"bytes_out":36}
{"time":"2023-12-02T14:22:23.792135471Z","level":"-","prefix":"echo","file":"recover.go","line":"120","message":"[PANIC RECOVER] error goroutine 5 [running]:\nmain.main.Recover.RecoverWithConfig.func2.1.1()\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/recover.go:100 +0x128\npanic({0x26eca0?, 0x34b200?})\n\t/usr/local/go/src/runtime/panic.go:914 +0x218\nmain.main.func1({0x400003e8a8?, 0x0?})\n\t/app/app/day2/main.go:30 +0x2c\ngithub.com/labstack/echo/v4.(*Echo).add.func1({0x351c58, 0x40000140a0})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:582 +0x50\nmain.main.Recover.RecoverWithConfig.func2.1({0x351c58, 0x40000140a0})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/recover.go:131 +0xec\ngithub.com/labstack/echo/v4/middleware.LoggerWithConfig.func2.1({0x351c58, 0x40000140a0})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/middleware/logger.go:126 +0xa0\ngithub.com/labstack/echo/v4.(*Echo).ServeHTTP(0x40000ea6c0, {0x34e4c8?, 0x4000072000}, 0x400010e200)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:669 +0x374\nnet/http.serverHandler.ServeHTTP({0x34d750?}, {0x34e4c8?, 0x4000072000?}, 0x6?)\n\t/usr/local/go/src/net/http/server.go:2938 +0xbc\nnet/http.(*conn).serve(0x400006c000, {0x34ec98, 0x400009dda0})\n\t/usr/local/go/src/net/http/server.go:2009 +0x518\ncreated by net/http.(*Server).Serve in goroutine 1\n\t/usr/local/go/src/net/http/server.go:3086 +0x4cc\n\ngoroutine 1 [IO wait]:\ninternal/poll.runtime_pollWait(0xffff38d43e98, 0x72)\n\t/usr/local/go/src/runtime/netpoll.go:343 +0xa0\ninternal/poll.(*pollDesc).wait(0x40000e0500?, 0x27050?, 0x0)\n\t/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x28\ninternal/poll.(*pollDesc).waitRead(...)\n\t/usr/local/go/src/internal/poll/fd_poll_runtime.go:89\ninternal/poll.(*FD).Accept(0x40000e0500)\n\t/usr/local/go/src/internal/poll/fd_unix.go:611 +0x250\nnet.(*netFD).accept(0x40000e0500)\n\t/usr/local/go/src/net/fd_unix.go:172 +0x28\nnet.(*TCPListener).accept(0x40000e4280)\n\t/usr/local/go/src/net/tcpsock_posix.go:152 +0x28\nnet.(*TCPListener).AcceptTCP(0x40000e4280)\n\t/usr/local/go/src/net/tcpsock.go:302 +0x2c\ngithub.com/labstack/echo/v4.tcpKeepAliveListener.Accept({0x40000f9cb8?})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:989 +0x1c\nnet/http.(*Server).Serve(0x4000118000, {0x34e5e8, 0x400009a080})\n\t/usr/local/go/src/net/http/server.go:3056 +0x2b8\ngithub.com/labstack/echo/v4.(*Echo).Start(0x40000ea6c0, {0x2ccbbf, 0x5})\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.11.3/echo.go:686 +0xcc\nmain.main()\n\t/app/app/day2/main.go:33 +0x3c0\n\n"}
{"time":"2023-12-02T14:22:23.792440429Z","id":"","remote_ip":"192.168.112.1","host":"localhost:1323","method":"POST","uri":"/error","user_agent":"curl/8.1.2","status":500,"error":"","latency":1243459,"latency_human":"1.243459ms","bytes_in":2,"bytes_out":36}
おしまい
2日目はwebサーバーを建ててみました。今後の日程でsqlなどを扱う予定ですが、そこでつなぎ込めるようにしようと考えています。