diff --git a/README.md b/README.md index 01ebcd90..fb789271 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,9 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v3.0.0-pre.release** +Current: **v3.0.0** > Iris is an active project - Read more about Semantic Versioning 2.0.0 - http://semver.org/ @@ -152,13 +151,20 @@ Read more about Semantic Versioning 2.0.0 Todo ------------ -> for the next release 'v3' +> for the next version 'v4' + +- [ ] Refactor & extend view engine, separate the engines from the main code base, easier for the community to create new view engines. +- [ ] Create a router as optional plugin, for optional path parts. Its name, 'ryan', taken from the community-member and donator who requested this feature. +- [ ] Extend the iris control plugin +- [ ] Remove deprecated functions. + +> completed for release 'v3' - [x] [Dynamic/Wildcard subdomains](https://kataras.gitbooks.io/iris/content/subdomains.html) - [x] [Create server & client side](https://kataras.gitbooks.io/iris/content/package-websocket.html) (js) library for .on('event', func action(...)) / .emit('event')... (like socket.io but supports only websocket) -- [x] [Create a view system](https://kataras.gitbooks.io/iris/content/render_templates.html) supporting different types of template engines +- [x] [Create a view engines system](https://kataras.gitbooks.io/iris/content/render_templates.html) supporting different types of template engines - [x] Extend, test and publish to the public the [Iris' cmd](https://github.com/kataras/iris/tree/master/iris)[*](https://github.com/kataras/rizla) -- [ ] Complete the API tests, community can help also, tests are located [here](https://github.com/iris-contrib/tests). +- [x] Complete the API tests, community can help also, tests are located [here](https://github.com/iris-contrib/tests). If you're **willing to donate** click [here](DONATIONS.md)! @@ -185,7 +191,7 @@ License can be found [here](LICENSE). [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-v3.0.0--pre.release-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v3.0.0-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/context.go b/context.go index aaebed1d..6721c5a7 100644 --- a/context.go +++ b/context.go @@ -78,6 +78,7 @@ var ( type ( // Map is just a conversion for a map[string]interface{} + // should not be used inside Render when PongoEngine is used. Map map[string]interface{} // Context is resetting every time a request is coming to the server // it is not good practice to use this object in goroutines, for these cases use the .Clone() diff --git a/deprecated.go b/deprecated.go index 4d24938e..6357741e 100644 --- a/deprecated.go +++ b/deprecated.go @@ -68,6 +68,40 @@ func (s *Framework) CloseWithErr() error { return s.Close() } +// MustUse registers Handler middleware to the beginning, prepends them instead of append +// DEPRECATED: use UseGlobal instead +// 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 beginning, prepends them instead of append +// DEPRECATED: use UseGlobalFunc instead +// 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 beginning, prepends them instead of append +// DEPRECATED: use UseGlobal instead +// 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 beginning, prepends them instead of append +// DEPRECATED: use UseGlobalFunc instead +// 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)...) +} + // PostFormMulti returns a slice of string from post request's data // DEPRECATED: Plase use FormValues instead func (ctx *Context) PostFormMulti(name string) []string { diff --git a/http.go b/http.go index 1826a479..70c30595 100644 --- a/http.go +++ b/http.go @@ -315,14 +315,19 @@ func (s *Server) Port() (port int) { return } -// FullHost returns the scheme+host -func (s *Server) FullHost() string { +// Scheme returns http:// or https:// if SSL is enabled +func (s *Server) Scheme() string { scheme := "http://" // we need to be able to take that before(for testing &debugging) and after server's listen if s.IsSecure() || (s.Config.CertFile != "" && s.Config.KeyFile != "") { scheme = "https://" } - return scheme + s.Host() + return scheme +} + +// FullHost returns the scheme+host +func (s *Server) FullHost() string { + return s.Scheme() + s.Host() } // Hostname returns the hostname part of the host (host expect port) @@ -850,7 +855,7 @@ func (e *muxEntry) add(path string, middleware Middleware) error { if len(path) >= len(e.part) && e.part == path[:len(e.part)] { - if len(e.part) >= len(path) || path[len(e.part)] == '/' { + if len(e.part) >= len(path) || path[len(e.part)] == slashByte { continue loop } } @@ -859,7 +864,7 @@ func (e *muxEntry) add(path string, middleware Middleware) error { c := path[0] - if e.entryCase == hasParams && c == '/' && len(e.nodes) == 1 { + if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 { e = e.nodes[0] e.precedence++ continue loop @@ -1148,8 +1153,12 @@ type ( Method() string // Path returns the path Path() string + // SetPath changes/sets the path for this route + SetPath(string) // Middleware returns the slice of Handler([]Handler) registed to this route Middleware() Middleware + // SetMiddleware changes/sets the middleware(handler(s)) for this route + SetMiddleware(Middleware) } route struct { @@ -1239,10 +1248,18 @@ func (r route) Path() string { return r.path } +func (r *route) SetPath(s string) { + r.path = s +} + func (r route) Middleware() Middleware { return r.middleware } +func (r *route) SetMiddleware(m Middleware) { + r.middleware = m +} + const ( // subdomainIndicator where './' exists in a registed path then it contains subdomain subdomainIndicator = "./" @@ -1265,6 +1282,8 @@ type ( tree *muxTree lookups []*route + onLookup func(Route) + api *muxAPI errorHandlers map[int]Handler logger *logger.Logger @@ -1360,6 +1379,9 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd // add to the lookups, it's just a collection of routes information lookup := newRoute(method, subdomain, path, middleware) + if mux.onLookup != nil { + mux.onLookup(lookup) + } mux.lookups = append(mux.lookups, lookup) return lookup diff --git a/http_test.go b/http_test.go index 12d2086f..e54b6328 100644 --- a/http_test.go +++ b/http_test.go @@ -103,6 +103,24 @@ func TestServerHostname(t *testing.T) { } } +func TestServerFullHost(t *testing.T) { + var server1 Server + var server2 Server + server1.Config.ListeningAddr = "127.0.0.1:8080" + server1.Config.CertFile = "notempty" + server1.Config.KeyFile = "notempty" + server2.Config.ListeningAddr = "127.0.0.1:8080" + server1ExpectingFullhost := "https://" + server1.Config.ListeningAddr + server2ExpectingFullhost := "http://" + server2.Config.ListeningAddr + if server1.FullHost() != server1ExpectingFullhost { + t.Fatalf("Expecting server 1's fullhost to be %s but got %s", server1ExpectingFullhost, server1.FullHost()) + } + if server2.FullHost() != server2ExpectingFullhost { + t.Fatalf("Expecting server 2's fullhost to be %s but got %s", server2ExpectingFullhost, server2.FullHost()) + } + +} + func TestServerPort(t *testing.T) { var server1, server2 Server expectedPort1 := 8080 diff --git a/iris.go b/iris.go index 3b766b1c..53d94149 100644 --- a/iris.go +++ b/iris.go @@ -81,7 +81,7 @@ import ( const ( // Version of the iris - Version = "3.0.0-pre.release" + Version = "3.0.0" // HTMLEngine conversion for config.HTMLEngine HTMLEngine = config.HTMLEngine @@ -165,8 +165,12 @@ type ( Go() error Close() error // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also + // deprecated Start MustUse(...Handler) MustUseFunc(...HandlerFunc) + // deprecated End + UseGlobal(...Handler) + UseGlobalFunc(...HandlerFunc) OnError(int, HandlerFunc) EmitError(int, *Context) Lookup(string) Route @@ -220,6 +224,7 @@ func New(cfg ...config.Iris) *Framework { s.Websocket = websocket.NewServer(s.Config.Websocket) // set the servemux, which will provide us the public API also, with its context pool mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger) + mux.onLookup = s.Plugins.DoPreLookup // set the public router API (and party) s.muxAPI = &muxAPI{mux: mux, relativePath: "/"} @@ -247,7 +252,6 @@ func (s *Framework) initialize() { // prepare the mux & the server s.mux.setCorrectPath(!s.Config.DisablePathCorrection) s.mux.setEscapePath(!s.Config.DisablePathEscape) - // set the debug profiling handlers if ProfilePath is setted if debugPath := s.Config.ProfilePath; debugPath != "" { s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...) @@ -479,37 +483,37 @@ func (s *Framework) Close() error { return s.Servers.CloseAll() } -// MustUse registers Handler middleware to the beginning, prepends them instead of append +// UseGlobal registers Handler middleware to the beginning, 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) { +func UseGlobal(handlers ...Handler) { Default.MustUse(handlers...) } -// MustUseFunc registers HandlerFunc middleware to the beginning, prepends them instead of append +// UseGlobalFunc registers HandlerFunc middleware to the beginning, 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) { +func UseGlobalFunc(handlersFn ...HandlerFunc) { Default.MustUseFunc(handlersFn...) } -// MustUse registers Handler middleware to the beginning, prepends them instead of append +// UseGlobal registers Handler middleware to the beginning, 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) { +func (s *Framework) UseGlobal(handlers ...Handler) { for _, r := range s.mux.lookups { r.middleware = append(handlers, r.middleware...) } } -// MustUseFunc registers HandlerFunc middleware to the beginning, prepends them instead of append +// UseGlobalFunc registers HandlerFunc middleware to the beginning, 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) { +func (s *Framework) UseGlobalFunc(handlersFn ...HandlerFunc) { s.MustUse(convertToHandlers(handlersFn)...) } @@ -914,6 +918,7 @@ func (api *muxAPI) Handle(method string, registedPath string, handlers ...Handle } path = strings.Replace(path, "//", "/", -1) // fix the path if double // + return api.mux.register([]byte(method), subdomain, path, middleware).setName } diff --git a/plugin.go b/plugin.go index 4f24abec..cb1ec492 100644 --- a/plugin.go +++ b/plugin.go @@ -53,6 +53,13 @@ type ( // PluginContainer parameter used to add other plugins if that's necessary by the plugin Activate(PluginContainer) error } + // pluginPreLookup implements the PreRoute(Route) method + pluginPreLookup interface { + // PreLookup called before register a route + PreLookup(Route) + } + // PreLookupFunc implements the simple function listener for the PreLookup(Route) + PreLookupFunc func(Route) // pluginPreListen implements the PreListen(*Framework) method pluginPreListen interface { // PreListen it's being called only one time, BEFORE the Server is started (if .Listen called) @@ -105,6 +112,8 @@ type ( GetDescription(Plugin) string GetByName(string) Plugin Printf(string, ...interface{}) + PreLookup(PreLookupFunc) + DoPreLookup(Route) PreListen(PreListenFunc) DoPreListen(*Framework) DoPreListenParallel(*Framework) @@ -148,6 +157,11 @@ type ( // convert the functions to plugin +// PreLookup called before register a route +func (fn PreLookupFunc) PreLookup(r Route) { + fn(r) +} + // PreListen it's being called only one time, BEFORE the Server is started (if .Listen called) // is used to do work at the time all other things are ready to go // parameter is the station @@ -339,12 +353,27 @@ func (p *pluginContainer) Printf(format string, a ...interface{}) { } +// PreLookup adds a PreLookup plugin-function to the plugin flow container +func (p *pluginContainer) PreLookup(fn PreLookupFunc) { + p.Add(fn) +} + +// DoPreLookup raise all plugins which has the PreLookup method +func (p *pluginContainer) DoPreLookup(r Route) { + for i := range p.activatedPlugins { + // check if this method exists on our plugin obj, these are optionaly and call it + if pluginObj, ok := p.activatedPlugins[i].(pluginPreLookup); ok { + pluginObj.PreLookup(r) + } + } +} + // PreListen adds a PreListen plugin-function to the plugin flow container func (p *pluginContainer) PreListen(fn PreListenFunc) { p.Add(fn) } -// DoPreListen raise all plugins which has the DoPreListen method +// DoPreListen raise all plugins which has the PreListen method func (p *pluginContainer) DoPreListen(station *Framework) { for i := range p.activatedPlugins { // check if this method exists on our plugin obj, these are optionaly and call it