
GAE/Goでweb framework的なものを使う
August 17, 2020,
tags:
golang
gae
chi
Goで使うweb framworkを探していたらこんな意見を頂いた。
僕も以前調べたんですが、Goでバリバリ開発してる会社に聞いた事あるんですが、最終的にFWは使わないのが一番良い、となったそうです。
— Tao Sasaki (@tao_s) August 15, 2020
僕もそう思います
Goはnet/httpのパッケージは良くできているのでいらないかもしれない。
ただ、以下の点は気になった。
- HTTPメソッドの指定
- Middleware的な処理
HTTPメソッドの指定
http.HandleFunc
だとHTTPのメソッド指定ができない。
これはちょっと個人的に使いづらさを感じてしまう。
Middleware的な処理
WebAPIを作る前提で考えているので、bodyParser的なものとか、X-Forwarded-ForなIPの対応とか、そういった処理は自前で用意したりライブラリ用意するのは嫌だ。
そんな感じchiを使ってみる
上記2つを満たすにはchiでいいかなと思い使ってみた。
前回の久しぶりのGAE/go 続きのコードで検証しているので同じように動作するものをchi
で書いてみた。
// main.go
package main
import (
"context"
"log"
"net/http"
"os"
"cloud.google.com/go/datastore"
)
var datastoreClient *datastore.Client
func main() {
ctx := context.Background()
var err error
datastoreClient, err = datastore.NewClient(ctx, "aaaaa")
if err != nil {
log.Fatal(err)
}
port := os.Getenv("PORT")
if port == "" {
port = "8000"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, Router()); err != nil {
log.Fatal(err)
}
}
// router.go
package main
import (
"log"
"os"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/render"
)
func Router() chi.Router {
r := chi.NewRouter()
middleware.DefaultLogger = middleware.RequestLogger(
&middleware.DefaultLogFormatter{
Logger: newLogger(),
},
)
r.Use(middleware.Logger)
r.Use(middleware.RealIP)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Use(render.SetContentType(render.ContentTypeJSON))
r.Get("/", Index)
return r
}
func newLogger() *log.Logger {
return log.New(os.Stdout, "chi-log: ", log.Lshortfile)
}
// handler.go
package main
import (
"context"
"fmt"
"net/http"
"time"
"cloud.google.com/go/datastore"
"github.com/go-chi/render"
)
func Index(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Get a list of the most recent visits.
visits, err := queryVisits(ctx, 10)
if err != nil {
msg := fmt.Sprintf("Could not save visit: %v", err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
// Record this visit.
if err := recordVisit(ctx, time.Now(), r.RemoteAddr); err != nil {
msg := fmt.Sprintf("Could not save visit: %v", err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
render.Status(r, http.StatusOK)
list := []render.Renderer{}
for _, visit := range visits {
response := &visitResponse{visit: visit}
list = append(list, response)
}
render.RenderList(w, r, list)
}
type visit struct {
Timestamp time.Time `json:"time"`
UserIP string `json:"ip"`
}
type visitResponse struct {
*visit
}
func (vr *visitResponse) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
func recordVisit(ctx context.Context, now time.Time, userIP string) error {
v := &visit{
Timestamp: now,
UserIP: userIP,
}
k := datastore.IncompleteKey("Visit", nil)
_, err := datastoreClient.Put(ctx, k, v)
return err
}
func queryVisits(ctx context.Context, limit int64) ([]*visit, error) {
q := datastore.NewQuery("Visit").Order("-Timestamp").Limit(10)
visits := make([]*visit, 0)
_, err := datastoreClient.GetAll(ctx, q, &visits)
return visits, err
}
実装したコードをはこんな感じ。
Routing、Middleware周りに関しては、chiを使っている。
Jsonのレンダリングには renderを使用した。
renderはレスポンス毎に構造体の定義が必要なので、ちょっとどうかなとかも思うが、どうなんだろう?
この辺はいい感じする方法を知りたい。
最後に
chiは薄いフレームワークなので、ちょうどいいかなと。
というか、webフレームワークというよりルーティングフレームワークっていいってもいいかも。
とりあえずchi使ってこう。