From 9e5672da25d4ea9fe6252b4dba493ae531a0e058 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 28 May 2020 16:20:58 +0300 Subject: [PATCH] add a new simple, builtin requestid middleware (makes use of the Context.SetID/GetID methods too) Former-commit-id: d46bce7c1964adada01934aa95daf389c141defc --- HISTORY.md | 2 +- NOTICE | 10 +-- .../cors/simple/client/main.go | 2 +- _examples/tutorial/url-shortener/factory.go | 4 +- _examples/tutorial/url-shortener/main.go | 2 +- context/context.go | 14 ++-- go.mod | 2 +- middleware/README.md | 2 + middleware/jwt/jwt.go | 2 +- middleware/requestid/requestid.go | 82 +++++++++++++++++++ middleware/requestid/requestid_test.go | 63 ++++++++++++++ sessions/config.go | 4 +- sessions/sessions.go | 2 +- 13 files changed, 170 insertions(+), 21 deletions(-) create mode 100644 middleware/requestid/requestid.go create mode 100644 middleware/requestid/requestid_test.go diff --git a/HISTORY.md b/HISTORY.md index 10a169a2..dd3d2023 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -380,7 +380,7 @@ Other Improvements: - Enhanced cookie security and management through new `Context.AddCookieOptions` method and new cookie options (look on New Package-level functions section below), [securecookie](https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie) example has been updated. - `Context.RemoveCookie` removes also the Request's specific cookie of the same request lifecycle when `iris.CookieAllowReclaim` is set to cookie options, [example](https://github.com/kataras/iris/tree/master/_examples/cookies/options). -- `iris.TLS` can now accept certificates as raw contents too. +- `iris.TLS` can now accept certificates in form of raw `[]byte` contents too. - `iris.TLS` registers a secondary http server which redirects "http://" to their "https://" equivalent requests, unless the new `iris.TLSNoRedirect` host Configurator is provided on `iris.TLS` (or `iris.AutoTLS`), e.g. `app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key", iris.TLSNoRedirect))`. - Fix an [issue](https://github.com/kataras/i18n/issues/1) about i18n loading from path which contains potential language code. diff --git a/NOTICE b/NOTICE index 9e55f3a0..f664b4ed 100644 --- a/NOTICE +++ b/NOTICE @@ -37,10 +37,7 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911 d132d2847a go-version 2b13044f5cdd383 https://github.com/hashicorp/go-version 3370d41ce57d8bf - 3cec5e62b8 - go.uuid 36e9d2ebbde5e3f https://github.com/iris-contrib/go.uuid - 13ab2e25625fd45 - 3271d6522e + 3cec5e62b8 golog 4038abff49248cc https://github.com/kataras/golog fe2043e2e763923 d612998bea @@ -97,4 +94,7 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911 b7bbc7f005 jose d84c719419c2a90 https://github.com/square/go-jose 8d188ea67e09652 - f5c1929ae8 \ No newline at end of file + f5c1929ae8 + uuid cb32006e483f2a2 https://github.com/google/uuid + 3230e24209cf185 + c65b477dbf diff --git a/_examples/experimental-handlers/cors/simple/client/main.go b/_examples/experimental-handlers/cors/simple/client/main.go index b0196965..5b941ee4 100644 --- a/_examples/experimental-handlers/cors/simple/client/main.go +++ b/_examples/experimental-handlers/cors/simple/client/main.go @@ -9,6 +9,6 @@ func main() { }) // Read index.html comments, - // adn then start and navigate to http://localhost:8080. + // and then start and navigate to http://localhost:8080. app.Listen(":8080") } diff --git a/_examples/tutorial/url-shortener/factory.go b/_examples/tutorial/url-shortener/factory.go index 66d6b408..d3e71952 100644 --- a/_examples/tutorial/url-shortener/factory.go +++ b/_examples/tutorial/url-shortener/factory.go @@ -3,7 +3,7 @@ package main import ( "net/url" - "github.com/iris-contrib/go.uuid" + "github.com/google/uuid" ) // Generator the type to generate keys(short urls) @@ -11,7 +11,7 @@ type Generator func() string // DefaultGenerator is the defautl url generator var DefaultGenerator = func() string { - id, _ := uuid.NewV4() + id, _ := uuid.NewRandom() return id.String() } diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go index 4dfd8baa..e71eacde 100644 --- a/_examples/tutorial/url-shortener/main.go +++ b/_examples/tutorial/url-shortener/main.go @@ -3,7 +3,7 @@ // Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7 // // $ go get go.etcd.io/bbolt/... -// $ go get github.com/iris-contrib/go.uuid +// $ go get github.com/google/uuid // $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener // $ go build // $ ./url-shortener diff --git a/context/context.go b/context/context.go index f4815096..122be22e 100644 --- a/context/context.go +++ b/context/context.go @@ -1149,14 +1149,15 @@ type Context interface { // and methods are not available here for the developer's safety. Application() Application - // SetID sets an ID, any value, to the Context. + // SetID sets an ID, any value, to the Request Context. // If possible the "id" should implement a `String() string` method // so it can be rendered on `Context.String` method. // - // See `GetID` too. + // See `GetID` and `middleware/requestid` too. SetID(id interface{}) - // GetID returns the Context's ID. + // GetID returns the Request Context's ID. // It returns nil if not given by a prior `SetID` call. + // See `middleware/requestid` too. GetID() interface{} // String returns the string representation of this request. // @@ -5518,17 +5519,18 @@ func (ctx *context) Application() Application { const idContextKey = "iris.context.id" -// SetID sets an ID, any value, to the Context. +// SetID sets an ID, any value, to the Request Context. // If possible the "id" should implement a `String() string` method // so it can be rendered on `Context.String` method. // -// See `GetID` too. +// See `GetID` and `middleware/requestid` too. func (ctx *context) SetID(id interface{}) { ctx.values.Set(idContextKey, id) } -// GetID returns the Context's ID. +// GetID returns the Request Context's ID. // It returns nil if not given by a prior `SetID` call. +// See `middleware/requestid` too. func (ctx *context) GetID() interface{} { return ctx.values.Get(idContextKey) } diff --git a/go.mod b/go.mod index 87286229..1d80c7f5 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/fatih/structs v1.1.0 github.com/golang/protobuf v1.4.2 github.com/gomodule/redigo v1.8.1 + github.com/google/uuid v1.1.2-0.20200519141726-cb32006e483f github.com/hashicorp/go-version v1.2.0 github.com/iris-contrib/blackfriday v2.0.0+incompatible - github.com/iris-contrib/go.uuid v2.0.0+incompatible github.com/iris-contrib/httpexpect/v2 v2.0.5 github.com/iris-contrib/jade v1.1.4 github.com/iris-contrib/pongo2 v0.0.1 diff --git a/middleware/README.md b/middleware/README.md index 4a3d4392..51fa610a 100644 --- a/middleware/README.md +++ b/middleware/README.md @@ -11,6 +11,8 @@ Builtin Handlers | [hCaptcha](hcaptcha) | [iris/_examples/miscellaneous/recaptcha](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/hcaptcha) | | [recovery](recover) | [iris/_examples/miscellaneous/recover](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/recover) | | [rate](rate) | [iris/_examples/miscellaneous/ratelimit](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/ratelimit) | +| [jwt](jwt) | [iris/_examples/miscellaneous/jwt](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/jwt) | +| [requestid](requestid) | [iris/middleware/requestid/requestid_test.go](https://github.com/kataras/iris/blob/master/_examples/middleware/requestid/requestid_test.go) | Community made ------------ diff --git a/middleware/jwt/jwt.go b/middleware/jwt/jwt.go index ce6857ab..3565ec31 100644 --- a/middleware/jwt/jwt.go +++ b/middleware/jwt/jwt.go @@ -106,7 +106,7 @@ type JWT struct { VerificationKey interface{} // Encrypter is used to, optionally, encrypt the token. - // It is set on `WithExpiration` method. + // It is set on `WithEncryption` method. Encrypter jose.Encrypter // DecriptionKey is used to decrypt the token (private key) DecriptionKey interface{} diff --git a/middleware/requestid/requestid.go b/middleware/requestid/requestid.go new file mode 100644 index 00000000..79da67d7 --- /dev/null +++ b/middleware/requestid/requestid.go @@ -0,0 +1,82 @@ +package requestid + +import ( + "github.com/kataras/iris/v12/context" + + "github.com/google/uuid" +) + +func init() { + context.SetHandlerName("iris/middleware/requestid.*", "iris.request.id") +} + +const xRequestIDHeaderValue = "X-Request-ID" + +// Generator defines the function which should extract or generate +// a Request ID. See `DefaultGenerator` and `New` package-level functions. +type Generator func(ctx context.Context) string + +// DefaultGenerator is the default `Generator` that is used +// when nil is passed on `New` package-level function. +// It extracts the ID from the "X-Request-ID" request header value +// or, if missing, it generates a new UUID(v4) and sets the header and context value. +// +// See `Get` package-level function too. +var DefaultGenerator Generator = func(ctx context.Context) string { + id := ctx.GetHeader(xRequestIDHeaderValue) + + if id == "" { + uid, err := uuid.NewRandom() + if err != nil { + ctx.StopWithStatus(500) + return "" + } + + id = uid.String() + ctx.Header(xRequestIDHeaderValue, id) + } + + return id +} + +// New returns a new request id middleware. +// It accepts an ID Generator. +// The Generator can stop the handlers chain with an error or +// return a valid ID (string). +// If it's nil then the `DefaultGenerator` will be used instead. +func New(gen Generator) context.Handler { + if gen == nil { + gen = DefaultGenerator + } + + return func(ctx context.Context) { + if Get(ctx) != "" { + ctx.Next() + return + } + + id := gen(ctx) + if ctx.IsStopped() { + // ctx.Next checks that + // but we don't want to call SetID if generator failed. + return + } + + ctx.SetID(id) + ctx.Next() + } +} + +// Get returns the Request ID or empty string. +// +// A shortcut of `context.GetID().(string)`. +func Get(ctx context.Context) string { + v := ctx.GetID() + if v != nil { + if id, ok := v.(string); ok { + return id + } + } + + return "" +} diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go new file mode 100644 index 00000000..897033d0 --- /dev/null +++ b/middleware/requestid/requestid_test.go @@ -0,0 +1,63 @@ +package requestid_test + +import ( + "testing" + + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/httptest" + "github.com/kataras/iris/v12/middleware/requestid" +) + +func TestRequestID(t *testing.T) { + app := iris.New() + h := func(ctx iris.Context) { + ctx.WriteString(requestid.Get(ctx)) + } + + def := app.Party("/default") + { + def.Use(requestid.New(nil)) + def.Get("/", h) + } + + const expectedCustomID = "my_id" + custom := app.Party("/custom") + { + customGen := func(ctx context.Context) string { + return expectedCustomID + } + + custom.Use(requestid.New(customGen)) + custom.Get("/", h) + } + + const expectedErrMsg = "no id" + customWithErr := app.Party("/custom_err") + { + customGen := func(ctx context.Context) string { + ctx.StopWithText(iris.StatusUnauthorized, expectedErrMsg) + return "" + } + + customWithErr.Use(requestid.New(customGen)) + customWithErr.Get("/", h) + } + + const expectedCustomIDFromOtherMiddleware = "my custom id" + changeID := app.Party("/custom_change_id") + { + changeID.Use(func(ctx iris.Context) { + ctx.SetID(expectedCustomIDFromOtherMiddleware) + ctx.Next() + }) + changeID.Use(requestid.New(nil)) + changeID.Get("/", h) + } + + e := httptest.New(t, app) + e.GET("/default").Expect().Status(httptest.StatusOK).Body().NotEmpty() + e.GET("/custom").Expect().Status(httptest.StatusOK).Body().Equal(expectedCustomID) + e.GET("/custom_err").Expect().Status(httptest.StatusUnauthorized).Body().Equal(expectedErrMsg) + e.GET("/custom_change_id").Expect().Status(httptest.StatusOK).Body().Equal(expectedCustomIDFromOtherMiddleware) +} diff --git a/sessions/config.go b/sessions/config.go index b958d494..269f214f 100644 --- a/sessions/config.go +++ b/sessions/config.go @@ -5,7 +5,7 @@ import ( "github.com/kataras/iris/v12/context" - uuid "github.com/iris-contrib/go.uuid" + "github.com/google/uuid" ) const ( @@ -71,7 +71,7 @@ func (c Config) Validate() Config { if c.SessionIDGenerator == nil { c.SessionIDGenerator = func(context.Context) string { - id, _ := uuid.NewV4() + id, _ := uuid.NewRandom() return id.String() } } diff --git a/sessions/sessions.go b/sessions/sessions.go index 41ebe6d9..c049d5f1 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -15,7 +15,7 @@ func init() { // on a Context, which returns a *Session, type. // It performs automatic memory cleanup on expired sessions. // It can accept a `Database` for persistence across server restarts. -// A session can set temporarly values (flash messages). +// A session can set temporary values (flash messages). type Sessions struct { config Config provider *provider