gin
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance – up to 40 times faster. If you need smashing performance, get yourself some Gin. https://gin-gonic.github.io/gin/
Ginは、goで書かれた、http web frameworkである martiniのようなAPIをより良いパフォーマンス(最大40倍速い)を発揮する。
とりあえず、スターティングGo言語を一通り読んだ。

- 作者: 松尾愛賀
- 出版社/メーカー: 翔泳社
- 発売日: 2016/04/15
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る
あまりよくわからないが、Ginのことを知るために、githubのドキュメントを読んで見た。 (英語苦手なので、もしおかしなところがあったら、ご指摘をお願いいたします。)
動かしてみる
上のgithubのREADME.mdを参考に、ginを動かしてみる。 また、なんとなく日本語で解説できればする。
# assume the following codes in example.go file $ cat example.go
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
これを実行してみる
$ go run example.go
これを起動した状態で、http://0.0.0.0:8080にアクセスしてみると、
{"message":"pong"}
という出力が得られる。
シェルを見てみると、
# [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. # - using env: export GIN_MODE=release # - using code: gin.SetMode(gin.ReleaseMode) # [GIN-debug] GET /ping --> main.main.func1 (3 handlers) # [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default # [GIN-debug] Listening and serving HTTP on :8080 # [GIN] 2017/08/25 - 17:35:03 | 200 | 185.427µs | 127.0.0.1 | GET /ping # [GIN] 2017/08/25 - 17:35:03 | 404 | 876ns | 127.0.0.1 | GET /favicon.ico
このような出力が得られていることがわかる。
Gin v1. stable
Zero allocation router.
0割り当てルーター?
Still the fastest http router and framework. From routing to writing.
最も速いhttpルーターであり、フレームワーク。ルーティングから描画まで。
Complete suite of unit tests
ユニットテストが揃っている
Battle tested
テストの戦い
API frozen, new releases will not break your code.
APIはフローズンなので、新しいリリースがあっても、コードが壊れることはない。
始め方
start-using-it ←ここをみる。
Govendorのようなvendorを使う
$ go get github.com/kardianos/govendor
まず、goのvendorを知らなかったので、ググった。
↑これを読んだ↑
プロジェクトフォルダを作成し、その中に入る。
$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_"
Vendor init your project and add gin
$ govendor init $ govendor fetch github.com/gin-gonic/gin@v1.2
なるほど。govendorを使うことで、ginをプロジェクトに紐つける。
Copy a starting template inside your project
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
Run your project
$ go run main.go
Build with jsoniter
$ go build -tags=jsoniter .
go言語では通常、json pakcageとして、encoding/jsonを使用しているが、上のコマンドを叩くことで、jsoniterを使用することができる。
API Examples
Using GET, POST, PUT, PATCH, DELETE and OPTIONS
文字通り
func main() { // Disable Console Color // gin.DisableConsoleColor() // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options) // By default it serves on :8080 unless a // PORT environment variable was defined. router.Run() // router.Run(":3000") for a hard coded port }
Parameters in path
func main() { router := gin.Default() // This handler will match /user/john but will not match neither /user/ or /user // /user/john にはマッチするが、/user/ や /user にはマッチしない(当然) // 受け取った文字列をc.Param("name")から取り出している。 router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) }) // However, this one will match /user/john/ and also /user/john/send // If no other routers match /user/john, it will redirect to /user/john/ // 読めばわかるが、 /user/john/ 、 /user/john/send のどちらにもマッチする router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) }) router.Run(":8080") }
このコードは、router := gin.Default()
とあるように上と同じ、a gin router with default middlewareである。
Querystring parameters
func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") c.String(http.StatusOK, "Hello %s %s", firstname, lastname) }) router.Run(":8080") }
これは、/welcomeにアクセスした人のURLの?以降のパラメータを読み込む。
DefaultQueryでなかった場合の、デフォルトを指定している?
c.Request.URL.Query().Get(“lastname”)のショートカットとなっているのは、構造体のキーが一意だから?
Multipart/Urlencoded Form
func main() { router := gin.Default() router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous") c.JSON(200, gin.H{ "status": "posted", "message": message, "nick": nick, }) }) router.Run(":8080") }
URL encodeed formを受け取るもの
Another example: query + post form
# POST /post?id=1234&page=1 HTTP/1.1 # Content-Type: application/x-www-form-urlencoded # name=manu&message=this_is_great
func main() { router := gin.Default() router.POST("/post", func(c *gin.Context) { id := c.Query("id") page := c.DefaultQuery("page", "0") name := c.PostForm("name") message := c.PostForm("message") fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) }) router.Run(":8080") }
QueryとPostFormのどちらからも値を受け取る例
# 出力結果 # id: 1234; page: 1; name: manu; message: this_is_great
Upload files
Single file
func main() { router := gin.Default() router.POST("/upload", func(c *gin.Context) { // single file file, _ := c.FormFile("file") log.Println(file.Filename) // Upload the file to specific dst. // c.SaveUploadedFile(file, dst) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") }
この、formfileっていうところで受け取っている
curlで試すには、
curl -X POST http://localhost:8080/upload \ -F "file=@/Users/appleboy/test.zip" \ -H "Content-Type: multipart/form-data"
とする。
Multiple files
func main() { router := gin.Default() router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() files := form.File["upload[]"] for _, file := range files { log.Println(file.Filename) // Upload the file to specific dst. // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) router.Run(":8080") }
for-rangeでとりだすらしい。
SaveUploadedFile(file, dst)
というコードは、読めばわかるように、ファイルをどこかに保存するためのもの
複数ファイルをcurlするには、以下のようにする。
curl -X POST http://localhost:8080/upload \ -F "upload[]=@/Users/appleboy/test1.zip" \ -F "upload[]=@/Users/appleboy/test2.zip" \ -H "Content-Type: multipart/form-data"
Grouping routes
func main() { router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") }
ルーティングをグルーピングする。
このサンプルコードでは、/v1
と/v2
でルーティングをわけている。
Blank Gin without middleware by default
middlewareを使用しないでGinを使う方法
r := gin.New() // or r := gin.Default() // Default With the Logger and Recovery middleware already attached
Using middleware
middlewareを使用した場合
func main() { // Creates a router without any middleware by default r := gin.New() // Global middleware // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery()) // Per route middleware, you can add as many as you desire. r.GET("/benchmark", MyBenchLogger(), benchEndpoint) // Authorization group // authorized := r.Group("/", AuthRequired()) // exactly the same as: authorized := r.Group("/") // per group middleware! in this case we use the custom created // AuthRequired() middleware just in the "authorized" group. authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint) // nested group testing := authorized.Group("testing") testing.GET("/analytics", analyticsEndpoint) } // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
How to write log file
func main() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() // Logging to a file. f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f) // Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) r.Run(":8080") }
Logging to a file.
となっているところで、ログファイルを指定、ログを記入します、となる。
Model binding and validation
リクエストを、バインドするためには、もデルバインティングを使用する。 Ginでは、JSON, XML及び、standard form valuesをサポートしている。
Ginではバリデーションのために、go-playground/validator.v8を使っている。
// Binding from JSON type Login struct { User string `form:"user" json:"user" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { router := gin.Default() // JSONのバインディング // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login // 構造体 if c.BindJSON(&json) == nil { if json.User == "manu" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } }) // html formからのバインディング // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. if c.Bind(&form) == nil { if form.User == "manu" && form.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } }) // Listen and serve on 0.0.0.0:8080 router.Run(":8080") }
Only Bind Query String
package main import ( "log" "github.com/gin-gonic/gin" ) type Person struct { Name string `form:"name"` Address string `form:"address"` } func main() { route := gin.Default() route.Any("/testing", startPage) route.Run(":8085") } // ここの、bind query functionは、クエリパラメータをバインドするためのもの func startPage(c *gin.Context) { var person Person if c.BindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) } c.String(200, "Success") }
Bind Query String or Post Data
これは、クエリパラメータだけでなく、postデータもバインドできる。(c.Bind)
package main import "log" import "github.com/gin-gonic/gin" import "time" type Person struct { Name string `form:"name"` Address string `form:"address"` Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` } func main() { route := gin.Default() route.GET("/testing", startPage) route.Run(":8085") } func startPage(c *gin.Context) { var person Person // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 if c.Bind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) } c.String(200, "Success") }
Bind HTML checkboxes
go
... type myForm struct { Colors []string `form:"colors[]"` } ... func formHandler(c *gin.Context) { var fakeForm myForm c.Bind(&fakeForm) c.JSON(200, gin.H{"color": fakeForm.Colors}) } ...
form.html
<form action="/" method="POST"> <p>Check some colors</p> <label for="red">Red</label> <input type="checkbox" name="colors[]" value="red" id="red" /> <label for="green">Green</label> <input type="checkbox" name="colors[]" value="green" id="green" /> <label for="blue">Blue</label> <input type="checkbox" name="colors[]" value="blue" id="blue" /> <input type="submit" /> </form>
これの結果は、以下のようになる。
{"color":["red","green","blue"]}
Multipart/Urlencoded binding
package main import ( "github.com/gin-gonic/gin" ) type LoginForm struct { User string `form:"user" binding:"required"` Password string `form:"password" binding:"required"` } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.MustBindWith(&form, binding.Form) // or you can simply use autobinding with Bind method: var form LoginForm // in this case proper binding will be automatically selected // この場合は、適切なバインディングが自動で選択される。 if c.Bind(&form) == nil { if form.User == "user" && form.Password == "password" { c.JSON(200, gin.H{"status": "you are logged in"}) } else { c.JSON(401, gin.H{"status": "unauthorized"}) } } }) router.Run(":8080") }
XML, JSON and YAML rendering
func main() { r := gin.Default() // gin.H is a shortcut for map[string]interface{} r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/moreJSON", func(c *gin.Context) { // You also can use a struct var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 // Note that msg.Name becomes "user" in the JSON // Will output : {"user": "Lena", "Message": "hey", "Number": 123} c.JSON(http.StatusOK, msg) }) r.GET("/someXML", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/someYAML", func(c *gin.Context) { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
これは、以下のコマンドで確認できる。
$ curl -v --form user=user --form password=password http://localhost:8080/login
Serving static files
func main() { router := gin.Default() router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") // Listen and serve on 0.0.0.0:8080 router.Run(":8080") }
SecureJSON
Using SecureJSON to prevent json hijacking. Default prepends “while(1),” to response body if the given struct is array values.
jsonのハイジャックから守るために、SecureJSONをつかう。 デフォルトでは、while(1)が先頭についている。
もし与えられたstructが配列の場合、ボディを応答する。
func main() { r := gin.Default() // You can also use your own secure json prefix // r.SecureJsonPrefix(")]}',\n") r.GET("/someJSON", func(c *gin.Context) { names := []string{"lena", "austin", "foo"} // Will output : while(1);["lena","austin","foo"] c.SecureJSON(http.StatusOK, names) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
Serving static files
ここには何も書いてなかったが、静的なファイルの表示の仕方のことだと思う。
func main() { router := gin.Default() router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") // Listen and serve on 0.0.0.0:8080 router.Run(":8080") }
HTML rendering
Using LoadHTMLGlob() or LoadHTMLFiles()
MVCでいう、controllerからviewの描画を行なっているっぽい。
go
func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", }) }) router.Run(":8080") }
templates/index.tmpl
<html> <h1> {{ .title }} </h1> </html>
Using templates with same name in different directories
異なるディレクトリに入っている、同じ名前のテンプレートを使う。
go
func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run(":8080") }
templates/posts/index.tmpl
{{ define "posts/index.tmpl" }} <html><h1> {{ .title }} </h1> <p>Using posts/index.tmpl</p> </html> {{ end }}
templates/users/index.tmpl
{{ define "users/index.tmpl" }} <html><h1> {{ .title }} </h1> <p>Using users/index.tmpl</p> </html> {{ end }}
そりゃそうか。
Custom Template renderer
You can also use your own html template render
好きなhtml template renderを使うことができる。
import "html/template" func main() { router := gin.Default() html := template.Must(template.ParseFiles("file1", "file2")) router.SetHTMLTemplate(html) router.Run(":8080") }
Custom Delimiters
Delimitersとは、区切り記号。
r := gin.Default() r.Delims("{[{", "}]}") r.LoadHTMLGlob("/path/to/templates"))
なるほど。今までの例では、htmlにgoをうめこむ時に、{{ .title }}
を使用していたが、
別のものを指定することもできるらしい。
Custom Template Funcs
main.go
import ( "fmt" "html/template" "net/http" "time" "github.com/gin-gonic/gin" ) func formatAsDate(t time.Time) string { year, month, day := t.Date() return fmt.Sprintf("%d%02d/%02d", year, month, day) } func main() { router := gin.Default() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) router.LoadHTMLFiles("./fixtures/basic/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) router.Run(":8080") }
raw.tmpl
Date: {[{.now | formatAsDate}]}
Result:
Date: 2017/07/01
さっきのやつの使用例か。
Multitemplate
Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.
Redirects
Issuing a HTTP redirect is easy: Both internal and external locations are supported.
r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") })
Custom Middleware
func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Print(latency) // access the status we are sending status := c.Writer.Status() log.Println(status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
今まではずっと、router := gin.Default()
を使用してきたが、ここでは、結構上で述べたように、
Defaultではないものを使用するやり方が書いてある。
これについては、今後、別のドキュメントを読んで、
ちゃんと理解したい。
Using BasicAuth() middleware
// simulate some private data var secrets = gin.H{ "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, "austin": gin.H{"email": "austin@example.com", "phone": "666"}, "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, } func main() { r := gin.Default() // Group using gin.BasicAuth() middleware // gin.Accounts is a shortcut for map[string]string authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ "foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", })) // /admin/secrets endpoint // hit "localhost:8080/admin/secrets authorized.GET("/secrets", func(c *gin.Context) { // get user, it was set by the BasicAuth middleware user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := secrets[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) } }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
authって書いてあるから、きっと認証関連なんだと思う。
Goroutines inside a middleware
When starting inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.
func main() { r := gin.Default() r.GET("/long_async", func(c *gin.Context) { // create copy to be used inside the goroutine cCp := c.Copy() go func() { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // note that you are using the copied context "cCp", IMPORTANT log.Println("Done! in path " + cCp.Request.URL.Path) }() }) r.GET("/long_sync", func(c *gin.Context) { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // since we are NOT using a goroutine, we do not have to copy the context log.Println("Done! in path " + c.Request.URL.Path) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
並列処理をやっている。要復習。
Custom HTTP configuration
Use http.ListenAndServe() directly, like this:
func main() { router := gin.Default() http.ListenAndServe(":8080", router) }
or
func main() { router := gin.Default() s := &http.Server{ Addr: ":8080", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
ここでは、ListenAndServe ディレクトリを使った例を示している。
Support Let’s Encrypt
example for 1-line LetsEncrypt HTTPS servers.
package main import ( "log" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // Ping handler r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) log.Fatal(autotls.Run(r, "example1.com", "example2.com")) }
ssl通信をサポートしている。autotls?
example for custom autocert manager.
package main import ( "log" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" "golang.org/x/crypto/acme/autocert" ) func main() { r := gin.Default() // Ping handler r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), Cache: autocert.DirCache("/var/www/.cache"), } log.Fatal(autotls.RunWithManager(r, &m)) }
automatic access to certificates from Let’s Encrypt and any other ACME-based CA.
公式ドキュメントには、上のように書いてるので、証明書関連の話。
Graceful restart or stop
router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router)
// +build go1.8 package main import ( "context" "log" "net/http" "os" "os/signal" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") }) srv := &http.Server{ Addr: ":8080", Handler: router, } go func() { // service connections if err := srv.ListenAndServe(); err != nil { log.Printf("listen: %s\n", err) } }() // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) <-quit log.Println("Shutdown Server ...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } log.Println("Server exist") }
直訳すると、優雅な再起動、停止。
まとめ
一通り読めた。次は、公式ドキュメントを読んでみようと思う。