はじめに
github.com
今回は以下について
- とりあえず触ってみた & 感想
- Middlewareについて調査
- Panic時の挙動とPanicをMiddlewareでハンドリングする
とりあえずGetting Startedを参考に触ってみた
https://goa.design/learn/getting-started/ 見ながらやっていく。
スッと動いた。良き!!
[calcapi] 16:16:15 HTTP "Add" mounted on GET /add/{a}/{b}
[calcapi] 16:16:15 HTTP "./gen/http/openapi.json" mounted on GET /openapi.json
[calcapi] 16:16:15 serving gRPC method calc.Calc/Add
[calcapi] 16:16:15 HTTP server listening on "localhost:8000"
[calcapi] 16:16:15 gRPC server listening on "localhost:8080"
[calcapi] 16:16:17 id=sGdiU14f req=GET /add/1/2 from=127.0.0.1
generateされたclientもあるけど、あえてcurlで叩いてみる。
curl -i -X GET http://localhost:8000/add/1/2
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 20 Oct 2019 07:20:22 GMT
Content-Length: 2
3
よさそう
v1 -> v3の移行手順はこれらしい
https://goa.design/learn/upgrading/
designは基本的には、これにしたがって移行していけばよさそう。
また今度実際に移行したら色々書く予定
midlewareについて
goa.design
gRPCに対応したことで、プロトコル関係なく実行するmiddlewareとプロトコル単位で設定できるmiddlewareの2種類になったようす。
Endpoint Middleware
Useを使えば今まで通りすべてのpathに対してのmiddlewareになるみたい。
calcEndpoints.Add
に対してだけ設定するみたいなやりかたもあるみたい。
calc/main.go.diff
var (
calcEndpoints *calc.Endpoints
)
{
calcEndpoints = calc.NewEndpoints(calcSvc)
calcEndpoints.Use(middleware.ErrorLogger(logger, "calc"))
}
Transport MIddleware
HTTPだけgRPCだけ実行みたいなのがやりたいときに使うぽい。
calc/middleware/auth.go
package middleware
import (
"context"
"net/http"
"strings"
)
func WithAuthToken() func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := r
if bearerToken := r.Header.Get("Authorization"); bearerToken != "" {
ctx := context.WithValue(r.Context(), AuthTokenKey, strings.Replace(bearerToken, "Bearer ", "", 1))
req = r.WithContext(ctx)
}
h.ServeHTTP(w, req)
})
}
}
goa.Context
じゃなくて普通の context.Context
がくるのでそこからGetすればOK
calc.go
s.logger.Print("Token = ", ctx.Value(middleware.AuthTokenKey))
PanicHandler
httpパッケージのserver.goのserveメソッド側のrecoverでhandlingされる。
2019/10/20 21:16:16 http: panic serving 127.0.0.1:57525: test
goroutine 21 [running]:
net/http.(*conn).serve.func1(0xc00017e280)
/usr/local/opt/go/libexec/src/net/http/server.go:1767 +0x139
panic(0x1507000, 0x168e4c0)
/usr/local/opt/go/libexec/src/runtime/panic.go:679 +0x1b2
calc.(*calcsrvc).Add(0xc0000b0550, 0x16a6860, 0xc0001d43f0, 0xc0001d2130, 0xc0001ec101, 0xc0001d2130, 0xc0000eb760)
/Users/kyokomi/workspace/ghq/github.com/kyokomi-sandbox/sandbox/golang/goav3/calc/calc.go:29 +0x11c
これはこれでとりあえず助かるんですが、responseも以下みたいな感じになってしまい...
共通の500エラーとか返したいなぁと...
$ curl -i -X GET -H "Authorization: Bearer hogehogehogehoge" http://localhost:8000/add/1/2
curl: (52) Empty reply from server
v1のようにEndpoint Middlewareでrecoverを入れてみる
注意点としては、recoverしたときにreturnのerrorをちゃんとdefer内で差し替えないと、正常処理に入ってしまいそっちで別のpanic等が起きてしまうことがあります(encodeとかdecodeのあたりで)
calc/middleware/panic_handler.go
package middleware
import (
"context"
"fmt"
"log"
goa "goa.design/goa/v3/pkg"
)
func PanicRecover(l *log.Logger) func(goa.Endpoint) goa.Endpoint {
return func(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (response interface{}, err error) {
defer func() {
if exception := recover(); exception != nil {
panicErr, ok := exception.(error)
if !ok {
panicErr = fmt.Errorf("recover error %v", exception)
}
l.Printf("[PANIC] %v", panicErr)
err = panicErr
}
}()
response, err = e(ctx, req)
return
}
}
}
適当にhandlerでpanicさせてときのログ
[calcapi] 21:19:33 calc.add
[calcapi] 21:19:33 Token = hogehogehogehoge
[calcapi] 21:19:33 [PANIC] recover error test
[calcapi] 21:19:33 [ERROR] calc: recover error test
[calcapi] 21:19:33 id=IGTAuYo3 status=500 bytes=111 time=247.777µs
curlの結果もこんな感じになる
$ curl -i -X GET -H "Authorization: Bearer hogehogehogehoge" http://localhost:8000/add/1/2
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Date: Sun, 20 Oct 2019 12:19:33 GMT
Content-Length: 111
{"name":"fault","id":"Aghc9GSY","message":"recover error test","temporary":false,"timeout":false,"fault":true}