From 131eddb7011a2d8c7a3647b6a9b2e2df9b297677 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Sun, 18 Sep 2016 06:55:44 +0300 Subject: [PATCH] Ability to change the whole default router --- HISTORY.md | 4 ++ README.md | 6 +-- configuration.go | 1 + http.go | 96 +++++++++++++++++++++++++----------------------- iris.go | 52 ++++++++++++++++++++------ 5 files changed, 99 insertions(+), 60 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index cf23cee5..fab0aefa 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,10 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris`. +## 4.2.6 -> 4.2.7 + +- **ADDED**: You are now able to use a raw fasthttp handler as the router instead of the default Iris' one. Example [here](https://github.com/iris-contrib/examples/blob/master/custom_fasthttp_router/main.go). But remember that I'm always recommending to use the Iris' default which supports subdomains, group of routes(parties), auto path correction and many other built'n features. This exists for specific users who told me that they need a feature like that inside Iris, we have no performance cost at all so that's ok to exists. + ## 4.2.5 -> 4.2.6 - **CHANGE**: Updater (See 4.2.4 and 4.2.3) runs in its own goroutine now, unless the `iris.Config.CheckForUpdatesSync` is true. diff --git a/README.md b/README.md index 504755e9..ee895897 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@
-Releases +Releases Examples @@ -181,7 +181,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v4.2.6** +Current: **v4.2.7** > Iris is an active project @@ -224,7 +224,7 @@ License can be found [here](LICENSE). [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-Apache%202.0%20%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-v4.2.6-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v4.2.7-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square [Chat]: https://kataras.rocket.chat/channel/iris diff --git a/configuration.go b/configuration.go index c5f934bd..664ca108 100644 --- a/configuration.go +++ b/configuration.go @@ -688,6 +688,7 @@ type ServerConfiguration struct { // for an optional second server with a different port you can always use: // iris.AddServer(iris.ServerConfiguration{ListeningAddr: ":9090", MaxRequestsPerConn:100}) MaxRequestsPerConn int + // RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT) // // NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server diff --git a/http.go b/http.go index 21a84480..a529698f 100644 --- a/http.go +++ b/http.go @@ -48,24 +48,25 @@ var ( AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace} /* methods as []byte, these are really used by iris */ - // methodGetBytes "GET" - methodGetBytes = []byte(MethodGet) - // methodPostBytes "POST" - methodPostBytes = []byte(MethodPost) - // methodPutBytes "PUT" - methodPutBytes = []byte(MethodPut) - // methodDeleteBytes "DELETE" - methodDeleteBytes = []byte(MethodDelete) - // methodConnectBytes "CONNECT" - methodConnectBytes = []byte(MethodConnect) - // methodHeadBytes "HEAD" - methodHeadBytes = []byte(MethodHead) - // methodPatchBytes "PATCH" - methodPatchBytes = []byte(MethodPatch) - // methodOptionsBytes "OPTIONS" - methodOptionsBytes = []byte(MethodOptions) - // methodTraceBytes "TRACE" - methodTraceBytes = []byte(MethodTrace) + + // MethodGetBytes "GET" + MethodGetBytes = []byte(MethodGet) + // MethodPostBytes "POST" + MethodPostBytes = []byte(MethodPost) + // MethodPutBytes "PUT" + MethodPutBytes = []byte(MethodPut) + // MethodDeleteBytes "DELETE" + MethodDeleteBytes = []byte(MethodDelete) + // MethodConnectBytes "CONNECT" + MethodConnectBytes = []byte(MethodConnect) + // MethodHeadBytes "HEAD" + MethodHeadBytes = []byte(MethodHead) + // MethodPatchBytes "PATCH" + MethodPatchBytes = []byte(MethodPatch) + // MethodOptionsBytes "OPTIONS" + MethodOptionsBytes = []byte(MethodOptions) + // MethodTraceBytes "TRACE" + MethodTraceBytes = []byte(MethodTrace) /* */ ) @@ -1444,7 +1445,7 @@ 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() { +func (mux *serveMux) build() (func(reqCtx *fasthttp.RequestCtx) string, func([]byte, []byte) bool) { mux.tree = nil sort.Sort(bySubdomain(mux.lookups)) for _, r := range mux.lookups { @@ -1474,6 +1475,31 @@ func (mux *serveMux) build() { mux.logger.Panic(err.Error()) } } + + // 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()) //string(ctx.Path()[:]) // a little bit of memory allocation, old method used: BytesToString, If I see the benchmarks get low I will change it back to old, but this way is safer. + } + + if !mux.escapePath { + getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) } + } + + methodEqual := func(treeMethod []byte, reqMethod []byte) bool { + return bytes.Equal(treeMethod, reqMethod) + } + // check for cors conflicts + for _, r := range mux.lookups { + if r.hasCors() { + methodEqual = func(treeMethod []byte, reqMethod []byte) bool { + return bytes.Equal(treeMethod, reqMethod) || bytes.Equal(reqMethod, MethodOptionsBytes) + } + break + } + } + + return getRequestPath, methodEqual + } func (mux *serveMux) lookup(routeName string) *route { @@ -1485,34 +1511,14 @@ func (mux *serveMux) lookup(routeName string) *route { return nil } -func (mux *serveMux) Handler() HandlerFunc { +// BuildHandler the default Iris router when iris.Handler is nil +func (mux *serveMux) BuildHandler() HandlerFunc { // 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(ctx *Context) string { - return utils.BytesToString(ctx.Path()) //string(ctx.Path()[:]) // a little bit of memory allocation, old method used: BytesToString, If I see the benchmarks get low I will change it back to old, but this way is safer. - } - if !mux.escapePath { - getRequestPath = func(ctx *Context) string { return utils.BytesToString(ctx.RequestCtx.RequestURI()) } - } - - methodEqual := func(treeMethod []byte, reqMethod []byte) bool { - return bytes.Equal(treeMethod, reqMethod) - } - - // check for cors conflicts - for _, r := range mux.lookups { - if r.hasCors() { - methodEqual = func(treeMethod []byte, reqMethod []byte) bool { - return bytes.Equal(treeMethod, reqMethod) || bytes.Equal(reqMethod, methodOptionsBytes) - } - break - } - } + getRequestPath, methodEqual := mux.build() return func(context *Context) { - routePath := getRequestPath(context) + routePath := getRequestPath(context.RequestCtx) tree := mux.tree for tree != nil { if !methodEqual(tree.method, context.Method()) { @@ -1553,7 +1559,7 @@ func (mux *serveMux) Handler() HandlerFunc { //ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent) context.Do() return - } else if mustRedirect && mux.correctPath && !bytes.Equal(context.Method(), methodConnectBytes) { + } else if mustRedirect && mux.correctPath && !bytes.Equal(context.Method(), MethodConnectBytes) { reqPath := routePath pathLen := len(reqPath) @@ -1574,7 +1580,7 @@ func (mux *serveMux) Handler() HandlerFunc { // RFC2616 recommends that a short note "SHOULD" be included in the // response because older user agents may not understand 301/307. // Shouldn't send the response for POST or HEAD; that leaves GET. - if bytes.Equal(tree.method, methodGetBytes) { + if bytes.Equal(tree.method, MethodGetBytes) { note := "Moved Permanently.\n" context.Write(note) } diff --git a/iris.go b/iris.go index 50c63e07..48f9a14c 100644 --- a/iris.go +++ b/iris.go @@ -78,7 +78,7 @@ import ( const ( // Version is the current version of the Iris web framework - Version = "4.2.6" + Version = "4.2.7" banner = ` _____ _ |_ _| (_) @@ -167,6 +167,11 @@ type ( // Implements the FrameworkAPI Framework struct { *muxAPI + // Handler field which can change the default iris' mux behavior + // if you want to get benefit with iris' context make use of: + // ctx:= iris.AcquireCtx(*fasthttp.RequestCtx) to get the context at the beginning of your handler + // iris.ReleaseCtx(ctx) to release/put the context to the pool, at the very end of your custom handler. + Handler fasthttp.RequestHandler contextPool sync.Pool Config *Configuration sessions sessions.Sessions @@ -301,24 +306,47 @@ func Go() error { return Default.Go() } +// AcquireCtx gets an Iris' Context from pool +// see iris.Handler & ReleaseCtx, Go() +func (s *Framework) AcquireCtx(reqCtx *fasthttp.RequestCtx) { + ctx := s.contextPool.Get().(*Context) // Changed to use the pool's New 09/07/2016, ~ -4k nanoseconds(9 bench tests) per requests (better performance) + ctx.RequestCtx = reqCtx +} + +// ReleaseCtx puts the Iris' Context back to the pool in order to be re-used +// see iris.Handler & AcquireCtx, Go() +func (s *Framework) ReleaseCtx(ctx *Context) { + ctx.Params = ctx.Params[0:0] + ctx.middleware = nil + ctx.session = nil + s.contextPool.Put(ctx) +} + // Go starts the iris station, listens to all registered servers, and prepare only if Virtual func (s *Framework) Go() error { s.initialize() s.Plugins.DoPreListen(s) - // build the fasthttp handler to bind it to the servers - h := s.mux.Handler() - reqHandler := func(reqCtx *fasthttp.RequestCtx) { - ctx := s.contextPool.Get().(*Context) // Changed to use the pool's New 09/07/2016, ~ -4k nanoseconds(9 bench tests) per requests (better performance) - ctx.RequestCtx = reqCtx - h(ctx) + if s.Handler == nil { // use the 'h' which is the default mux' handler + // build and get the default mux' handler(*Context) + serve := s.mux.BuildHandler() + // build the fasthttp handler to bind it to the servers + defaultHandler := func(reqCtx *fasthttp.RequestCtx) { + ctx := s.contextPool.Get().(*Context) // Changed to use the pool's New 09/07/2016, ~ -4k nanoseconds(9 bench tests) per requests (better performance) + ctx.RequestCtx = reqCtx - ctx.Params = ctx.Params[0:0] - ctx.middleware = nil - ctx.session = nil - s.contextPool.Put(ctx) + serve(ctx) + + ctx.Params = ctx.Params[0:0] + ctx.middleware = nil + ctx.session = nil + s.contextPool.Put(ctx) + } + + s.Handler = defaultHandler } - if firstErr := s.Servers.OpenAll(reqHandler); firstErr != nil { + + if firstErr := s.Servers.OpenAll(s.Handler); firstErr != nil { return firstErr }