はじめに
個人開発をしていて、APIをgoで作ることにしました。
ある程度調べて作ってみたので、不慣れですが備忘録にメモしておこうと思います。
何個か作ることになったらテンプレっぽくなったらいいなと思います。
作ったもの
package main
import (
"context"
"flag"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gorilla/mux"
)
var (
listenAddr string
logger = log.New(os.Stdout, "http: ", log.LstdFlags)
wait time.Duration
)
// HealthCheckHandler ...
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
// simple health check
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
io.WriteString(w, `{"alive": true}`)
}
func loggingMIddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Printf("id:%d %s %s %s %s", time.Now().UnixNano(), r.Method, r.RequestURI, r.RemoteAddr, r.UserAgent())
next.ServeHTTP(w, r)
})
}
func main() {
flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
flag.StringVar(&listenAddr, "listen-addr", ":8000", "server listen address")
flag.Parse()
logger.Println("servre is starting ...")
r := mux.NewRouter()
// Add your routes as needed
r.HandleFunc("/health", HealthCheckHandler)
// middleware
r.Use(loggingMIddleware)
server := &http.Server{
Addr: listenAddr,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
if err := server.ListenAndServe(); err != nil {
logger.Fatalf("%s", err)
}
}()
// シグナルを受信するまでブロック
// https://github.com/gorilla/mux#graceful-shutdown
// https://gist.github.com/enricofoltran/10b4a980cd07cb02836f70a4ab3e72d7
logger.Println("server is ready to handle requests at", listenAddr)
<-c
// SIGINT (Ctrl + C) で正常なシャットダウンを行う
// https://qiita.com/TubAnri/items/019f8d19b91f32c878cf
// https://qiita.com/marnie_ms4/items/985d67c4c1b29e11fffc
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// 接続がなくなるまでブロック
server.Shutdown(ctx)
os.Exit(0)
}
フラグ
flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
flag.StringVar(&listenAddr, "listen-addr", ":8000", "server listen address")
flag.Parse()
実行時のコマンドに引数を与えるもの。
http://golang.jp/pkg/flag
ルーター
r := mux.NewRouter()
// Add your routes as needed
r.HandleFunc("/health", HealthCheckHandler)
// middleware
r.Use(loggingMIddleware)
gorilla/muxを使用。ここは直感的でわかりやすい。middlewareも楽チンで実装できる。
https://github.com/gorilla/mux
サーバー
server := &http.Server{
Addr: listenAddr,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
スロー攻撃を避けるために、ここでタイムアウトの指定をしておくことが推奨されている。
https://golang.org/pkg/net/http/#pkg-examples
実際の処理の部分
signal.Notify
go func() {
if err := server.ListenAndServe(); err != nil {
logger.Fatalf("%s", err)
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
出ましたgoroutin。golangをやっている感じになる。
送られてきたシグナルの中で、チャネルに送るものを指定する。
https://golang.org/pkg/os/#Signal
The only signal values guaranteed to be present in the os package on all systems are Interrupt (send the process an interrupt) and Kill (force the process to exit). Interrupt is not implemented on Windows; using it with os.Process.Signal will return an error.
全てのOS共通にあるコマンドは割り込み
と終了
で、割り込み
だった場合チャネルにシグナルを送信する。
ニュアンス的には終了以外って覚えた方が覚えやすいかも。
チャネルを受け取るまで待機
// シグナルを受信するまでブロック
// https://github.com/gorilla/mux#graceful-shutdown
// https://gist.github.com/enricofoltran/10b4a980cd07cb02836f70a4ab3e72d7
logger.Println("server is ready to handle requests at", listenAddr)
<-c
golangっぽくて楽しい。
context.WithTimeout
// SIGINT (Ctrl + C) で正常なシャットダウンを行う
// https://qiita.com/TubAnri/items/019f8d19b91f32c878cf
// https://qiita.com/marnie_ms4/items/985d67c4c1b29e11fffc
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// 接続がなくなるまでブロック
server.Shutdown(ctx)
os.Exit(0)
終了するときはここで一旦処理を受け取って、アクセスがなくなるまで待ってからサーバーをシャットダウンするようにしている?という解釈であっているだろうか?
https://qiita.com/TubAnri/items/019f8d19b91f32c878cf
https://qiita.com/marnie_ms4/items/985d67c4c1b29e11fffc
最後に
golangここまで触ったのは初めてでしたが、ちょっと慣れればとてもわかりやすいですね。
この調子で開発を続けていきたいと思います。
なんかおかしいところがあったらぜひ教えていただきたいです。🙏