MustUse and MustUseFunc - registers middleware for all parties and subdomains - Subdomains don't care about registering order now

1st:
MustUse and MustUseFunc - registers middleware for all parties and
subdomains
2nd:
Subdomains don't care about registering order now
3rd:
iris control plugin realtime logger
This commit is contained in:
Makis Maropoulos 2016-06-17 07:18:09 +03:00
parent dde7ce31d5
commit f21faa7cfd
4 changed files with 200 additions and 45 deletions

55
http.go
View File

@ -1004,18 +1004,32 @@ type (
// if no name given then it's the subdomain+path // if no name given then it's the subdomain+path
name string name string
subdomain string subdomain string
method string method []byte
methodStr string
path string path string
middleware Middleware middleware Middleware
formattedPath string formattedPath string
formattedParts int formattedParts int
} }
bySubdomain []*route
) )
// Sorting happens when the mux's request handler initialized
func (s bySubdomain) Len() int {
return len(s)
}
func (s bySubdomain) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s bySubdomain) Less(i, j int) bool {
return len(s[i].Subdomain()) > len(s[j].Subdomain())
}
var _ Route = &route{} var _ Route = &route{}
func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route { func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
r := &route{name: path + subdomain, method: string(method), subdomain: subdomain, path: path, middleware: middleware} r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
r.formatPath() r.formatPath()
return r return r
} }
@ -1063,7 +1077,10 @@ func (r route) Subdomain() string {
} }
func (r route) Method() string { func (r route) Method() string {
return r.method if r.methodStr == "" {
r.methodStr = string(r.method)
}
return r.methodStr
} }
func (r route) Path() string { func (r route) Path() string {
@ -1188,11 +1205,25 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd
if subdomain != "" { if subdomain != "" {
mux.hosts = true mux.hosts = true
} }
// add to the lookups, it's just a collection of routes information
lookup := newRoute(method, subdomain, path, middleware)
mux.lookups = append(mux.lookups, lookup)
return lookup
}
// build collects all routes info and adds them to the registry in order to be served from the request handler
// this happens once when server is setting the mux's handler.
func (mux *serveMux) build() {
routes := bySubdomain(mux.lookups)
for _, r := range routes {
// add to the registry tree // add to the registry tree
tree := mux.getTree(method, subdomain) tree := mux.getTree(r.method, r.subdomain)
if tree == nil { if tree == nil {
//first time we register a route to this method with this domain //first time we register a route to this method with this domain
tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}, next: nil} tree = &muxTree{method: r.method, subdomain: r.subdomain, entry: &muxEntry{}, next: nil}
if mux.tree == nil { if mux.tree == nil {
// it's the first entry // it's the first entry
mux.tree = tree mux.tree = tree
@ -1210,16 +1241,10 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd
} }
// I decide that it's better to explicit give subdomain and a path to it than registedPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something // I decide that it's better to explicit give subdomain and a path to it than registedPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
// we have different tree for each of subdomains, now you can use everyting you can use with the normal paths ( before you couldn't set /any/*path) // we have different tree for each of subdomains, now you can use everyting you can use with the normal paths ( before you couldn't set /any/*path)
if err := tree.entry.add(path, middleware); err != nil { if err := tree.entry.add(r.path, r.middleware); err != nil {
mux.logger.Panic(err.Error()) mux.logger.Panic(err.Error())
} }
}
// add to the lookups, it's just a collection of routes information
lookup := newRoute(method, subdomain, path, middleware)
mux.lookups = append(mux.lookups, lookup)
return lookup
} }
func (mux *serveMux) lookup(routeName string) *route { func (mux *serveMux) lookup(routeName string) *route {
@ -1232,6 +1257,10 @@ func (mux *serveMux) lookup(routeName string) *route {
} }
func (mux *serveMux) ServeRequest() fasthttp.RequestHandler { func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
// initialize the router once
mux.build()
// optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :) // optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
getRequestPath := func(reqCtx *fasthttp.RequestCtx) string { getRequestPath := func(reqCtx *fasthttp.RequestCtx) string {
return utils.BytesToString(reqCtx.Path()) return utils.BytesToString(reqCtx.Path())

View File

@ -125,7 +125,7 @@ func (s *Framework) initialize() {
// listen to websocket connections // listen to websocket connections
websocket.RegisterServer(s, s.Websocket, s.Logger) websocket.RegisterServer(s, s.Websocket, s.Logger)
// prepare the mux // prepare the mux & the server
s.mux.setCorrectPath(!s.Config.DisablePathCorrection) s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
s.mux.setEscapePath(!s.Config.DisablePathEscape) s.mux.setEscapePath(!s.Config.DisablePathEscape)
s.mux.setHostname(s.HTTPServer.VirtualHostname()) s.mux.setHostname(s.HTTPServer.VirtualHostname())
@ -134,9 +134,6 @@ func (s *Framework) initialize() {
s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...) s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
} }
// prepare the server
s.HTTPServer.SetHandler(s.mux)
if s.Config.MaxRequestBodySize > 0 { if s.Config.MaxRequestBodySize > 0 {
s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize) s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
} }
@ -171,6 +168,8 @@ func (s *Framework) prepareTemplates() {
func (s *Framework) openServer() (err error) { func (s *Framework) openServer() (err error) {
s.initialize() s.initialize()
s.Plugins.DoPreListen(s) s.Plugins.DoPreListen(s)
// set the server's handler now, in order to give the chance to the plugins to add their own middlewares and routes to this station
s.HTTPServer.SetHandler(s.mux)
if err = s.HTTPServer.Open(); err == nil { if err = s.HTTPServer.Open(); err == nil {
// print the banner // print the banner
if !s.Config.DisableBanner { if !s.Config.DisableBanner {
@ -191,3 +190,12 @@ func (s *Framework) closeServer() error {
s.Plugins.DoPreClose(s) s.Plugins.DoPreClose(s)
return s.HTTPServer.close() return s.HTTPServer.close()
} }
// justServe initializes the whole framework but server doesn't listens to a specific net.Listener
func (s *Framework) justServe() *Server {
s.initialize()
s.Plugins.DoPreListen(s)
s.HTTPServer.SetHandler(s.mux)
s.Plugins.DoPostListen(s)
return s.HTTPServer
}

55
iris.go
View File

@ -92,6 +92,9 @@ type (
ListenUNIX(string, os.FileMode) ListenUNIX(string, os.FileMode)
NoListen() *Server NoListen() *Server
Close() Close()
// global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also
MustUse(...Handler)
MustUseFunc(...HandlerFunc)
OnError(int, HandlerFunc) OnError(int, HandlerFunc)
EmitError(int, *Context) EmitError(int, *Context)
Lookup(string) Route Lookup(string) Route
@ -107,10 +110,10 @@ type (
// MuxAPI the visible api for the serveMux // MuxAPI the visible api for the serveMux
MuxAPI interface { MuxAPI interface {
Party(string, ...HandlerFunc) MuxAPI Party(string, ...HandlerFunc) MuxAPI
// middleware serial, appending
// middleware
Use(...Handler) Use(...Handler)
UseFunc(...HandlerFunc) UseFunc(...HandlerFunc)
// main handlers // main handlers
Handle(string, string, ...Handler) RouteNameFunc Handle(string, string, ...Handler) RouteNameFunc
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
@ -292,11 +295,9 @@ func NoListen() *Server {
} }
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it // NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
// initializes the whole framework but server doesn't listens to a specific net.Listener
func (s *Framework) NoListen() *Server { func (s *Framework) NoListen() *Server {
s.initialize() return s.justServe()
s.Plugins.DoPreListen(s)
s.Plugins.DoPostListen(s)
return s.HTTPServer
} }
// CloseWithErr terminates the server and returns an error if any // CloseWithErr terminates the server and returns an error if any
@ -319,6 +320,40 @@ func (s *Framework) Close() {
s.Must(s.CloseWithErr()) s.Must(s.CloseWithErr())
} }
// MustUse registers Handler middleware to the beggining, prepends them instead of append
//
// Use it when you want to add a global middleware to all parties, to all routes in all subdomains
// It can be called after other, (but before .Listen of course)
func MustUse(handlers ...Handler) {
Default.MustUse(handlers...)
}
// MustUseFunc registers HandlerFunc middleware to the beggining, prepends them instead of append
//
// Use it when you want to add a global middleware to all parties, to all routes in all subdomains
// It can be called after other, (but before .Listen of course)
func MustUseFunc(handlersFn ...HandlerFunc) {
Default.MustUseFunc(handlersFn...)
}
// MustUse registers Handler middleware to the beggining, prepends them instead of append
//
// Use it when you want to add a global middleware to all parties, to all routes in all subdomains
// It can be called after other, (but before .Listen of course)
func (s *Framework) MustUse(handlers ...Handler) {
for _, r := range s.mux.lookups {
r.middleware = append(handlers, r.middleware...)
}
}
// MustUseFunc registers HandlerFunc middleware to the beggining, prepends them instead of append
//
// Use it when you want to add a global middleware to all parties, to all routes in all subdomains
// It can be called after other, (but before .Listen of course)
func (s *Framework) MustUseFunc(handlersFn ...HandlerFunc) {
s.MustUse(convertToHandlers(handlersFn)...)
}
// OnError registers a custom http error handler // OnError registers a custom http error handler
func OnError(statusCode int, handlerFn HandlerFunc) { func OnError(statusCode int, handlerFn HandlerFunc) {
Default.OnError(statusCode, handlerFn) Default.OnError(statusCode, handlerFn)
@ -576,22 +611,22 @@ func (api *muxAPI) Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI
return &muxAPI{relativePath: fullpath, mux: api.mux, middleware: middleware} return &muxAPI{relativePath: fullpath, mux: api.mux, middleware: middleware}
} }
// Use registers a Handler middleware // Use registers Handler middleware
func Use(handlers ...Handler) { func Use(handlers ...Handler) {
Default.Use(handlers...) Default.Use(handlers...)
} }
// UseFunc registers a HandlerFunc middleware // UseFunc registers HandlerFunc middleware
func UseFunc(handlersFn ...HandlerFunc) { func UseFunc(handlersFn ...HandlerFunc) {
Default.UseFunc(handlersFn...) Default.UseFunc(handlersFn...)
} }
// Use registers a Handler middleware // Use registers Handler middleware
func (api *muxAPI) Use(handlers ...Handler) { func (api *muxAPI) Use(handlers ...Handler) {
api.middleware = append(api.middleware, handlers...) api.middleware = append(api.middleware, handlers...)
} }
// UseFunc registers a HandlerFunc middleware // UseFunc registers HandlerFunc middleware
func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) { func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) {
api.Use(convertToHandlers(handlersFn)...) api.Use(convertToHandlers(handlersFn)...)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/config" "github.com/kataras/iris/config"
"github.com/kataras/iris/middleware/basicauth" "github.com/kataras/iris/middleware/basicauth"
"github.com/kataras/iris/websocket"
) )
type ( type (
@ -22,19 +23,70 @@ type (
// the station which this plugins is registed to // the station which this plugins is registed to
parent *iris.Framework parent *iris.Framework
parentLastOp time.Time parentLastOp time.Time
// websocket
clients clients
} }
clients []websocket.Connection
pluginInfo struct { pluginInfo struct {
Name string Name string
Description string Description string
} }
logInfo struct {
Date string
Status int
Latency time.Duration
IP string
Method string
Subdomain string
Path string
}
) )
func (c clients) indexOf(connectionID string) int {
for i := range c {
if c[i].ID() == connectionID {
return i
}
}
return -1
}
var _ IrisControl = &iriscontrol{} var _ IrisControl = &iriscontrol{}
func (i *iriscontrol) listen(f *iris.Framework) { func (i *iriscontrol) listen(f *iris.Framework) {
// set the path logger to the parent which will send the log via websocket to the browser
f.MustUseFunc(func(ctx *iris.Context) {
status := ctx.Response.StatusCode()
path := ctx.PathString()
method := ctx.MethodString()
subdomain := ctx.Subdomain()
ip := ctx.RemoteAddr()
startTime := time.Now()
ctx.Next()
//no time.Since in order to format it well after
endTime := time.Now()
date := endTime.Format("01/02 - 15:04:05")
latency := endTime.Sub(startTime)
info := logInfo{
Date: date,
Status: status,
Latency: latency,
IP: ip,
Method: method,
Subdomain: subdomain,
Path: path,
}
i.Emit("log", info) //send this text to the browser,
})
i.parent = f i.parent = f
i.parentLastOp = time.Now() i.parentLastOp = time.Now()
i.initializeChild() i.initializeChild()
} }
@ -42,21 +94,45 @@ func (i *iriscontrol) initializeChild() {
i.child = iris.New() i.child = iris.New()
i.child.Config.DisableBanner = true i.child.Config.DisableBanner = true
i.child.Config.Render.Template.Directory = assetsPath + "templates" i.child.Config.Render.Template.Directory = assetsPath + "templates"
i.child.Config.Websocket.Endpoint = "/ws"
// set the assets // set the assets
i.child.Static("/public", assetsPath+"static", 1) i.child.Static("/public", assetsPath+"static", 1)
// set the authentication middleware // set the authentication middleware to all except websocket
i.child.Use(basicauth.New(config.BasicAuth{ auth := basicauth.New(config.BasicAuth{
Users: i.users, Users: i.users,
ContextKey: "user", ContextKey: "user",
Realm: config.DefaultBasicAuthRealm, Realm: config.DefaultBasicAuthRealm,
Expires: time.Duration(1) * time.Hour, Expires: time.Duration(1) * time.Hour,
})) })
i.child.UseFunc(func(ctx *iris.Context) {
///TODO: Remove this and make client-side basic auth when websocket connection.
if ctx.PathString() == i.child.Config.Websocket.Endpoint {
ctx.Next()
return
}
auth.Serve(ctx)
})
i.child.Websocket.OnConnection(func(c websocket.Connection) {
// add the client to the list
i.clients = append(i.clients, c)
c.OnDisconnect(func() {
// remove the client from the list
if idx := i.clients.indexOf(c.ID()); idx != -1 {
i.clients[idx] = i.clients[len(i.clients)-1]
i.clients = i.clients[:len(i.clients)-1]
}
})
})
i.child.Get("/", func(ctx *iris.Context) { i.child.Get("/", func(ctx *iris.Context) {
ctx.MustRender("index.html", iris.Map{ ctx.MustRender("index.html", iris.Map{
"ServerIsRunning": i.parentIsRunning(), "ServerIsRunning": i.parentIsRunning(),
"Host": i.child.Config.Server.ListeningAddr,
"Routes": i.parentLookups(), "Routes": i.parentLookups(),
"Plugins": i.infoPlugins(), "Plugins": i.infoPlugins(),
"LastOperationDateStr": i.infoLastOp(), "LastOperationDateStr": i.infoLastOp(),
@ -72,6 +148,7 @@ func (i *iriscontrol) initializeChild() {
i.parent.Logger.Warningf(err.Error()) i.parent.Logger.Warningf(err.Error())
} }
}() }()
i.parentLastOp = time.Now() i.parentLastOp = time.Now()
} }
}) })
@ -123,3 +200,9 @@ func (i *iriscontrol) infoPlugins() (info []pluginInfo) {
func (i *iriscontrol) infoLastOp() string { func (i *iriscontrol) infoLastOp() string {
return i.parentLastOp.Format(config.TimeFormat) return i.parentLastOp.Format(config.TimeFormat)
} }
func (i *iriscontrol) Emit(event string, msg interface{}) {
for j := range i.clients {
i.clients[j].Emit(event, msg)
}
}