mirror of
https://github.com/kataras/iris.git
synced 2026-01-04 10:47:20 +00:00
API versioning improvements
Replace the go-version package with a regex-free alternative semver
the result: versioned apis have almost zero performance cost now
thanks @motogo for your kind donation ❤️ - please check your github notifications
This commit is contained in:
@@ -4,16 +4,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/jwt"
|
||||
"github.com/kataras/iris/v12/middleware/jwt"
|
||||
)
|
||||
|
||||
/*
|
||||
Learn how to use any JWT 3rd-party package with Iris.
|
||||
In this example we use the kataras/jwt one.
|
||||
|
||||
Install with:
|
||||
go get -u github.com/kataras/jwt
|
||||
|
||||
Documentation:
|
||||
https://github.com/kataras/jwt#table-of-contents
|
||||
*/
|
||||
@@ -71,6 +65,7 @@ func protected(ctx iris.Context) {
|
||||
|
||||
// Just an example on how you can retrieve all the standard claims (set by jwt.MaxAge, "exp").
|
||||
standardClaims := jwt.GetVerifiedToken(ctx).StandardClaims
|
||||
|
||||
expiresAtString := standardClaims.ExpiresAt().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
||||
timeLeft := standardClaims.Timeleft()
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ func newApp() *iris.Application {
|
||||
{
|
||||
m := mvc.New(dataRouter)
|
||||
|
||||
m.Handle(new(v1Controller), mvc.Version("1"), mvc.Deprecated(opts)) // 1 or 1.0, 1.0.0 ...
|
||||
m.Handle(new(v2Controller), mvc.Version("2.3")) // 2.3 or 2.3.0
|
||||
m.Handle(new(v3Controller), mvc.Version(">=3, <4")) // 3, 3.x, 3.x.x ...
|
||||
m.Handle(new(noVersionController)) // or if missing it will respond with 501 version not found.
|
||||
m.Handle(new(v1Controller), mvc.Version("1.0.0"), mvc.Deprecated(opts))
|
||||
m.Handle(new(v2Controller), mvc.Version("2.3.0"))
|
||||
m.Handle(new(v3Controller), mvc.Version(">=3.0.0 <4.0.0"))
|
||||
m.Handle(new(noVersionController)) // or if missing it will respond with 501 version not found.
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
@@ -12,21 +12,21 @@ func TestVersionedController(t *testing.T) {
|
||||
app := newApp()
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect().
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1.0.0").Expect().
|
||||
Status(iris.StatusOK).Body().Equal("data (v1.x)")
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "2.3.0").Expect().
|
||||
Status(iris.StatusOK).Body().Equal("data (v2.x)")
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "3.1").Expect().
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "3.1.0").Expect().
|
||||
Status(iris.StatusOK).Body().Equal("data (v3.x)")
|
||||
|
||||
// Test invalid version or no version at all.
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "4").Expect().
|
||||
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "4.0.0").Expect().
|
||||
Status(iris.StatusOK).Body().Equal("data")
|
||||
e.GET("/data").Expect().
|
||||
Status(iris.StatusOK).Body().Equal("data")
|
||||
|
||||
// Test Deprecated (v1)
|
||||
ex := e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1.0").Expect()
|
||||
ex := e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1.0.0").Expect()
|
||||
ex.Status(iris.StatusOK).Body().Equal("data (v1.x)")
|
||||
ex.Header("X-API-Warn").Equal(opts.WarnMessage)
|
||||
expectedDateStr := opts.DeprecationDate.Format(app.ConfigurationReadOnly().GetTimeFormat())
|
||||
|
||||
@@ -8,84 +8,93 @@ import (
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
examplePerRoute(app)
|
||||
examplePerParty(app)
|
||||
app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
|
||||
ctx.WriteString(`Root not found handler.
|
||||
This will be applied everywhere except the /api/* requests.`)
|
||||
})
|
||||
|
||||
api := app.Party("/api")
|
||||
// Optional, set version aliases (literal strings).
|
||||
// We use `UseRouter` instead of `Use`
|
||||
// to handle HTTP errors per version, but it's up to you.
|
||||
api.UseRouter(versioning.Aliases(versioning.AliasMap{
|
||||
// If no version provided by the client, default it to the "1.0.0".
|
||||
versioning.Empty: "1.0.0",
|
||||
// If a "latest" version is provided by the client,
|
||||
// set the version to be compared to "3.0.0".
|
||||
"latest": "3.0.0",
|
||||
}))
|
||||
|
||||
/*
|
||||
A version is extracted through the versioning.GetVersion function,
|
||||
request headers:
|
||||
- Accept-Version: 1.0.0
|
||||
- Accept: application/json; version=1.0.0
|
||||
You can customize it by setting a version based on the request context:
|
||||
api.Use(func(ctx *context.Context) {
|
||||
if version := ctx.URLParam("version"); version != "" {
|
||||
SetVersion(ctx, version)
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
})
|
||||
OR: api.Use(versioning.FromQuery("version", ""))
|
||||
*/
|
||||
|
||||
// |----------------|
|
||||
// | The fun begins |
|
||||
// |----------------|
|
||||
|
||||
// Create a new Group, which is a compatible Party,
|
||||
// based on version constraints.
|
||||
v1 := versioning.NewGroup(api, ">=1.0.0 <2.0.0")
|
||||
|
||||
// Optionally, set custom view engine and path
|
||||
// for templates based on the version.
|
||||
v1.RegisterView(iris.HTML("./v1", ".html"))
|
||||
|
||||
// Optionally, set custom error handler(s) based on the version.
|
||||
// Keep in mind that if you do this, you will
|
||||
// have to register error handlers
|
||||
// for the rest of the parties as well.
|
||||
v1.OnErrorCode(iris.StatusNotFound, testError("v1"))
|
||||
|
||||
// Register resources based on the version.
|
||||
v1.Get("/", testHandler("v1"))
|
||||
v1.Get("/render", testView)
|
||||
|
||||
// Do the same for version 2 and version 3,
|
||||
// for the sake of the example.
|
||||
v2 := versioning.NewGroup(api, ">=2.0.0 <3.0.0")
|
||||
v2.RegisterView(iris.HTML("./v2", ".html"))
|
||||
v2.OnErrorCode(iris.StatusNotFound, testError("v2"))
|
||||
v2.Get("/", testHandler("v2"))
|
||||
v2.Get("/render", testView)
|
||||
|
||||
v3 := versioning.NewGroup(api, ">=3.0.0 <4.0.0")
|
||||
v3.RegisterView(iris.HTML("./v3", ".html"))
|
||||
v3.OnErrorCode(iris.StatusNotFound, testError("v3"))
|
||||
v3.Get("/", testHandler("v3"))
|
||||
v3.Get("/render", testView)
|
||||
|
||||
// Read the README.md before any action.
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
// How to test:
|
||||
// Open Postman
|
||||
// GET: localhost:8080/api/cats
|
||||
// Headers[1] = Accept-Version: "1" and repeat with
|
||||
// Headers[1] = Accept-Version: "2.5"
|
||||
// or even "Accept": "application/json; version=2.5"
|
||||
func examplePerRoute(app *iris.Application) {
|
||||
app.Get("/api/cats", versioning.NewMatcher(versioning.Map{
|
||||
"1": catsVersionExactly1Handler,
|
||||
">= 2, < 3": catsV2Handler,
|
||||
versioning.NotFound: versioning.NotFoundHandler,
|
||||
}))
|
||||
func testHandler(v string) iris.Handler {
|
||||
return func(ctx iris.Context) {
|
||||
ctx.JSON(iris.Map{
|
||||
"version": v,
|
||||
"message": "Hello, world!",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// How to test:
|
||||
// Open Postman
|
||||
// GET: localhost:8080/api/users
|
||||
// Headers[1] = Accept-Version: "1.9.9" and repeat with
|
||||
// Headers[1] = Accept-Version: "2.5"
|
||||
//
|
||||
// POST: localhost:8080/api/users/new
|
||||
// Headers[1] = Accept-Version: "1.8.3"
|
||||
//
|
||||
// POST: localhost:8080/api/users
|
||||
// Headers[1] = Accept-Version: "2"
|
||||
func examplePerParty(app *iris.Application) {
|
||||
usersAPI := app.Party("/api/users")
|
||||
// You can customize the way a version is extracting
|
||||
// via middleware, for example:
|
||||
// version url parameter, and, if it's missing we default it to "1".
|
||||
// usersAPI.Use(func(ctx iris.Context) {
|
||||
// versioning.SetVersion(ctx, ctx.URLParamDefault("version", "1"))
|
||||
// ctx.Next()
|
||||
// })
|
||||
// OR:
|
||||
usersAPI.Use(versioning.FromQuery("version", "1"))
|
||||
|
||||
// version 1.
|
||||
usersAPIV1 := versioning.NewGroup(usersAPI, ">= 1, < 2")
|
||||
usersAPIV1.Get("/", func(ctx iris.Context) {
|
||||
ctx.Writef("v1 resource: /api/users handler")
|
||||
})
|
||||
usersAPIV1.Post("/new", func(ctx iris.Context) {
|
||||
ctx.Writef("v1 resource: /api/users/new post handler")
|
||||
})
|
||||
|
||||
// version 2.
|
||||
usersAPIV2 := versioning.NewGroup(usersAPI, ">= 2, < 3")
|
||||
usersAPIV2.Get("/", func(ctx iris.Context) {
|
||||
ctx.Writef("v2 resource: /api/users handler")
|
||||
})
|
||||
usersAPIV2.Post("/", func(ctx iris.Context) {
|
||||
ctx.Writef("v2 resource: /api/users post handler")
|
||||
})
|
||||
|
||||
// version 3, pass it as a common iris.Party.
|
||||
usersAPIV3 := versioning.NewGroup(usersAPI, ">= 3, < 4")
|
||||
registerAPIV3(usersAPIV3)
|
||||
func testError(v string) iris.Handler {
|
||||
return func(ctx iris.Context) {
|
||||
ctx.Writef("not found: %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func catsVersionExactly1Handler(ctx iris.Context) {
|
||||
ctx.Writef("v1 exactly resource: /api/cats handler")
|
||||
}
|
||||
|
||||
func catsV2Handler(ctx iris.Context) {
|
||||
ctx.Writef("v2 resource: /api/cats handler")
|
||||
}
|
||||
|
||||
func registerAPIV3(p iris.Party) {
|
||||
p.Get("/", func(ctx iris.Context) {
|
||||
ctx.Writef("v3 resource: /api/users handler")
|
||||
})
|
||||
// [...]
|
||||
func testView(ctx iris.Context) {
|
||||
ctx.View("index.html")
|
||||
}
|
||||
|
||||
1
_examples/routing/versioning/v1/index.html
Normal file
1
_examples/routing/versioning/v1/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<h1>This is the directory for version 1 templates</h1>
|
||||
1
_examples/routing/versioning/v2/index.html
Normal file
1
_examples/routing/versioning/v2/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<h1>This is the directory for version 2 templates</h1>
|
||||
1
_examples/routing/versioning/v3/index.html
Normal file
1
_examples/routing/versioning/v3/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<h1>This is the directory for version 3 templates</h1>
|
||||
Reference in New Issue
Block a user