Gin v1.10.0 の変更点
はじめに
gin-gonic/gin v1.10.0 が2024年5月7日にリリースされました🎉
新しいバージョンでどういう変更が入ったのかリリースページの Changelog のうち 「Features」 と 「Enhancements」 の変更点を調べて一つ一つ解説を入れてみました。普段私が使っていない機能もあるので間違っていることを書いていたらコメント欄でご指摘ください。
🆕 Features
#3877) (@EndlessParadox1)
feat(auth): add proxy-server authentication (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%
#3871) (@RedCrazyGhost)
feat(bind): ShouldBindBodyWith shortcut and change doc (次の 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`)
}
#3933) (@dkkb)
feat(binding): Support custom BindUnmarshaler for binding. (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"
#3514) (@ssfyn)
feat(binding): support override default binding implement (binding.JSON
や binding.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!"}
#3572) (@flc1125)
feat(engine): Added OptionFunc and With (type OptionFunc func(*Engine)
が追加になり、この OptionFunc
を gin.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")
}
#3593) (@palvaneh)
feat(logger): ability to skip logs based on user-defined logic (Gin.logger.LoggerConfig
に type 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
#3595) (@qloog)
chore(CI): update release args (goreleaserの非推奨オプションrm-distをcleanにおきかえました。 gin を利用する側にとっては特に影響のない変更です。
#3839) (@ab)
chore(IP): add TrustedPlatform constant for Fly.io. (TrustedPlatformにFly.ioが追加されました。
#2337) (@josegonzalez)
chore(debug): add ability to override the debugPrint statement (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
#3835) (@appleboy)
chore(deps): update dependencies to latest versions (依存するpackageのバージョンアップを行いました。
#3851) (@vincentbernat)
chore(header): Add support for RFC 9512: application/yaml (RFC 9512 で application/x-yaml
が非推奨となり application/yaml
が採用されたそうです。 Bindするときは application/x-yaml
と application/yaml
の両方を受け入れて、レスポンスの Content-Type
は application/yaml
を返すようになったみたいです。
こちらについては未確認なので、もし間違っていればコメント欄でご指摘ください。
#3741) (@viralparmarme)
chore(http): use white color for HTTP 1XX (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未満の時に白で表示されるようになったみたいです(以前は赤だった)。
#3911) (@1911860538)
chore(optimize): the ShouldBindUri method of the Context struct (ShouldBindUri
でmapを初期化するときに長さをlenを使うように改善されました。
#3859) (@1911860538)
chore(perf): Optimize the Copy method of the Context struct (func (c *Context) Copy()
の処理が最適化されました。
#3855) (@demoManito)
chore(refactor): modify interface check way (interfaceのチェック方法を改善
#3419) (@noahyao1024)
chore(request): check reader if it's nil before reading (func (c *Context) GetRawData() ([]byte, error)
に nil チェックが追加になったみたいです。
#3893) (@Fotkurz)
chore(security): upgrade Protobuf for CVE-2024-24786 (google.golang.org/protobuf
のバージョンが上がりました。
#3848) (@appleboy)
chore: refactor CI and update dependencies (依存packageのバージョンアップと、CIの設定改善されたようです。
#3951) (@appleboy)
chore: refactor configuration files for better readability (.goreleaser.yaml
の設定が変更になりました。 gin を利用する側にとっては特に影響のない変更です。
#3792) (@appleboy)
chore: update GitHub Actions configuration (GitHub Actions configurationの記述が修正されたようです。
#3917) (@appleboy)
chore: update changelog categories and improve documentation (.goreleaser.yaml
の設定が変更になりました。 gin を利用する側にとっては特に影響のない変更です。
#3694) (@appleboy)
chore: update dependencies to latest versions (依存するpackageのバージョンアップを行いました。
#3950) (@appleboy)
chore: update external dependencies to latest versions (依存するpackageのバージョンアップを行いました。
#3901) (@appleboy)
chore: update various Go dependencies to latest versions (依存するpackageのバージョンアップを行いました。
最後に
これで終わりです。
ここで紹介したもの以外にも Bug fixes などが入っているようです。気になる方はginのリリースページをご確認いただければと思います。
Discussion