🥃

Gin v1.10.0 の変更点

2024/06/14に公開

はじめに

gin-gonic/gin v1.10.0 が2024年5月7日にリリースされました🎉

新しいバージョンでどういう変更が入ったのかリリースページの Changelog のうち 「Features」 と 「Enhancements」 の変更点を調べて一つ一つ解説を入れてみました。普段私が使っていない機能もあるので間違っていることを書いていたらコメント欄でご指摘ください。

https://github.com/gin-gonic/gin/releases/tag/v1.10.0

🆕 Features

feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1)

gin.BasicAuthForProxy() が追加になりました。これによってginをproxyとして使っている場合、 Proxy-Authorization ヘッダーを使ってBasic認証をできるようになります。

ちなみに Proxy Authentication はこの図のように認証を行います。


引用元: https://www.oreilly.com/library/view/http-the-definitive/1565925092/ch06s07.html

gin.BasicAuthForProxy() は次のように使います。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	accounts := gin.Accounts{"foo": "bar"}
	router := gin.New()
	router.Use(gin.BasicAuthForProxy(accounts, ""))
	router.Any("/*proxyPath", func(c *gin.Context) {
		c.String(http.StatusOK, c.MustGet(gin.AuthProxyUserKey).(string))
	})
	router.Run(":8080")
}

そして次のようにアクセスすると認証が通ります。

$ account="Basic $(echo -n 'foo:bar' | base64)"

$ echo $account
Basic Zm9vOmJhcg==

$ curl localhost:8080 -i -H "Proxy-Authorization: $account"
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 12 Jun 2024 00:23:25 GMT
Content-Length: 3

foo%

feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost)

次の ShouldBindBodyWith のショートカットが追加になりました。

  • ShouldBindBodyWithJSON
  • ShouldBindBodyWithXML
  • ShouldBindBodyWithYAML
  • ShouldBindBodyWithTOML

元々次のように書いていたものが、

    if err := c.ShouldBindBodyWith(&obj, binding.JSON); err == nil {
        c.String(http.StatusOK, `OK`)
    }

次のようにちょっと楽に書けるようになりました。

    if err := c.ShouldBindBodyWithJSON(&obj); err == nil {
        c.String(http.StatusOK, `OK`)
    }

feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb)

Bind時のカスタムアンマーシャリングがサポートされました。
具体的には UnmarshalParam というメソッドを定義しておくと、Bind時に実行されるようになりました。

次のサンプルコードを実行して、

package main

import (
	"strings"

	"github.com/gin-gonic/gin"
)

type Birthday string

func (b *Birthday) UnmarshalParam(param string) error {
	*b = Birthday(strings.Replace(param, "-", "/", -1))
	return nil
}

func main() {
	route := gin.Default()
	var request struct {
		Birthday Birthday `form:"birthday"`
	}
	route.GET("/test", func(ctx *gin.Context) {
		_ = ctx.BindQuery(&request)
		ctx.JSON(200, request.Birthday)
	})
	route.Run(":8088")
}

次のようにリクエストすると UnmarshalParam が実行されます。今回の例では -/ に置き換わっているので UnmarshalParam が実行された事が確認できます。

$ curl 'localhost:8088/test?birthday=2000-01-01'
"2000/01/01"

feat(binding): support override default binding implement (#3514) (@ssfyn)

binding.JSONbinding.Uri が struct ではな くinterface になりました。

PRに使い道の解説がないので詳細は不明ですが、やろうとしたらbinding.JSONを上書きできるようになったのでかなり無茶なことも実装できるようになったと思います。

次は実際に上書きした例になりますが、実際にはこんなことはしないと思います。
もしも有用な利用例があればコメント欄で教えてください。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

type myBindingJSON struct{}

func (myBindingJSON) Name() string {
	return "json"
}
func (myBindingJSON) Bind(req *http.Request, obj any) error {
	if o, ok := obj.(*Req); ok {
		o.User = "override!"
	}
	return nil
}
func (myBindingJSON) BindBody(body []byte, obj any) error {
	return nil
}

type Req struct {
	User string `json:"user" binding:"required"`
}

func main() {
	route := gin.Default()
	binding.JSON = myBindingJSON{} // 上書きできる
	route.POST("/", func(c *gin.Context) {
		var json Req
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": json.User})
	})
	route.Run(":8080")
}

実際にリクエストを投げてみると myBindingJSON によって処理を差し込んでいるので "override!" という文字列が返ってくることが確認できます。

$ curl -X POST \
  http://localhost:8080/ \
  -H 'content-type: application/json' \
  -d '{ "user": "test" }'
  {"status":"override!"}

feat(engine): Added OptionFunc and With (#3572) (@flc1125)

type OptionFunc func(*Engine) が追加になり、この OptionFuncgin.Default()gin.New() に渡せるよになりました。また func (engine *Engine) With(opts ...OptionFunc) *Engine が追加になって、ここにも OptionFunc を渡せるようになっているのでGinの設定を柔軟に書けるようになりました。

今までは次のように書いていましたが、

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.New()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run(":8002")
}

OptionFunc を使うと次のように書けます。

package main

import "github.com/gin-gonic/gin"

func main() {
	var fc = func(e *gin.Engine) {
		e.GET("/ping", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"message": "pong",
			})
		})
		e.Use(gin.Recovery())
	}
	r := gin.New(fc)
	r.Run(":8002")
}

また With を使うと次のように書けます。

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.New()
	r.With(func(e *gin.Engine) {
		e.GET("/ping", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"message": "pong",
			})
		})
		e.Use(gin.Recovery())
	})
	r.Run(":8002")
}

feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh)

Gin.logger.LoggerConfigtype Skipper func(c *Context) bool が追加になり、これを使ってログを出さなくすることができるようになります。

今までも SkipPaths を使ってログをスキップできていましたが、パス指定以外の方法でログをスキップできることになります。

次のソースは SkipPaths を使ったログのスキップの実装例です。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.New()

	loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}}

	router.Use(gin.LoggerWithConfig(loggerConfig))
	router.Use(gin.Recovery())
	router.GET("/metrics", func(c *gin.Context) {
		c.Status(http.StatusNotImplemented)
	})
	router.GET("/ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong")
	})
	router.GET("/data", func(c *gin.Context) {
		c.Status(http.StatusNotImplemented)
	})
	router.Run(":8080")
}

ここに次のようにリクエストを送ると、

$ curl http://localhost:8080/metrics
$ curl http://localhost:8080/ping
$ curl http://localhost:8080/data

このように /metrics の場合はログが出ないように制御できていました。

ここに追加で Skip の設定を書くと、

 	loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}}
+	loggerConfig.Skip = func(c *gin.Context) bool {
+		// as an example skip non server side errors
+		return c.Writer.Status() < http.StatusInternalServerError
+	}

/ping の場合もログが出なくなり、パス指定以外の方法でログをスキップすることができます。

✨ Enhancements

chore(CI): update release args (#3595) (@qloog)

goreleaserの非推奨オプションrm-distをcleanにおきかえました。 gin を利用する側にとっては特に影響のない変更です。

chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab)

TrustedPlatformにFly.ioが追加されました。

chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez)

var DebugPrintFunc func(format string, values ...interface{}) が追加になりました。
これを使ってdebugのログに出力されるフォーマットを変更できるようになりました。

今までは次のようなコードを書くと、

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.New()
	router.GET("/", func(c *gin.Context) {
		c.String(http.StatusInternalServerError, "error")
	})
	router.Run(":8080")
}

起動時には次のようなログが出ていました。 [GIN-debug] という部分に注目です。

$ go run ./
[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    /                         --> main.main.func1 (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080

上記に対して DebugPrintFunc を次のように設定すると、

  package main
  
  import (
+ 	"fmt"
  	"net/http"
+ 	"strings"
  
  	"github.com/gin-gonic/gin"
  )
  
  func main() {
+ 	gin.DebugPrintFunc = func(format string, values ...interface{}) {
+ 		if !strings.HasSuffix(format, "\n") {
+ 			format += "\n"
+ 		}
+ 		fmt.Fprintf(gin.DefaultWriter, "[my-debug] "+format, values...)
+ 	}
  	router := gin.New()
  	router.GET("/", func(c *gin.Context) {
  		c.String(http.StatusInternalServerError, "error")
  	})
  	router.Run(":8080")
  }

次のようなログに変わります。 [my-debug] と出力されるようになりました。

$ go run ./
[my-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[my-debug] GET    /                         --> main.main.func2 (1 handlers)
[my-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[my-debug] Listening and serving HTTP on :8080

chore(deps): update dependencies to latest versions (#3835) (@appleboy)

依存するpackageのバージョンアップを行いました。

chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat)

RFC 9512application/x-yaml が非推奨となり application/yaml が採用されたそうです。 Bindするときは application/x-yamlapplication/yaml の両方を受け入れて、レスポンスの Content-Typeapplication/yaml を返すようになったみたいです。

こちらについては未確認なので、もし間違っていればコメント欄でご指摘ください。

chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme)

StatusCodeが200未満の時のログ出力の色が赤から白になりました。

このようなコードに対して、

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.New()
	router.Use(gin.Logger())
	router.GET("/ok", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})
	router.GET("/continue", func(c *gin.Context) {
		c.String(http.StatusContinue, "continue")
	})
	router.Run(":8080")
}

次のようにリクエストすると、

$ curl http://localhost:8080/ok
$ curl http://localhost:8080/continue

次のように200未満の時に白で表示されるようになったみたいです(以前は赤だった)。

chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538)

ShouldBindUri でmapを初期化するときに長さをlenを使うように改善されました。

chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538)

func (c *Context) Copy() の処理が最適化されました。

chore(refactor): modify interface check way (#3855) (@demoManito)

interfaceのチェック方法を改善

chore(request): check reader if it's nil before reading (#3419) (@noahyao1024)

func (c *Context) GetRawData() ([]byte, error) に nil チェックが追加になったみたいです。

chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz)

google.golang.org/protobuf のバージョンが上がりました。

chore: refactor CI and update dependencies (#3848) (@appleboy)

依存packageのバージョンアップと、CIの設定改善されたようです。

chore: refactor configuration files for better readability (#3951) (@appleboy)

.goreleaser.yaml の設定が変更になりました。 gin を利用する側にとっては特に影響のない変更です。

chore: update GitHub Actions configuration (#3792) (@appleboy)

GitHub Actions configurationの記述が修正されたようです。

chore: update changelog categories and improve documentation (#3917) (@appleboy)

.goreleaser.yaml の設定が変更になりました。 gin を利用する側にとっては特に影響のない変更です。

chore: update dependencies to latest versions (#3694) (@appleboy)

依存するpackageのバージョンアップを行いました。

chore: update external dependencies to latest versions (#3950) (@appleboy)

依存するpackageのバージョンアップを行いました。

chore: update various Go dependencies to latest versions (#3901) (@appleboy)

依存するpackageのバージョンアップを行いました。

最後に

これで終わりです。

ここで紹介したもの以外にも Bug fixes などが入っているようです。気になる方はginのリリースページをご確認いただければと思います。

Discussion