go-chi/chiの動作確認時に最初に書くコード
個人的には、細々としたコードの動作確認のために小さな1ファイルのコードを書くということを常々やっている。頭があまり良い方ではないので、ドキュメントを読んだだけでは実際の動作を正確に理解することができない。加えて微妙な差異を追加しての比較検討なしに違いを把握することができない機能があったりもする。
go-chi
個人的にgoでのweb APIの実装を試すときにはgo-chi/chiを使っている。
動作確認のための最初のコードをここにメモしておくことにする。
READMEのコードは少し複雑すぎる
readmeの最初のコードは簡潔すぎるし、発展的な例は少し複雑すぎる気がする。提供されている細々とした機能を試すのにこれらを全部コピペというのはあまり嬉しくない。
最初に書くコード
個人的にはこれくらいからはじめていくのが良いと思っている。web APIはjsonを返しておきたい。
package main import ( "log" "net/http" "os" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/render" ) func main() { r := chi.NewRouter() r.Use(middleware.Logger) r.Get("/api", func(w http.ResponseWriter, r *http.Request) { data := map[string]string{ "message": "hello", } render.JSON(w, r, data) }) addr := os.Getenv("Addr") if addr == "" { addr = ":4444" } log.Printf("listen: %s", addr) if err := http.ListenAndServe(addr, r); err != nil { log.Fatalf("!! %+v", err) } }
ポイントはいくつかあって
- JSONを返す一番手軽な方法が知りたい
- request/responseが成功したかを確認したい
- どこにrequestすれば良いか知りたい (待ち受けるportを知りたい)
- portは場合によっては変更したい
- 例とは言えどもerrorハンドリングはまともにしたい
export Addr ?= :44444 00: go run $(shell echo $@*/)main.go
確認はhttpieで。
$ http :4444/api HTTP/1.1 200 OK Content-Length: 20 Content-Type: application/json; charset=utf-8 Date: Tue, 22 Sep 2020 12:19:34 GMT { "message": "hello" }
$ tree . ├── 00hello │ └── main.go ├── Makefile ├── go.mod └── go.sum 1 directory, 4 files
go.modは作っておく場合も多い。
何か変更したかったら00から01を作る感じ。
例えば以下のようなことを考えて対応するコードを埋めていく感じで作業をしている。
- requestのpathからデータを取りたい
- postされたJSONをハンドリングしたい
- NotFound時のデフォルトハンドラーを設定したい
次の作業の例
試しにJSONのハンドリングを例に取ると、以下の様な作業を行う。概ね以下のような流れ。
00の次なので01を作ろう
$ mkdir 01postjson $ cp -r 00/* 01* # edit 01*/main.go
postされたJSONをハンドリングしよう
できたところまで一気に。
--- 00hello/main.go 2020-09-22 21:21:09.000000000 +0900 +++ 01postjson/main.go 2020-09-22 21:30:15.000000000 +0900 @@ -1,6 +1,8 @@ package main import ( + "encoding/json" + "fmt" "log" "net/http" "os" @@ -21,6 +23,24 @@ render.JSON(w, r, data) }) + r.Post("/api/hello", func(w http.ResponseWriter, r *http.Request) { + // {"target": "someone"} + params := map[string]string{} + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(¶ms); err != nil { + render.Status(r, 400) + render.JSON(w, r, map[string]interface{}{ + "error": err.Error(), + }) + } + + defer r.Body.Close() + data := map[string]string{ + "message": fmt.Sprintf("hello %s", params["target"]), + } + render.JSON(w, r, data) + }) + addr := os.Getenv("Addr") if addr == "" { addr = ":4444"
- net/httpをそのまま見る形なので特に悩むことはなくrequest.Bodyを触れば良い
- render.Status()という物がある。
これでなるほどーと思う感じになる(net/httpに寄せるならrenderパッケージは不要だが、content typeの指定などはだるいので省略したい)。
動作確認
動作確認もほぼほぼ同じ様な感じ。
makeにこれを付け足す。
# echo '{"target": "foo"}' | http --json POST :44444/api/hello 01: go run $(shell echo $@*/)main.go
実際に動かしてみる。
server側
$ make 01 go run 01postjson/main.go 2020/09/22 21:30:18 listen: :44444 2020/09/22 21:30:21 "POST http://localhost:44444/api/hello HTTP/1.1" from 127.0.0.1:57526 - 200 24B in 281.307µs
client側
$ echo '{"target": "foo"}' | http --json POST :44444/api/hello HTTP/1.1 200 OK Content-Length: 24 Content-Type: application/json; charset=utf-8 Date: Tue, 22 Sep 2020 12:30:21 GMT { "message": "hello foo" }
と、まぁそういう感じ。1つ試したあとのディレクトリはこういう形。
$ tree . ├── 00hello │ └── main.go ├── 01postjson │ └── main.go ├── Makefile ├── go.mod └── go.sum 2 directories, 5 files
おわりに
こんな感じで細かい1ファイルを作りまくって動作検証をしたりしている日々。
gist
gistのことを考えると、main.goではなくmain00.goのような名前にしたほうが良いかもしれない。
-
exportの利用は複雑なものに関してはオススメされていないがまぁ便利なので。。↩