人生は勉強ブログ

黒地に白ってかっこいい。。。

golangのWAF Ginについて調べた

gin

gin-gonic/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言語を一通り読んだ。

スターティングGo言語 (CodeZine BOOKS)

スターティングGo言語 (CodeZine BOOKS)

あまりよくわからないが、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を知らなかったので、ググった。

今更だけどGoのVendoringについて思いをはせる

↑これを読んだ↑

プロジェクトフォルダを作成し、その中に入る。

$ 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")
}

直訳すると、優雅な再起動、停止。

まとめ

一通り読めた。次は、公式ドキュメントを読んでみようと思う。