first commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Gin-Gonic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,72 @@
|
||||
# RequestID
|
||||
|
||||
Request ID middleware for Gin Framework. Adds an indentifier to the response using the `X-Request-ID` header. Passes the `X-Request-ID` value back to the caller if it's sent in the request headers.
|
||||
|
||||
Copied from [gin-contrib/requestid](https://github.com/gin-contrib/requestid)
|
||||
|
||||
## Config
|
||||
|
||||
define your custom generator function:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
|
||||
r := gin.New()
|
||||
|
||||
r.Use(
|
||||
requestid.New(
|
||||
requestid.WithGenerator(func() string {
|
||||
return "test"
|
||||
}),
|
||||
requestid.WithCustomHeaderStrKey("your-customer-key"),
|
||||
),
|
||||
)
|
||||
|
||||
// Example ping request.
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitverse.ru/andoma/gin-contrib/requestid"
|
||||
"gitverse.ru/andoma/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
r := gin.New()
|
||||
|
||||
r.Use(requestid.New())
|
||||
|
||||
// Example ping request.
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
How to get the request identifier:
|
||||
|
||||
```go
|
||||
// Example / request.
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "id:"+requestid.Get(c))
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"gitverse.ru/andoma/gin"
|
||||
)
|
||||
|
||||
// Option for queue system
|
||||
type Option func(*config)
|
||||
|
||||
type (
|
||||
Generator func() string
|
||||
Handler func(c *gin.Context, requestID string)
|
||||
)
|
||||
|
||||
type HeaderStrKey string
|
||||
|
||||
// WithGenerator set generator function
|
||||
func WithGenerator(g Generator) Option {
|
||||
return func(cfg *config) {
|
||||
cfg.generator = g
|
||||
}
|
||||
}
|
||||
|
||||
// WithCustomHeaderStrKey set custom header key for request id
|
||||
func WithCustomHeaderStrKey(s HeaderStrKey) Option {
|
||||
return func(cfg *config) {
|
||||
cfg.headerKey = s
|
||||
}
|
||||
}
|
||||
|
||||
// WithHandler set handler function for request id with context
|
||||
func WithHandler(handler Handler) Option {
|
||||
return func(cfg *config) {
|
||||
cfg.handler = handler
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gitverse.ru/andoma/gin"
|
||||
)
|
||||
|
||||
const defaultHeaderKey = "X-Request-ID"
|
||||
|
||||
var headerXRequestID string
|
||||
|
||||
// Config defines the config for RequestID middleware
|
||||
type config struct {
|
||||
// Generator defines a function to generate an ID.
|
||||
// Optional. Default: func() string {
|
||||
// return uuid.New().String()
|
||||
// }
|
||||
generator Generator
|
||||
headerKey HeaderStrKey
|
||||
handler Handler
|
||||
}
|
||||
|
||||
// New initializes the RequestID middleware.
|
||||
func New(opts ...Option) gin.HandlerFunc {
|
||||
cfg := &config{
|
||||
generator: func() string {
|
||||
return uuid.New().String()
|
||||
},
|
||||
headerKey: defaultHeaderKey,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
headerXRequestID = string(cfg.headerKey)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
// Get id from request
|
||||
rid := c.GetHeader(headerXRequestID)
|
||||
if rid == "" {
|
||||
rid = cfg.generator()
|
||||
c.Request.Header.Add(headerXRequestID, rid)
|
||||
}
|
||||
if cfg.handler != nil {
|
||||
cfg.handler(c, rid)
|
||||
}
|
||||
// Set the id to ensure that the requestid is in the response
|
||||
c.Header(headerXRequestID, rid)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the request identifier
|
||||
func Get(c *gin.Context) string {
|
||||
return c.GetHeader(headerXRequestID)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitverse.ru/andoma/gin"
|
||||
)
|
||||
|
||||
const testXRequestID = "test-request-id"
|
||||
|
||||
func emptySuccessResponse(c *gin.Context) {
|
||||
c.String(http.StatusOK, "")
|
||||
}
|
||||
|
||||
func Test_RequestID_CreateNew(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(New())
|
||||
r.GET("/", emptySuccessResponse)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.NotEmpty(t, w.Header().Get(headerXRequestID))
|
||||
}
|
||||
|
||||
func Test_RequestID_PassThru(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(New())
|
||||
r.GET("/", emptySuccessResponse)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
req.Header.Set(headerXRequestID, testXRequestID)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, testXRequestID, w.Header().Get(headerXRequestID))
|
||||
}
|
||||
|
||||
func TestRequestIDWithCustomID(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(
|
||||
New(
|
||||
WithGenerator(func() string {
|
||||
return testXRequestID
|
||||
}),
|
||||
),
|
||||
)
|
||||
r.GET("/", emptySuccessResponse)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, testXRequestID, w.Header().Get(headerXRequestID))
|
||||
}
|
||||
|
||||
func TestRequestIDWithCustomHeaderKey(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(
|
||||
New(
|
||||
WithCustomHeaderStrKey("customKey"),
|
||||
),
|
||||
)
|
||||
r.GET("/", emptySuccessResponse)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
req.Header.Set("customKey", testXRequestID)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, testXRequestID, w.Header().Get("customKey"))
|
||||
}
|
||||
|
||||
func TestRequestIDWithHandler(t *testing.T) {
|
||||
r := gin.New()
|
||||
called := false
|
||||
r.Use(
|
||||
New(
|
||||
WithHandler(func(c *gin.Context, requestID string) {
|
||||
called = true
|
||||
assert.Equal(t, testXRequestID, requestID)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
req.Header.Set("X-Request-ID", testXRequestID)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called)
|
||||
}
|
||||
|
||||
func TestRequestIDIsAttachedToRequestHeaders(t *testing.T) {
|
||||
r := gin.New()
|
||||
|
||||
r.Use(New())
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
result := c.GetHeader(defaultHeaderKey)
|
||||
assert.NotEmpty(t, result)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func TestRequestIDNotNilAfterGinCopy(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(New())
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
copy := c.Copy()
|
||||
result := Get(copy)
|
||||
assert.NotEmpty(t, result)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
}
|
||||
Reference in New Issue
Block a user