Middleware
When we refer to the concept of Middleware within Iris, we're talking about running code before or after our main handler within an HTTP request's lifecycle. For example, a middleware called "logger" might write incoming request details to a file or the console, before proceeding with the main handler. One of the cool things about middleware is that these units are incredibly flexible and reusable within and across your applications!
A middleware is just an iris.Handler taking the form of
func(ctx iris.Context)
. Each middleware is executed when the previous middleware calls the ctx.Next()
method, which can be used for authentication, i.e., if the request isn't authorized to continue, we could throw an error response instead of calling this method.Iris has 7 (+1) different ways to register middleware, depending on your application's requirements. Party.UseError, Party.Use, Party.UseOnce, Application.UseGlobal, Party.Done, Application.DoneGlobal, Party.UseRouter and Application.WrapRouter. Iris gives you the tools to have full control over the request flow.
Let's dive into the details of each one of the above methods through a quick lesson on how you can register middlewares in an Iris Application and Party (group of routes). Below you will see a simple example which outputs to the client the order of execution of each registered handler.
package main
import (
"net/http"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.WrapRouter(routerWrapper)
app.UseRouter(routerMiddleware)
app.UseGlobal(globalMiddleware)
app.Use(useMiddleware)
app.UseError(errorMiddleware)
// app.Done(done)
// app.DoneGlobal(doneGlobal)
// Adding a OnErrorCode(iris.StatusNotFound) causes `.UseGlobal`
// to be fired on 404 pages without this,
// only `UseError` will be called, and thus should
// be used for error pages.
app.OnErrorCode(iris.StatusNotFound, notFoundHandler)
app.Get("/", mainHandler)
app.Listen(":8080")
}
func mainHandler(ctx iris.Context) {
ctx.WriteString("Main Handler")
}
func notFoundHandler(ctx iris.Context) {
ctx.WriteString("404 Error Handler")
}
A WrapperFunc registered in this way runs for the whole Application, before anything else. It is executed last registered first, unlike other middleware methods. A
WrapperFunc
is not an iris.Handler
, instead its signature is: func(http.ResponseWriter, *http.Request, router http.HandlerFunc)
. It's the lowest-level functionality used to intercept requests and optionally change the behaviour of the router (e.g. navigate to another route than the requested path one, perform security checks before even the router is executed and more). A good example of use-case is a CORS implementation.A middleware registered in this way will run under a specific Party's path prefix on all routes (whether they are matched or not, including routes that would result in an error.) All child Parties inherit them unless
Party.ResetRouteFilters()
or Party.Reset()
is called. They are executed in the order they are registered, and they run before UseGlobal
and Use
on matched routes or UseError
on errors.A middleware registered in this way will run on all previous and future routes, and registered error handlers (pages matched with
OnErrorCode
), in the whole Application (all Parties and Subdomains included.) They are also executed in the order they are registered, and they run before Use
or UseError
.A middleware registered in this way will run after
UseGlobal
under a particular Party and its children. They are also executed in the order they are registered, and they run right before the Handle itself (such as Get
, Post
, Patch
, ...) It is not executed on errors.A middleware registered in this way will run only when an HTTP error is encountered (such as the case with an unavailable
404
or protected 401
resources), unlike Use
. They fire for all children Parties and will be called in the order they were registered.Register handler to run after routes middleware and handlers under a specific Party and its children. Requires a
ctx.Next
call on the last route's handler, unless Execution Rules are modified.Same as
UseGlobal
but for the Done
handlers. Runs on the Application instance level, after everything else.The middlewares used for this example are included below:
func routerWrapper(w http.ResponseWriter, r *http.Request,
router http.HandlerFunc) {
if r.URL.Path == "/" {
w.Write([]byte("#1 .WrapRouter\n"))
/* Note for new Gophers:
If we Write anything here on an error resource in the raw
`net/http` wrapper like this one, then the response writer will
automatically send a `200` OK status code (when we first write).
Any error handler executed after this will not fire as expected.
Also, when `w.WriteHeader` is called you can NOT change the
status code later on.
In Iris Handlers, if you write before the status code has been
set, then it will also automatically send the 200 OK status
code which then cannot be changed later. However, if we call
`ctx.StatusCode` inside an Iris Handler without writing any
content, then we can change the status code later on. When you
need to change that behaviour, you must start the handler with
a `ctx.Record` call.
*/
}
// Continue by executing the Iris Router and let it do its job.
router(w, r)
}
func routerMiddleware(ctx iris.Context) {
if ctx.Path() == "/" {
ctx.WriteString("#2 .UseRouter\n")
// The same caveat described in routerWrapper applies here as well.
}
ctx.Next()
}
func globalMiddleware(ctx iris.Context) {
ctx.WriteString("#3 .UseGlobal\n")
ctx.Next()
}
func useMiddleware(ctx iris.Context) {
ctx.WriteString("#4 .Use\n")
ctx.Next()
}
func errorMiddleware(ctx iris.Context) {
ctx.WriteString("#3 .UseError\n")
ctx.Next()
}
Run our simple application:
$ go run main.go
Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.
Point your browser to
http://localhost:8080
, and the output should look exactly like this:#1 .WrapRouter
#2 .UseRouter
#3 .UseGlobal
#4 .Use
Main Handler
And for a page that should give a 404, such as
http://localhost:8080/a_not_found_resource
:#3 .UseGlobal
#3 .UseError
404 Error Handler
(Note that
WrapRouter
and UseRouter
are still fired 1st and 2nd, just not shown in the output. Read the block comment to learn why.)However, without an
OnErrorCode
:It is important to note that without
OnErrorCode
registered, your UseGlobal
will not be executed.#3 .UseError
Not Found
You can modify the behavior of any existing middleware by wrapping it.
Let's take, for example, that you want to register a middleware on
UseRouter
to run everywhere except on the static.example.com
subdomain.You have two ways to do that. The first one is:
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
func main() {
users := map[string]string{"username":"password"}
auth := basicauth.Default(users)
app.UseRouter(skipStaticSubdomain(auth)) // <--
// [...]
app.Listen(":80")
}
func skipStaticSubdomain(handler iris.Handler) iris.Handler {
return func(ctx iris.Context) {
if ctx.Subdomain() == "static." {
// continue to the next or main handler and exit.
ctx.Next()
return
}
handler(ctx)
}
}
type Filter func(Context) bool
func NewConditionalHandler(filter Filter, handlers ...Handler) Handler
Here is a usage example, which checks if a subdomain exists and it is NOT the
static.
one:app.UseRouter(iris.NewConditionalHandler(isNotStaticSubdomain, auth))
func isNotStaticSubdomain(ctx iris.Context) bool {
return ctx.Subdomain() != "static."
}
That's all. When you've got the idea, it's trivial.
Each request-response lifecycle contains an instance of
iris.Context
. This instance is shared across the handlers chain—the Context.Values()
returns temporary memory storage which can be used to transfer data between middleware and handlers.This storage contains many helper methods, the most important you'll probably use are:
Set(key string, value interface{}) (Entry, bool)
Get(key string) interface{}
Set a value:
func myMiddleware(ctx iris.Context) {
ctx.Values().Set("key", value)
}
Get a value:
func myHandler(ctx iris.Context) {
value := ctx.Values().Get("key")
}
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// or app.Use(before) and app.Done(after).
app.Get("/", before, mainHandler, after)
app.Listen(":8080")
}
func before(ctx iris.Context) {
shareInformation := "this is a sharable information between handlers"
requestPath := ctx.Path()
println("Before the mainHandler: " + requestPath)
ctx.Values().Set("info", shareInformation)
ctx.Next() // execute the next handler, in this case the main one.
}
func after(ctx iris.Context) {
println("After the mainHandler")
}
func mainHandler(ctx iris.Context) {
println("Inside mainHandler")
// take the info from the "before" handler.
info := ctx.Values().GetString("info")
// write something to the client as a response.
ctx.HTML("<h1>Response</h1>")
ctx.HTML("<br/> Info: " + info)
ctx.Next() // execute the "after".
}
$ go run main.go # and navigate to the http://localhost:8080
Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.
Before the mainHandler: /
Inside mainHandler
After the mainHandler
To remove a specific handler from a specific route use the
RemoveHandler
method of the *Route
registered by the Handle/Get/Post/Put...
methods. The RemoveHandler
method expects the handler name (it's the PC func, see HandlerName
of the iris context
subpackage) or the handler itself.Example Code:
func middleware(ctx iris.Context) {
// [...]
}
func main() {
app := iris.New()
// Register the middleware to all matched routes.
app.Use(middleware)
// Handlers = middleware, other
app.Get("/", index)
// Handlers = other
app.Get("/other", other).RemoveHandler(middleware)
}
You could also use the
ExecutionRules
to force Begin(UseXXX) and Finish(DoneXXX) handlers to be executed without the requirement of a ctx.Next()
call, to forcibly forward them:app.SetExecutionRules(iris.ExecutionRules{
// Begin: ...
// Main: ...
Done: iris.ExecutionOptions{Force: true},
})
However, you are not limited to them - you are free to use any third-party middleware that is compatible with the net/http package.
Iris, unlike others, is 100% compatible with the standards and that's why the majority of big companies that adapt Go to their workflow, like a very famous US Television Network, trust Iris; it's up-to-date, and it will always be aligned with the std
net/http
package which is modernized by the Go Authors on each new release of the Go Programming Language.Any third-party middleware that written for
net/http
is compatible with Iris using the iris.FromStd(aThirdPartyMiddleware)
. Remember, ctx.ResponseWriter()
and ctx.Request()
returns the same net/http
input arguments of an http.Handler.Here is a list of some handlers made specifically for Iris:
Middleware | Example |
---|---|
Middleware | Description | Example |
---|---|---|
An authorization library that supports access control models like ACL, RBAC, ABAC | ||
AWS cloudwatch metrics middleware | ||
HTTP Access Control | ||
Cross-Site Request Forgery Protection | ||
Middleware checks for a JWT on the Authorization header on incoming requests and decodes it | ||
Middleware that implements a few quick security wins | ||
Sentry client in Go | ||
Rate limiting access to HTTP endpoints | ||
Generic middleware to rate-limit HTTP requests |
Iris has its own middleware form of
func(ctx iris.Context)
, but it's also compatible with all net/http
middleware forms. See here.Here's a small list of useful third-party handlers:
Middleware | Description |
---|---|
Add delays/latency to endpoints. Useful when testing effects of high latency | |
Generate TinySVG, HTML and CSS on the fly | |
Secure authentication for REST API endpoints | |
Store information about your web application (response time, etc.) | |
Last modified 2mo ago