mirror of
https://github.com/kataras/iris.git
synced 2025-03-21 11:16:28 +01:00
various improvements and new 'UseOnce' method - read HISTORY.md
This commit is contained in:
parent
5d480dc801
commit
46a3a99adf
|
@ -359,6 +359,10 @@ Response:
|
||||||
|
|
||||||
Other Improvements:
|
Other Improvements:
|
||||||
|
|
||||||
|
- `*versioning.Group` type is a full `Party` now.
|
||||||
|
|
||||||
|
- `Party.UseOnce` - either inserts a middleware, or on the basis of the middleware already existing, replace that existing middleware instead.
|
||||||
|
|
||||||
- Ability to register a view engine per group of routes or for the current chain of handlers through `Party.RegisterView` and `Context.ViewEngine` respectfully.
|
- Ability to register a view engine per group of routes or for the current chain of handlers through `Party.RegisterView` and `Context.ViewEngine` respectfully.
|
||||||
|
|
||||||
- Add [Blocks](_examples/view/template_blocks_0) template engine. <!-- Reminder for @kataras: follow https://github.com/flosch/pongo2/pull/236#issuecomment-668950566 discussion so we can get back on using the original pongo2 repository as they fixed the issue about an incompatible 3rd party package (although they need more fixes, that's why I commented there) -->
|
- Add [Blocks](_examples/view/template_blocks_0) template engine. <!-- Reminder for @kataras: follow https://github.com/flosch/pongo2/pull/236#issuecomment-668950566 discussion so we can get back on using the original pongo2 repository as they fixed the issue about an incompatible 3rd party package (although they need more fixes, that's why I commented there) -->
|
||||||
|
@ -527,6 +531,8 @@ New Context Methods:
|
||||||
|
|
||||||
Breaking Changes:
|
Breaking Changes:
|
||||||
|
|
||||||
|
- `versioning.NewGroup(string)` now accepts a `Party` as its first input argument: `NewGroup(Party, string)`.
|
||||||
|
- `versioning.RegisterGroups` is **removed** as it is no longer necessary.
|
||||||
- `Configuration.RemoteAddrHeaders` from `map[string]bool` to `[]string`. If you used `With(out)RemoteAddrHeader` then you are ready to proceed without any code changes for that one.
|
- `Configuration.RemoteAddrHeaders` from `map[string]bool` to `[]string`. If you used `With(out)RemoteAddrHeader` then you are ready to proceed without any code changes for that one.
|
||||||
- `ctx.Gzip(boolean)` replaced with `ctx.CompressWriter(boolean) error`.
|
- `ctx.Gzip(boolean)` replaced with `ctx.CompressWriter(boolean) error`.
|
||||||
- `ctx.GzipReader(boolean) error` replaced with `ctx.CompressReader(boolean) error`.
|
- `ctx.GzipReader(boolean) error` replaced with `ctx.CompressReader(boolean) error`.
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
* [The `urlpath` tmpl func](view/template_html_3/main.go)
|
* [The `urlpath` tmpl func](view/template_html_3/main.go)
|
||||||
* [The `url` tmpl func](view/template_html_4/main.go)
|
* [The `url` tmpl func](view/template_html_4/main.go)
|
||||||
* [Inject Data Between Handlers](view/context-view-data/main.go)
|
* [Inject Data Between Handlers](view/context-view-data/main.go)
|
||||||
|
* [Inject Engine Between Handlers](view/context-view-engine/main.go)
|
||||||
* [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
|
* [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
|
||||||
* [Write to a custom `io.Writer`](view/write-to)
|
* [Write to a custom `io.Writer`](view/write-to)
|
||||||
* [Blocks](view/template_blocks_0)
|
* [Blocks](view/template_blocks_0)
|
||||||
|
|
|
@ -31,7 +31,7 @@ func newApp() *iris.Application {
|
||||||
m.Handle(new(v1Controller), mvc.Version("1"), mvc.Deprecated(opts)) // 1 or 1.0, 1.0.0 ...
|
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(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(v3Controller), mvc.Version(">=3, <4")) // 3, 3.x, 3.x.x ...
|
||||||
m.Handle(new(noVersionController))
|
m.Handle(new(noVersionController)) // or if missing it will respond with 501 version not found.
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -42,9 +42,16 @@ func examplePerRoute(app *iris.Application) {
|
||||||
// Headers[1] = Accept-Version: "2"
|
// Headers[1] = Accept-Version: "2"
|
||||||
func examplePerParty(app *iris.Application) {
|
func examplePerParty(app *iris.Application) {
|
||||||
usersAPI := app.Party("/api/users")
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
// version 1.
|
// version 1.
|
||||||
usersAPIV1 := versioning.NewGroup(">= 1, < 2")
|
usersAPIV1 := versioning.NewGroup(usersAPI, ">= 1, < 2")
|
||||||
usersAPIV1.Get("/", func(ctx iris.Context) {
|
usersAPIV1.Get("/", func(ctx iris.Context) {
|
||||||
ctx.Writef("v1 resource: /api/users handler")
|
ctx.Writef("v1 resource: /api/users handler")
|
||||||
})
|
})
|
||||||
|
@ -53,15 +60,13 @@ func examplePerParty(app *iris.Application) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// version 2.
|
// version 2.
|
||||||
usersAPIV2 := versioning.NewGroup(">= 2, < 3")
|
usersAPIV2 := versioning.NewGroup(usersAPI, ">= 2, < 3")
|
||||||
usersAPIV2.Get("/", func(ctx iris.Context) {
|
usersAPIV2.Get("/", func(ctx iris.Context) {
|
||||||
ctx.Writef("v2 resource: /api/users handler")
|
ctx.Writef("v2 resource: /api/users handler")
|
||||||
})
|
})
|
||||||
usersAPIV2.Post("/", func(ctx iris.Context) {
|
usersAPIV2.Post("/", func(ctx iris.Context) {
|
||||||
ctx.Writef("v2 resource: /api/users post handler")
|
ctx.Writef("v2 resource: /api/users post handler")
|
||||||
})
|
})
|
||||||
|
|
||||||
versioning.RegisterGroups(usersAPI, versioning.NotFoundHandler, usersAPIV1, usersAPIV2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func catsVersionExactly1Handler(ctx iris.Context) {
|
func catsVersionExactly1Handler(ctx iris.Context) {
|
||||||
|
|
|
@ -303,7 +303,7 @@ var acquireGoroutines = func() interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *Context) Go(fn func(cancelCtx stdContext.Context)) (running int) {
|
func (ctx *Context) Go(fn func(cancelCtx stdContext.Context)) (running int) {
|
||||||
g := ctx.Values().GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines)
|
g := ctx.values.GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines)
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
g.wg.Add(1)
|
g.wg.Add(1)
|
||||||
|
|
||||||
|
@ -613,6 +613,18 @@ func (ctx *Context) StopWithError(statusCode int, err error) {
|
||||||
ctx.StopWithText(statusCode, err.Error())
|
ctx.StopWithText(statusCode, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopWithPlainError like `StopWithError` but it does NOT
|
||||||
|
// write anything to the response writer, it stores the error
|
||||||
|
// so any error handler matching the given "statusCode" can handle it by its own.
|
||||||
|
func (ctx *Context) StopWithPlainError(statusCode int, err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetErr(err)
|
||||||
|
ctx.StopWithStatus(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
// StopWithJSON stops the handlers chain, writes the status code
|
// StopWithJSON stops the handlers chain, writes the status code
|
||||||
// and sends a JSON response.
|
// and sends a JSON response.
|
||||||
//
|
//
|
||||||
|
@ -4459,7 +4471,7 @@ func (ctx *Context) Exec(method string, path string) {
|
||||||
// backup the request path information
|
// backup the request path information
|
||||||
backupPath := req.URL.Path
|
backupPath := req.URL.Path
|
||||||
backupMethod := req.Method
|
backupMethod := req.Method
|
||||||
// don't backupValues := ctx.Values().ReadOnly()
|
// don't backupValues := ctx.values.ReadOnly()
|
||||||
// set the request to be align with the 'againstRequestPath'
|
// set the request to be align with the 'againstRequestPath'
|
||||||
req.RequestURI = path
|
req.RequestURI = path
|
||||||
req.URL.Path = path
|
req.URL.Path = path
|
||||||
|
@ -4548,7 +4560,7 @@ func (ctx *Context) RegisterDependency(v interface{}) {
|
||||||
val = reflect.ValueOf(v)
|
val = reflect.ValueOf(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
cv := ctx.Values().Get(DependenciesContextKey)
|
cv := ctx.values.Get(DependenciesContextKey)
|
||||||
if cv != nil {
|
if cv != nil {
|
||||||
m, ok := cv.(DependenciesMap)
|
m, ok := cv.(DependenciesMap)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -4559,7 +4571,7 @@ func (ctx *Context) RegisterDependency(v interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Values().Set(DependenciesContextKey, DependenciesMap{
|
ctx.values.Set(DependenciesContextKey, DependenciesMap{
|
||||||
val.Type(): val,
|
val.Type(): val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4567,7 +4579,7 @@ func (ctx *Context) RegisterDependency(v interface{}) {
|
||||||
// UnregisterDependency removes a dependency based on its type.
|
// UnregisterDependency removes a dependency based on its type.
|
||||||
// Reports whether a dependency with that type was found and removed successfully.
|
// Reports whether a dependency with that type was found and removed successfully.
|
||||||
func (ctx *Context) UnregisterDependency(typ reflect.Type) bool {
|
func (ctx *Context) UnregisterDependency(typ reflect.Type) bool {
|
||||||
cv := ctx.Values().Get(DependenciesContextKey)
|
cv := ctx.values.Get(DependenciesContextKey)
|
||||||
if cv != nil {
|
if cv != nil {
|
||||||
m, ok := cv.(DependenciesMap)
|
m, ok := cv.(DependenciesMap)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -4594,18 +4606,25 @@ const errorContextKey = "iris.context.error"
|
||||||
// as a context value, it does nothing more.
|
// as a context value, it does nothing more.
|
||||||
// Also, by-default this error's value is written to the client
|
// Also, by-default this error's value is written to the client
|
||||||
// on failures when no registered error handler is available (see `Party.On(Any)ErrorCode`).
|
// on failures when no registered error handler is available (see `Party.On(Any)ErrorCode`).
|
||||||
// See `GetError` to retrieve it back.
|
// See `GetErr` to retrieve it back.
|
||||||
|
//
|
||||||
|
// To remove an error simply pass nil.
|
||||||
//
|
//
|
||||||
// Note that, if you want to stop the chain
|
// Note that, if you want to stop the chain
|
||||||
// with an error see the `StopWithError` instead.
|
// with an error see the `StopWithError/StopWithPlainError` instead.
|
||||||
func (ctx *Context) SetErr(err error) {
|
func (ctx *Context) SetErr(err error) {
|
||||||
ctx.Values().Set(errorContextKey, err)
|
if err == nil {
|
||||||
|
ctx.values.Remove(errorContextKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.values.Set(errorContextKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetErr is a helper which retrieves
|
// GetErr is a helper which retrieves
|
||||||
// the error value stored by `SetErr`.
|
// the error value stored by `SetErr`.
|
||||||
func (ctx *Context) GetErr() error {
|
func (ctx *Context) GetErr() error {
|
||||||
if v := ctx.Values().Get(errorContextKey); v != nil {
|
if v := ctx.values.Get(errorContextKey); v != nil {
|
||||||
if err, ok := v.(error); ok {
|
if err, ok := v.(error); ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,8 @@ func overlapRoute(r *Route, next *Route) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.SetErr(nil) // clear any stored error.
|
||||||
|
// Set the route to the next one and execute it.
|
||||||
ctx.SetCurrentRoute(next.ReadOnly)
|
ctx.SetCurrentRoute(next.ReadOnly)
|
||||||
ctx.HandlerIndex(0)
|
ctx.HandlerIndex(0)
|
||||||
ctx.Do(nextHandlers)
|
ctx.Do(nextHandlers)
|
||||||
|
@ -768,6 +770,25 @@ func (api *APIBuilder) Use(handlers ...context.Handler) {
|
||||||
api.middleware = append(api.middleware, handlers...)
|
api.middleware = append(api.middleware, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UseOnce either inserts a middleware,
|
||||||
|
// or on the basis of the middleware already existing,
|
||||||
|
// replace that existing middleware instead.
|
||||||
|
func (api *APIBuilder) UseOnce(handlers ...context.Handler) {
|
||||||
|
reg:
|
||||||
|
for _, handler := range handlers {
|
||||||
|
name := context.HandlerName(handler)
|
||||||
|
for i, registeredHandler := range api.middleware {
|
||||||
|
registeredName := context.HandlerName(registeredHandler)
|
||||||
|
if name == registeredName {
|
||||||
|
api.middleware[i] = handler // replace this handler with the new one.
|
||||||
|
continue reg // break and continue to the next handler.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.middleware = append(api.middleware, handler) // or just insert it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UseGlobal registers handlers that should run at the very beginning.
|
// UseGlobal registers handlers that should run at the very beginning.
|
||||||
// It prepends those handler(s) to all routes,
|
// It prepends those handler(s) to all routes,
|
||||||
// including all parties, subdomains.
|
// including all parties, subdomains.
|
||||||
|
|
|
@ -46,7 +46,7 @@ type Attachments struct {
|
||||||
type DirCacheOptions struct {
|
type DirCacheOptions struct {
|
||||||
// Enable or disable cache.
|
// Enable or disable cache.
|
||||||
Enable bool
|
Enable bool
|
||||||
// Minimium content size for compression in bytes.
|
// Minimum content size for compression in bytes.
|
||||||
CompressMinSize int64
|
CompressMinSize int64
|
||||||
// Ignore compress files that match this pattern.
|
// Ignore compress files that match this pattern.
|
||||||
CompressIgnore *regexp.Regexp
|
CompressIgnore *regexp.Regexp
|
||||||
|
|
|
@ -76,9 +76,14 @@ type Party interface {
|
||||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||||
Use(middleware ...context.Handler)
|
Use(middleware ...context.Handler)
|
||||||
|
// UseOnce either inserts a middleware,
|
||||||
|
// or on the basis of the middleware already existing,
|
||||||
|
// replace that existing middleware instead.
|
||||||
|
UseOnce(handlers ...context.Handler)
|
||||||
// Done appends to the very end, Handler(s) to the current Party's routes and child routes.
|
// Done appends to the very end, Handler(s) to the current Party's routes and child routes.
|
||||||
// The difference from .Use is that this/or these Handler(s) are being always running last.
|
// The difference from .Use is that this/or these Handler(s) are being always running last.
|
||||||
Done(handlers ...context.Handler)
|
Done(handlers ...context.Handler)
|
||||||
|
|
||||||
// Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`,
|
// Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`,
|
||||||
// and the execution rules.
|
// and the execution rules.
|
||||||
// Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`.
|
// Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`.
|
||||||
|
|
|
@ -25,15 +25,8 @@ import (
|
||||||
func Version(version string) OptionFunc {
|
func Version(version string) OptionFunc {
|
||||||
return func(c *ControllerActivator) {
|
return func(c *ControllerActivator) {
|
||||||
c.Router().SetRegisterRule(router.RouteOverlap) // required for this feature.
|
c.Router().SetRegisterRule(router.RouteOverlap) // required for this feature.
|
||||||
|
// Note: Do not use a group, we need c.Use for the specific controller's routes.
|
||||||
c.Use(func(ctx *context.Context) {
|
c.Use(versioning.Handler(version))
|
||||||
if !versioning.Match(ctx, version) {
|
|
||||||
ctx.StopExecution()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Next()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,185 +1,46 @@
|
||||||
package versioning
|
package versioning
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/context"
|
"github.com/kataras/iris/v12/context"
|
||||||
"github.com/kataras/iris/v12/core/router"
|
"github.com/kataras/iris/v12/core/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// Group is a group of version-based routes.
|
||||||
vroute struct {
|
// One version per one or more routes.
|
||||||
method string
|
type Group struct {
|
||||||
path string
|
router.Party
|
||||||
versions Map
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group is a group of version-based routes.
|
// Information not currently in-use.
|
||||||
// One version per one or more routes.
|
version string
|
||||||
Group struct {
|
deprecation DeprecationOptions
|
||||||
version string
|
}
|
||||||
extraMethods []string
|
|
||||||
routes []vroute
|
|
||||||
|
|
||||||
deprecation DeprecationOptions
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewGroup returns a ptr to Group based on the given "version".
|
// NewGroup returns a ptr to Group based on the given "version".
|
||||||
|
// It sets the API Version for the "r" Party.
|
||||||
//
|
//
|
||||||
// See `Handle` and `RegisterGroups` for more.
|
// See `Handle` and `RegisterGroups` for more.
|
||||||
func NewGroup(version string) *Group {
|
func NewGroup(r router.Party, version string) *Group {
|
||||||
|
// Note that this feature alters the RouteRegisterRule to RouteOverlap
|
||||||
|
// the RouteOverlap rule does not contain any performance downside
|
||||||
|
// but it's good to know that if you registered other mode, this wanna change it.
|
||||||
|
r.SetRegisterRule(router.RouteOverlap)
|
||||||
|
r.UseOnce(Handler(version)) // this is required in order to not populate this middleware to the next group.
|
||||||
|
|
||||||
return &Group{
|
return &Group{
|
||||||
|
Party: r,
|
||||||
version: version,
|
version: version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated marks this group and all its versioned routes
|
// Deprecated marks this group and all its versioned routes
|
||||||
// as deprecated versions of that endpoint.
|
// as deprecated versions of that endpoint.
|
||||||
// It can be called in the end just before `RegisterGroups`
|
|
||||||
// or first by `NewGroup(...).Deprecated(...)`. It returns itself.
|
|
||||||
func (g *Group) Deprecated(options DeprecationOptions) *Group {
|
func (g *Group) Deprecated(options DeprecationOptions) *Group {
|
||||||
// if `Deprecated` is called in the end.
|
// store it for future use, e.g. collect all deprecated APIs and notify the developer.
|
||||||
for _, r := range g.routes {
|
|
||||||
r.versions[g.version] = Deprecated(r.versions[g.version], options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the options if called before registering any versioned routes.
|
|
||||||
g.deprecation = options
|
g.deprecation = options
|
||||||
|
|
||||||
return g
|
g.Party.UseOnce(func(ctx *context.Context) {
|
||||||
}
|
WriteDeprecated(ctx, options)
|
||||||
|
ctx.Next()
|
||||||
// AllowMethods can be called before `Handle/Get/Post...`
|
|
||||||
// to tell the underline router that all routes should be registered
|
|
||||||
// to these "methods" as well.
|
|
||||||
func (g *Group) AllowMethods(methods ...string) *Group {
|
|
||||||
g.extraMethods = append(g.extraMethods, methods...)
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) addVRoute(method, path string, handler context.Handler) {
|
|
||||||
for _, r := range g.routes { // check if route already exists.
|
|
||||||
if r.method == method && r.path == path {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.routes = append(g.routes, vroute{
|
|
||||||
method: method,
|
|
||||||
path: path,
|
|
||||||
versions: Map{g.version: handler},
|
|
||||||
})
|
})
|
||||||
}
|
return g
|
||||||
|
|
||||||
// Handle registers a versioned route to the group.
|
|
||||||
// A call of `RegisterGroups` is necessary in order to register the actual routes
|
|
||||||
// when the group is complete.
|
|
||||||
//
|
|
||||||
// `RegisterGroups` for more.
|
|
||||||
func (g *Group) Handle(method string, path string, handler context.Handler) {
|
|
||||||
if g.deprecation.ShouldHandle() { // if `Deprecated` called first.
|
|
||||||
handler = Deprecated(handler, g.deprecation)
|
|
||||||
}
|
|
||||||
|
|
||||||
methods := append(g.extraMethods, method)
|
|
||||||
|
|
||||||
for _, method := range methods {
|
|
||||||
g.addVRoute(method, path, handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// None registers an "offline" versioned route
|
|
||||||
// see `context#ExecRoute(routeName)` and routing examples.
|
|
||||||
func (g *Group) None(path string, handler context.Handler) {
|
|
||||||
g.Handle(router.MethodNone, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get registers a versioned route for the Get http method.
|
|
||||||
func (g *Group) Get(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodGet, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post registers a versioned route for the Post http method.
|
|
||||||
func (g *Group) Post(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodPost, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put registers a versioned route for the Put http method
|
|
||||||
func (g *Group) Put(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodPut, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete registers a versioned route for the Delete http method.
|
|
||||||
func (g *Group) Delete(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodDelete, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect registers a versioned route for the Connect http method.
|
|
||||||
func (g *Group) Connect(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodConnect, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head registers a versioned route for the Head http method.
|
|
||||||
func (g *Group) Head(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodHead, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options registers a versioned route for the Options http method.
|
|
||||||
func (g *Group) Options(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodOptions, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch registers a versioned route for the Patch http method.
|
|
||||||
func (g *Group) Patch(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodPatch, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace registers a versioned route for the Trace http method.
|
|
||||||
func (g *Group) Trace(path string, handler context.Handler) {
|
|
||||||
g.Handle(http.MethodTrace, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any registers a versioned route for ALL of the http methods
|
|
||||||
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
|
|
||||||
func (g *Group) Any(registeredPath string, handler context.Handler) {
|
|
||||||
g.Get(registeredPath, handler)
|
|
||||||
g.Post(registeredPath, handler)
|
|
||||||
g.Put(registeredPath, handler)
|
|
||||||
g.Delete(registeredPath, handler)
|
|
||||||
g.Connect(registeredPath, handler)
|
|
||||||
g.Head(registeredPath, handler)
|
|
||||||
g.Options(registeredPath, handler)
|
|
||||||
g.Patch(registeredPath, handler)
|
|
||||||
g.Trace(registeredPath, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterGroups registers one or more groups to an `iris.Party` or to the root router.
|
|
||||||
// See `NewGroup` and `NotFoundHandler` too.
|
|
||||||
func RegisterGroups(r router.Party, notFoundHandler context.Handler, groups ...*Group) (actualRoutes []*router.Route) {
|
|
||||||
var total []vroute
|
|
||||||
for _, g := range groups {
|
|
||||||
inner:
|
|
||||||
for _, r := range g.routes {
|
|
||||||
for i, tr := range total {
|
|
||||||
if tr.method == r.method && tr.path == r.path {
|
|
||||||
total[i].versions[g.version] = r.versions[g.version]
|
|
||||||
continue inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total = append(total, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vr := range total {
|
|
||||||
if notFoundHandler != nil {
|
|
||||||
vr.versions[NotFound] = notFoundHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
route := r.Handle(vr.method, vr.path, NewMatcher(vr.versions))
|
|
||||||
actualRoutes = append(actualRoutes, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package versioning
|
package versioning
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/context"
|
"github.com/kataras/iris/v12/context"
|
||||||
|
@ -31,6 +32,10 @@ const (
|
||||||
NotFound = "iris.api.version.notfound"
|
NotFound = "iris.api.version.notfound"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrNotFound reports whether a requested version
|
||||||
|
// does not match with any of the server's implemented ones.
|
||||||
|
var ErrNotFound = errors.New("version not found")
|
||||||
|
|
||||||
// NotFoundHandler is the default version not found handler that
|
// NotFoundHandler is the default version not found handler that
|
||||||
// is executed from `NewMatcher` when no version is registered as available to dispatch a resource.
|
// is executed from `NewMatcher` when no version is registered as available to dispatch a resource.
|
||||||
var NotFoundHandler = func(ctx *context.Context) {
|
var NotFoundHandler = func(ctx *context.Context) {
|
||||||
|
@ -46,8 +51,7 @@ var NotFoundHandler = func(ctx *context.Context) {
|
||||||
recognize the request method and is not capable of supporting it for any resource.
|
recognize the request method and is not capable of supporting it for any resource.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ctx.StatusCode(501)
|
ctx.StopWithPlainError(501, ErrNotFound)
|
||||||
ctx.WriteString("version not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVersion returns the current request version.
|
// GetVersion returns the current request version.
|
||||||
|
|
|
@ -44,6 +44,22 @@ func Match(ctx *context.Context, expectedVersion string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler returns a handler which stop the execution
|
||||||
|
// when the given "version" does not match with the requested one.
|
||||||
|
func Handler(version string) context.Handler {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
if !Match(ctx, version) {
|
||||||
|
// Any overlapped handler
|
||||||
|
// can just clear the status code
|
||||||
|
// and the error to ignore this (see `NewGroup`).
|
||||||
|
NotFoundHandler(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map is a map of versions targets to a handlers,
|
// Map is a map of versions targets to a handlers,
|
||||||
// a handler per version or constraint, the key can be something like ">1, <=2" or just "1".
|
// a handler per version or constraint, the key can be something like ">1, <=2" or just "1".
|
||||||
type Map map[string]context.Handler
|
type Map map[string]context.Handler
|
||||||
|
|
|
@ -79,7 +79,7 @@ func TestNewGroup(t *testing.T) {
|
||||||
userAPI := app.Party("/api/user")
|
userAPI := app.Party("/api/user")
|
||||||
// [... static serving, middlewares and etc goes here].
|
// [... static serving, middlewares and etc goes here].
|
||||||
|
|
||||||
userAPIV10 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)
|
userAPIV10 := versioning.NewGroup(userAPI, "1.0").Deprecated(versioning.DefaultDeprecationOptions)
|
||||||
// V10middlewareResponse := "m1"
|
// V10middlewareResponse := "m1"
|
||||||
// userAPIV10.Use(func(ctx iris.Context) {
|
// userAPIV10.Use(func(ctx iris.Context) {
|
||||||
// println("exec userAPIV10.Use - midl1")
|
// println("exec userAPIV10.Use - midl1")
|
||||||
|
@ -97,7 +97,7 @@ func TestNewGroup(t *testing.T) {
|
||||||
// })
|
// })
|
||||||
|
|
||||||
userAPIV10.Get("/", sendHandler(v10Response))
|
userAPIV10.Get("/", sendHandler(v10Response))
|
||||||
userAPIV2 := versioning.NewGroup(">= 2, < 3")
|
userAPIV2 := versioning.NewGroup(userAPI, ">= 2, < 3")
|
||||||
// V2middlewareResponse := "m2"
|
// V2middlewareResponse := "m2"
|
||||||
// userAPIV2.Use(func(ctx iris.Context) {
|
// userAPIV2.Use(func(ctx iris.Context) {
|
||||||
// println("exec userAPIV2.Use - midl1")
|
// println("exec userAPIV2.Use - midl1")
|
||||||
|
@ -113,8 +113,6 @@ func TestNewGroup(t *testing.T) {
|
||||||
userAPIV2.Post("/", sendHandler(v2Response))
|
userAPIV2.Post("/", sendHandler(v2Response))
|
||||||
userAPIV2.Put("/other", sendHandler(v2Response))
|
userAPIV2.Put("/other", sendHandler(v2Response))
|
||||||
|
|
||||||
versioning.RegisterGroups(userAPI, versioning.NotFoundHandler, userAPIV10, userAPIV2)
|
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
ex := e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect()
|
ex := e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect()
|
||||||
|
|
|
@ -17,25 +17,7 @@ Parse using embedded assets, Layouts and Party-specific layout, Template Funcs,
|
||||||
| 7 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) |
|
| 7 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) |
|
||||||
| 8 | Ace | [yosssi/ace](https://github.com/yosssi/ace) |
|
| 8 | Ace | [yosssi/ace](https://github.com/yosssi/ace) |
|
||||||
|
|
||||||
## Examples
|
[List of Examples](https://github.com/kataras/iris/tree/master/_examples/view).
|
||||||
|
|
||||||
- [Overview](https://github.com/kataras/iris/blob/master/_examples/view/overview/main.go)
|
|
||||||
- [Hi](https://github.com/kataras/iris/blob/master/_examples/view/template_html_0/main.go)
|
|
||||||
- [A simple Layout](https://github.com/kataras/iris/blob/master/_examples/view/template_html_1/main.go)
|
|
||||||
- [Layouts: `yield` and `render` tmpl funcs](https://github.com/kataras/iris/blob/master/_examples/view/template_html_2/main.go)
|
|
||||||
- [The `urlpath` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_3/main.go)
|
|
||||||
- [The `url` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_4/main.go)
|
|
||||||
- [Inject Data Between Handlers](https://github.com/kataras/iris/blob/master/_examples/view/context-view-data/main.go)
|
|
||||||
- [Embedding Templates Into App Executable File](https://github.com/kataras/iris/blob/master/_examples/view/embedding-templates-into-app/main.go)
|
|
||||||
- [Blocks](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_0)
|
|
||||||
- [Blocks Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_1_embedded)
|
|
||||||
- [Greeting with Pug (Jade)`](view/template_pug_0)
|
|
||||||
- [Pug (Jade) Actions`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_1)
|
|
||||||
- [Pug (Jade) Includes`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_2)
|
|
||||||
- [Pug (Jade) Extends`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_3)
|
|
||||||
- [Jet](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_0)
|
|
||||||
- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded)
|
|
||||||
- [Ace](https://github.com/kataras/iris/blob/master/_examples/view/template_ace_0)
|
|
||||||
|
|
||||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `Context.ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example.
|
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `Context.ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example.
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,10 @@ type AmberEngine struct {
|
||||||
templateCache map[string]*template.Template
|
templateCache map[string]*template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*AmberEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*AmberEngine)(nil)
|
||||||
|
_ EngineFuncer = (*AmberEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// Amber creates and returns a new amber view engine.
|
// Amber creates and returns a new amber view engine.
|
||||||
// The given "extension" MUST begin with a dot.
|
// The given "extension" MUST begin with a dot.
|
||||||
|
|
|
@ -24,7 +24,10 @@ type BlocksEngine struct {
|
||||||
Engine *blocks.Blocks
|
Engine *blocks.Blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*BlocksEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*BlocksEngine)(nil)
|
||||||
|
_ EngineFuncer = (*BlocksEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// WrapBlocks wraps an initialized blocks engine and returns its Iris adapter.
|
// WrapBlocks wraps an initialized blocks engine and returns its Iris adapter.
|
||||||
// See `Blocks` package-level function too.
|
// See `Blocks` package-level function too.
|
||||||
|
@ -53,9 +56,8 @@ func (s *BlocksEngine) Ext() string {
|
||||||
// - url func(routeName string, args ...string) string
|
// - url func(routeName string, args ...string) string
|
||||||
// - urlpath func(routeName string, args ...string) string
|
// - urlpath func(routeName string, args ...string) string
|
||||||
// - tr func(lang, key string, args ...interface{}) string
|
// - tr func(lang, key string, args ...interface{}) string
|
||||||
func (s *BlocksEngine) AddFunc(funcName string, funcBody interface{}) *BlocksEngine {
|
func (s *BlocksEngine) AddFunc(funcName string, funcBody interface{}) {
|
||||||
s.Engine.Funcs(template.FuncMap{funcName: funcBody})
|
s.Engine.Funcs(template.FuncMap{funcName: funcBody})
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLayoutFunc adds a template function for templates that are marked as layouts.
|
// AddLayoutFunc adds a template function for templates that are marked as layouts.
|
||||||
|
|
|
@ -106,7 +106,10 @@ type DjangoEngine struct {
|
||||||
templateCache map[string]*pongo2.Template
|
templateCache map[string]*pongo2.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*DjangoEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*DjangoEngine)(nil)
|
||||||
|
_ EngineFuncer = (*DjangoEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// Django creates and returns a new django view engine.
|
// Django creates and returns a new django view engine.
|
||||||
// The given "extension" MUST begin with a dot.
|
// The given "extension" MUST begin with a dot.
|
||||||
|
|
|
@ -27,7 +27,10 @@ type HandlebarsEngine struct {
|
||||||
templateCache map[string]*raymond.Template
|
templateCache map[string]*raymond.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*HandlebarsEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*HandlebarsEngine)(nil)
|
||||||
|
_ EngineFuncer = (*HandlebarsEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// Handlebars creates and returns a new handlebars view engine.
|
// Handlebars creates and returns a new handlebars view engine.
|
||||||
// The given "extension" MUST begin with a dot.
|
// The given "extension" MUST begin with a dot.
|
||||||
|
|
|
@ -35,7 +35,10 @@ type HTMLEngine struct {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*HTMLEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*HTMLEngine)(nil)
|
||||||
|
_ EngineFuncer = (*HTMLEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
var emptyFuncs = template.FuncMap{
|
var emptyFuncs = template.FuncMap{
|
||||||
"yield": func(binding interface{}) (string, error) {
|
"yield": func(binding interface{}) (string, error) {
|
||||||
|
@ -175,12 +178,10 @@ func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLE
|
||||||
// - urlpath func(routeName string, args ...string) string
|
// - urlpath func(routeName string, args ...string) string
|
||||||
// - render func(fullPartialName string) (template.HTML, error).
|
// - render func(fullPartialName string) (template.HTML, error).
|
||||||
// - tr func(lang, key string, args ...interface{}) string
|
// - tr func(lang, key string, args ...interface{}) string
|
||||||
func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) *HTMLEngine {
|
func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) {
|
||||||
s.rmu.Lock()
|
s.rmu.Lock()
|
||||||
s.funcs[funcName] = funcBody
|
s.funcs[funcName] = funcBody
|
||||||
s.rmu.Unlock()
|
s.rmu.Unlock()
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFuncs overrides the template funcs with the given "funcMap".
|
// SetFuncs overrides the template funcs with the given "funcMap".
|
||||||
|
|
|
@ -36,7 +36,10 @@ type JetEngine struct {
|
||||||
jetDataContextKey string
|
jetDataContextKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Engine = (*JetEngine)(nil)
|
var (
|
||||||
|
_ Engine = (*JetEngine)(nil)
|
||||||
|
_ EngineFuncer = (*JetEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// jet library does not export or give us any option to modify them via Set
|
// jet library does not export or give us any option to modify them via Set
|
||||||
// (unless we parse the files by ourselves but this is not a smart choice).
|
// (unless we parse the files by ourselves but this is not a smart choice).
|
||||||
|
|
Loading…
Reference in New Issue
Block a user