search
LoginSignup
8

More than 3 years have passed since last update.

posted at

golangでAPIサーバーを作った

はじめに

個人開発をしていて、APIをgoで作ることにしました。
ある程度調べて作ってみたので、不慣れですが備忘録にメモしておこうと思います。
何個か作ることになったらテンプレっぽくなったらいいなと思います。

作ったもの

main.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ここまで触ったのは初めてでしたが、ちょっと慣れればとてもわかりやすいですね。
この調子で開発を続けていきたいと思います。

なんかおかしいところがあったらぜひ教えていただきたいです。🙏

参考リンク

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8