From 942f331e523e28b3b7f78c9913e82afc2939e186 Mon Sep 17 00:00:00 2001 From: daz-3ux Date: Thu, 14 Sep 2023 17:17:36 +0800 Subject: [PATCH] feat: add middleware function Signed-off-by: daz-3ux --- go.mod | 1 + go.sum | 1 + internal/dazBlog/dazBlog.go | 7 ++++ internal/pkg/known/README.md | 5 +++ internal/pkg/known/known.go | 11 +++++++ internal/pkg/log/log.go | 26 +++++++++++++++ internal/pkg/middleware/header.go | 48 ++++++++++++++++++++++++++++ internal/pkg/middleware/requestid.go | 33 +++++++++++++++++++ 8 files changed, 132 insertions(+) create mode 100644 internal/pkg/known/README.md create mode 100644 internal/pkg/known/known.go create mode 100644 internal/pkg/middleware/header.go create mode 100644 internal/pkg/middleware/requestid.go diff --git a/go.mod b/go.mod index 8379737..c44be57 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.0 require ( github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.1.2 github.com/gosuri/uitable v0.0.4 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 23304dd..ef50d73 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= diff --git a/internal/dazBlog/dazBlog.go b/internal/dazBlog/dazBlog.go index 0ffd48f..c6f7e46 100644 --- a/internal/dazBlog/dazBlog.go +++ b/internal/dazBlog/dazBlog.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "github.com/Daz-3ux/dBlog/internal/pkg/log" + mw "github.com/Daz-3ux/dBlog/internal/pkg/middleware" "github.com/Daz-3ux/dBlog/pkg/version/verflag" "github.com/gin-gonic/gin" "github.com/spf13/cobra" @@ -83,6 +84,11 @@ func run() error { // create Gin engine g := gin.New() + // gin.Recovery() middleware is used to capture any panics and recover from them + mws := []gin.HandlerFunc{gin.Recovery(), mw.NoCache, mw.Cors, mw.Secure, mw.RequestID()} + + g.Use(mws...) + // register 404 handler g.LoadHTMLGlob("internal/resource/*.html") g.NoRoute(func(c *gin.Context) { @@ -92,6 +98,7 @@ func run() error { // register /healthz handler g.GET("/healthz", func(c *gin.Context) { + log.C(c).Infow("Healthz function called") c.JSON(http.StatusOK, gin.H{"status": "OK"}) }) diff --git a/internal/pkg/known/README.md b/internal/pkg/known/README.md new file mode 100644 index 0000000..de765ef --- /dev/null +++ b/internal/pkg/known/README.md @@ -0,0 +1,5 @@ +## known + +- 放置需要共享的 key + - X-Request-ID 会同时被 `日志` 以及 `gin.Context` 需要 + - 所以将 key 的名字保存在共享包 `known` 中, 便于使用 \ No newline at end of file diff --git a/internal/pkg/known/known.go b/internal/pkg/known/known.go new file mode 100644 index 0000000..c98b6bf --- /dev/null +++ b/internal/pkg/known/known.go @@ -0,0 +1,11 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package known + +const ( + // XRequestIDKey is used to define the key in the Gin context representing the request UUID. + XRequestIDKey = "X-Request-Id" +) diff --git a/internal/pkg/log/log.go b/internal/pkg/log/log.go index 5380cdd..0c7a430 100644 --- a/internal/pkg/log/log.go +++ b/internal/pkg/log/log.go @@ -7,6 +7,8 @@ package log import ( + "context" + "github.com/Daz-3ux/dBlog/internal/pkg/known" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -171,3 +173,27 @@ func Fatalw(msg string, keysAndValues ...interface{}) { func (l *zapLogger) Fatalw(msg string, keysAndValues ...interface{}) { l.z.Sugar().Fatalw(msg, keysAndValues...) } + +// C extracts relevant key-value pairs from the incoming context +// and adds them to the structured logs of the zap.Logger. +func C(ctx context.Context) *zapLogger { + return std.C(ctx) +} + +func (l *zapLogger) C(ctx context.Context) *zapLogger { + lc := l.clone() + + if requestID := ctx.Value(known.XRequestIDKey); requestID != nil { + lc.z = lc.z.With(zap.Any(known.XRequestIDKey, requestID)) + } + + return lc +} + +// clone deep copy the zapLogger +// because the log package is called concurrently by multiple request, +// X-Request-ID is protected against contamination +func (l *zapLogger) clone() *zapLogger { + lc := *l + return &lc +} diff --git a/internal/pkg/middleware/header.go b/internal/pkg/middleware/header.go new file mode 100644 index 0000000..d1e210a --- /dev/null +++ b/internal/pkg/middleware/header.go @@ -0,0 +1,48 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package middleware + +import ( + "github.com/gin-gonic/gin" + "net/http" + "time" +) + +// NoCache is a Gin middleware used to disable client-side caching of HTTP request response +func NoCache(c *gin.Context) { + c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") + c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") + c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) + c.Next() +} + +// Cors is a Gin middleware used to set the headers for OPTIONS requests +// then exit the middleware chain and complete the request (for handling browser cross-origin requests). +func Cors(c *gin.Context) { + if c.Request.Method != "OPTIONS" { + c.Next() + } else { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") + c.Header("Access-Control-Allow-Credentials", "false") + c.Header("Access-Control-Max-Age", "600") + c.Header("ALLOW", "HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS") + c.Header("Content-Type", "application/json") + c.AbortWithStatus(http.StatusNoContent) + } +} + +// Secure is a Gin middleware used to add HTTP headers related to security and resource access +func Secure(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("X-Frame-Options", "DENY") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-XSS-Protection", "1; mode=block") + if c.Request.TLS != nil { + c.Header("Strict-Transport-Security", "max-age=31536000") + } +} diff --git a/internal/pkg/middleware/requestid.go b/internal/pkg/middleware/requestid.go new file mode 100644 index 0000000..9e99cbb --- /dev/null +++ b/internal/pkg/middleware/requestid.go @@ -0,0 +1,33 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package middleware + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func RequestID() gin.HandlerFunc { + return func(c *gin.Context) { + // Get the request id from the Gin context + requestID := c.Request.Header.Get(known.XRequestIDKey) + + // If the request id is empty, set a new one + if requestID == "" { + requestID = uuid.New().String() + } + + // Set the request id to the Gin context + c.Set(known.XRequestIDKey, requestID) + + // Set the request id to the response header + c.Writer.Header().Set(known.XRequestIDKey, requestID) + + // Continue + c.Next() + } +}