diff --git a/http.go b/http.go index 5fb0c787..8c0903c7 100644 --- a/http.go +++ b/http.go @@ -1004,18 +1004,32 @@ type ( // if no name given then it's the subdomain+path name string subdomain string - method string + method []byte + methodStr string path string middleware Middleware formattedPath string 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{} 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() return r } @@ -1063,7 +1077,10 @@ func (r route) Subdomain() 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 { @@ -1188,31 +1205,6 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd if subdomain != "" { mux.hosts = true } - // add to the registry tree - tree := mux.getTree(method, subdomain) - if tree == nil { - //first time we register a route to this method with this domain - tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}, next: nil} - if mux.tree == nil { - // it's the first entry - mux.tree = tree - } else { - // find the last tree and make the .next to the tree we created before - lastTree := mux.tree - for lastTree != nil { - if lastTree.next == nil { - lastTree.next = tree - break - } - lastTree = lastTree.next - } - } - } - // 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) - if err := tree.entry.add(path, middleware); err != nil { - mux.logger.Panic(err.Error()) - } // add to the lookups, it's just a collection of routes information lookup := newRoute(method, subdomain, path, middleware) @@ -1222,6 +1214,39 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd } +// 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 + tree := mux.getTree(r.method, r.subdomain) + if tree == nil { + //first time we register a route to this method with this domain + tree = &muxTree{method: r.method, subdomain: r.subdomain, entry: &muxEntry{}, next: nil} + if mux.tree == nil { + // it's the first entry + mux.tree = tree + } else { + // find the last tree and make the .next to the tree we created before + lastTree := mux.tree + for lastTree != nil { + if lastTree.next == nil { + lastTree.next = tree + break + } + lastTree = lastTree.next + } + } + } + // 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) + if err := tree.entry.add(r.path, r.middleware); err != nil { + mux.logger.Panic(err.Error()) + } + } +} + func (mux *serveMux) lookup(routeName string) *route { for i := range mux.lookups { if r := mux.lookups[i]; r.name == routeName { @@ -1232,6 +1257,10 @@ func (mux *serveMux) lookup(routeName string) *route { } 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 :) getRequestPath := func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.Path()) diff --git a/initiatory.go b/initiatory.go index a21f7c13..f6269e9d 100644 --- a/initiatory.go +++ b/initiatory.go @@ -125,7 +125,7 @@ func (s *Framework) initialize() { // listen to websocket connections websocket.RegisterServer(s, s.Websocket, s.Logger) - // prepare the mux + // prepare the mux & the server s.mux.setCorrectPath(!s.Config.DisablePathCorrection) s.mux.setEscapePath(!s.Config.DisablePathEscape) s.mux.setHostname(s.HTTPServer.VirtualHostname()) @@ -134,9 +134,6 @@ func (s *Framework) initialize() { s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...) } - // prepare the server - s.HTTPServer.SetHandler(s.mux) - if s.Config.MaxRequestBodySize > 0 { s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize) } @@ -171,6 +168,8 @@ func (s *Framework) prepareTemplates() { func (s *Framework) openServer() (err error) { s.initialize() 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 { // print the banner if !s.Config.DisableBanner { @@ -191,3 +190,12 @@ func (s *Framework) closeServer() error { s.Plugins.DoPreClose(s) 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 +} diff --git a/iris.go b/iris.go index c48236c5..7d3e794f 100644 --- a/iris.go +++ b/iris.go @@ -92,6 +92,9 @@ type ( ListenUNIX(string, os.FileMode) NoListen() *Server 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) EmitError(int, *Context) Lookup(string) Route @@ -107,10 +110,10 @@ type ( // MuxAPI the visible api for the serveMux MuxAPI interface { Party(string, ...HandlerFunc) MuxAPI - - // middleware + // middleware serial, appending Use(...Handler) UseFunc(...HandlerFunc) + // main handlers Handle(string, string, ...Handler) 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 +// initializes the whole framework but server doesn't listens to a specific net.Listener func (s *Framework) NoListen() *Server { - s.initialize() - s.Plugins.DoPreListen(s) - s.Plugins.DoPostListen(s) - return s.HTTPServer + return s.justServe() } // CloseWithErr terminates the server and returns an error if any @@ -319,6 +320,40 @@ func (s *Framework) Close() { 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 func OnError(statusCode int, handlerFn HandlerFunc) { 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} } -// Use registers a Handler middleware +// Use registers Handler middleware func Use(handlers ...Handler) { Default.Use(handlers...) } -// UseFunc registers a HandlerFunc middleware +// UseFunc registers HandlerFunc middleware func UseFunc(handlersFn ...HandlerFunc) { Default.UseFunc(handlersFn...) } -// Use registers a Handler middleware +// Use registers Handler middleware func (api *muxAPI) Use(handlers ...Handler) { api.middleware = append(api.middleware, handlers...) } -// UseFunc registers a HandlerFunc middleware +// UseFunc registers HandlerFunc middleware func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) { api.Use(convertToHandlers(handlersFn)...) } diff --git a/plugin/iriscontrol/iriscontrol.go b/plugin/iriscontrol/iriscontrol.go index 8ff67eff..58c8f4d6 100644 --- a/plugin/iriscontrol/iriscontrol.go +++ b/plugin/iriscontrol/iriscontrol.go @@ -7,6 +7,7 @@ import ( "github.com/kataras/iris" "github.com/kataras/iris/config" "github.com/kataras/iris/middleware/basicauth" + "github.com/kataras/iris/websocket" ) type ( @@ -22,19 +23,70 @@ type ( // the station which this plugins is registed to parent *iris.Framework parentLastOp time.Time + + // websocket + clients clients } + clients []websocket.Connection + pluginInfo struct { Name 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{} 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.parentLastOp = time.Now() + i.initializeChild() } @@ -42,21 +94,45 @@ func (i *iriscontrol) initializeChild() { i.child = iris.New() i.child.Config.DisableBanner = true i.child.Config.Render.Template.Directory = assetsPath + "templates" + i.child.Config.Websocket.Endpoint = "/ws" // set the assets i.child.Static("/public", assetsPath+"static", 1) - // set the authentication middleware - i.child.Use(basicauth.New(config.BasicAuth{ + // set the authentication middleware to all except websocket + auth := basicauth.New(config.BasicAuth{ Users: i.users, ContextKey: "user", Realm: config.DefaultBasicAuthRealm, 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) { ctx.MustRender("index.html", iris.Map{ "ServerIsRunning": i.parentIsRunning(), + "Host": i.child.Config.Server.ListeningAddr, "Routes": i.parentLookups(), "Plugins": i.infoPlugins(), "LastOperationDateStr": i.infoLastOp(), @@ -72,6 +148,7 @@ func (i *iriscontrol) initializeChild() { i.parent.Logger.Warningf(err.Error()) } }() + i.parentLastOp = time.Now() } }) @@ -123,3 +200,9 @@ func (i *iriscontrol) infoPlugins() (info []pluginInfo) { func (i *iriscontrol) infoLastOp() string { 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) + } +}