diff --git a/HISTORY.md b/HISTORY.md index d5de89cf..595fe768 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,9 +1,16 @@ # History +## 3.0.0-beta -> 3.0.0-beta.2 + +- NEW: Wildcard(dynamic) subdomains, read [here](https://kataras.gitbooks.io/iris/content/subdomains.html) + +- NEW: Implement feature request [#165](https://github.com/kataras/iris/issues/165). Routes can now be selected by `a custom name`, and this allows us to use the {{ url "custom-name" "any" "named" "parameters"}}`` inside HTMLEngine's templates. Example [here](https://github.com/iris-contrib/examples/tree/master/templates_9). + + ## 3.0.0-alpha.beta -> 3.0.0-beta -- New [iris.API] for easy API declaration, read more [here](https://kataras.gitbooks.io/iris/content/using-handlerapi.html), example [there](https://github.com/iris-contrib/examples/tree/master/api_handler_2). +- New iris.API for easy API declaration, read more [here](https://kataras.gitbooks.io/iris/content/using-handlerapi.html), example [there](https://github.com/iris-contrib/examples/tree/master/api_handler_2). - Add [example](https://github.com/iris-contrib/examples/tree/master/middleware_basicauth_2) and fix the Basic Authentication middleware diff --git a/context.go b/context.go index 6295c3ad..12f53499 100644 --- a/context.go +++ b/context.go @@ -55,8 +55,6 @@ type ( sessionStore store.IStore // pos is the position number of the Context, look .Next to understand pos uint8 - // subdomain the subdomain (taken from the host), this is empty until GetSubdomain called - subdomain string } ) diff --git a/context/context.go b/context/context.go index 8b695156..b1aaf6e1 100644 --- a/context/context.go +++ b/context/context.go @@ -86,12 +86,12 @@ type ( URLParams() map[string]string MethodString() string HostString() string + Subdomain() string PathString() string RequestIP() string RemoteAddr() string RequestHeader(k string) string PostFormValue(string) string - GetSubdomain() string } // IContextResponse is part of the IContext diff --git a/context_request.go b/context_request.go index 36952ab6..0aae9480 100644 --- a/context_request.go +++ b/context_request.go @@ -95,18 +95,14 @@ func (ctx *Context) PostFormValue(name string) string { return string(ctx.RequestCtx.PostArgs().Peek(name)) } -// GetSubdomain returns the subdomain if any, else empty string -func (ctx *Context) GetSubdomain() string { - if ctx.subdomain == "" { - host := ctx.HostString() - if index := strings.IndexByte(host, '.'); index > 0 { - subdomain := host[0:index] - ctx.subdomain = subdomain - } +// Subdomain returns the subdomain (string) of this request, if any +func (ctx *Context) Subdomain() (subdomain string) { + host := ctx.HostString() + if index := strings.IndexByte(host, '.'); index > 0 { + subdomain = host[0:index] } - return ctx.subdomain - + return } // URLEncode returns the path encoded as url diff --git a/context_response.go b/context_response.go index 4f663d0b..54e70634 100644 --- a/context_response.go +++ b/context_response.go @@ -25,11 +25,10 @@ func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) { // RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name func (ctx *Context) RedirectTo(routeName string, args ...interface{}) { - s, ok := ctx.station.RouteByName(routeName).Parse(args...) - if ok { + s := ctx.station.RouteByName(routeName).ParseURI(args...) + if s != "" { ctx.Redirect(s, StatusFound) } - } // Error handling diff --git a/iris.go b/iris.go index 83c12a68..98bd2415 100644 --- a/iris.go +++ b/iris.go @@ -124,14 +124,12 @@ func (s *Iris) initTemplates() { "url": func(routeName string, args ...interface{}) (string, error) { r := s.RouteByName(routeName) // check if not found - if r.GetPath() == "" { + if r.GetMethod() == "" { return "", ErrRenderRouteNotFound.Format(routeName) } - if result, ok := r.Parse(args...); ok { - return result, nil - } - return "", nil + return r.ParseURI(args...), nil + }, } // these should be already a non-nil map but if .New(cfg) it's not, is mergo's bug, temporary: @@ -259,9 +257,6 @@ func (s *Iris) PreListen(opt config.Server) *server.Server { func (s *Iris) PostListen() { //if not error opening the server, then: - // prepare the route actions, these actions needs real server's access because that it's after server's listen - s.router.optimizeLookups() - //set the rest (for Data, Text, JSON, JSONP, XML) s.rest = rest.New(s.config.Render.Rest) // set the templates diff --git a/party.go b/party.go index 55339e80..9baab984 100644 --- a/party.go +++ b/party.go @@ -75,7 +75,7 @@ func (p *GardenParty) Handle(method string, registedPath string, handlers ...Han path = fixPath(absPath(p.relativePath, registedPath)) // "/xyz" } middleware := JoinMiddleware(p.middleware, handlers) - route := NewRoute(method, path, middleware) + route := NewRoute(method, path, middleware, p.station) p.station.plugins.DoPreHandle(route) p.station.addRoute(route) p.station.plugins.DoPostHandle(route) @@ -634,7 +634,7 @@ func fixPath(str string) string { strafter := strings.Replace(str, "//", Slash, -1) - if strafter[0] == SlashByte && strings.Count(strafter, ".") >= 2 { + if strafter[0] == SlashByte && strings.Contains(strafter, ".") { //it's domain, remove the first slash strafter = strafter[1:] } diff --git a/route.go b/route.go index 14b6c962..e5a55e10 100644 --- a/route.go +++ b/route.go @@ -18,18 +18,8 @@ type ( Name(string) IRoute GetMiddleware() Middleware HasCors() bool - // internal methods - setTLS(bool) - setHost(string) - // - - // used to check arguments with the route's named parameters and return the correct url - // second return value is false when the action cannot be done - Parse(...interface{}) (string, bool) - - // GetURI returns the GetDomain() + Parse(...optional named parameters if route is dynamic) - // instead of Parse it just returns an empty string if path parse is failed - GetURI(...interface{}) string + ParsePath(...interface{}) string + ParseURI(...interface{}) string } // RouteNameFunc is returned to from route handle @@ -43,10 +33,8 @@ type ( // the name of the route, the default name is just the registed path. name string middleware Middleware - // if true then https:// - isTLS bool - // the real host - host string + // station + station *Iris // this is used to convert /mypath/:aparam/:something to -> /mypath/%v/%v and /mypath/* -> mypath/%v // we use %v to escape from the conversions between strings,booleans and integers. // used inside custom html template func 'url' @@ -59,7 +47,7 @@ type ( var _ IRoute = &Route{} // NewRoute creates, from a path string, and a slice of HandlerFunc -func NewRoute(method string, registedPath string, middleware Middleware) *Route { +func NewRoute(method string, registedPath string, middleware Middleware, station *Iris) *Route { domain := "" //dirdy but I'm not touching this again:P if registedPath[0] != SlashByte && strings.Contains(registedPath, ".") && (strings.IndexByte(registedPath, SlashByte) == -1 || strings.IndexByte(registedPath, SlashByte) > strings.IndexByte(registedPath, '.')) { @@ -74,7 +62,7 @@ func NewRoute(method string, registedPath string, middleware Middleware) *Route //e.g /admin.ideopod.com/hey //then just remove the first slash and re-execute the NewRoute and return it registedPath = registedPath[1:] - return NewRoute(method, registedPath, middleware) + return NewRoute(method, registedPath, middleware, station) } //if it's just the domain, then set it(registedPath) as the domain //and after set the registedPath to a slash '/' for the path part @@ -88,11 +76,15 @@ func NewRoute(method string, registedPath string, middleware Middleware) *Route } } - r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware, name: registedPath, formattedPath: registedPath} + r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware, name: registedPath, formattedPath: registedPath, station: station} r.formatPath() return r } +func (r *Route) isWildcard() bool { + return r.domain != r.station.server.Hostname() && r.domain == PrefixDynamicSubdomain +} + func (r *Route) formatPath() { // we don't care about performance here, no runtime func. @@ -161,22 +153,14 @@ func (r *Route) HasCors() bool { return RouteConflicts(r, "httpmethod") } -func (r *Route) setTLS(isSecure bool) { - r.isTLS = isSecure -} - -func (r *Route) setHost(s string) { - r.host = s -} - -// Parse used to check arguments with the route's named parameters and return the correct url -// second return value is false when the action cannot be done -func (r *Route) Parse(args ...interface{}) (string, bool) { +// ParsePath used to check arguments with the route's named parameters and return the correct url +// if parse failed returns empty string +func (r *Route) ParsePath(args ...interface{}) string { argsLen := len(args) // we have named parameters but arguments not given if argsLen == 0 && r.formattedParts > 0 { - return "", false + return "" } // we have arguments but they are much more than the named parameters @@ -205,10 +189,10 @@ func (r *Route) Parse(args ...interface{}) (string, bool) { parameter := strings.Join(argsString, Slash) result := fmt.Sprintf(r.formattedPath, parameter) - return result, true + return result } // 2 if !1 return false - return "", false + return "" } arguments := args[0:] @@ -228,20 +212,53 @@ func (r *Route) Parse(args ...interface{}) (string, bool) { } } - return fmt.Sprintf(r.formattedPath, arguments...), true + return fmt.Sprintf(r.formattedPath, arguments...) } -// GetURI returns the GetDomain() + Parse(...optional named parameters if route is dynamic) -// instead of Parse it just returns an empty string if path parse is failed -func (r *Route) GetURI(args ...interface{}) (uri string) { +// ParseURI returns the subdomain+ host + ParsePath(...optional named parameters if route is dynamic) +// returns an empty string if parse is failed +func (r *Route) ParseURI(args ...interface{}) (uri string) { scheme := "http://" - if r.isTLS { + if r.station.server.IsSecure() { scheme = "https://" } - host := r.host + host := r.station.server.Host() - if parsedPath, ok := r.Parse(args...); ok { + arguments := args[0:] + + // join arrays as arguments + for i, v := range arguments { + if arr, ok := v.([]string); ok { + if len(arr) > 0 { + interfaceArr := make([]interface{}, len(arr)) + for j, sv := range arr { + interfaceArr[j] = sv + } + arguments[i] = interfaceArr[0] + arguments = append(arguments, interfaceArr[1:]...) + } + + } + } + + // if it's dynamic subdomain then the first argument is the subdomain part + if r.isWildcard() { + if len(arguments) == 0 { // it's a wildcard subdomain but not arguments + return + } + + if subdomain, ok := arguments[0].(string); ok { + host = subdomain + "." + host + } else { + // it is not array because we join them before. if not pass a string then this is not a subdomain part, return empty uri + return + } + + arguments = arguments[1:] + } + + if parsedPath := r.ParsePath(arguments...); parsedPath != "" { uri = scheme + host + parsedPath } diff --git a/router.go b/router.go index b07e6822..1df88bac 100644 --- a/router.go +++ b/router.go @@ -45,8 +45,6 @@ const ( ) var ( - // PrefixDynamicSubdomainBytes is the prefix (as []byte) which dynamic subdomains are registed to, as virtual. Used internaly by Iris but good to know. - PrefixDynamicSubdomainBytes = []byte(PrefixDynamicSubdomain) // HTTP Methods(2) @@ -220,26 +218,6 @@ func (r *router) optimize() { r.optimized = true } -// optimizeLookups runs AFTER server's listen -func (r *router) optimizeLookups() { - if r.station.server != nil && r.station.server.IsListening() { - // set the isTLS on all routes and the listening full host - listeningHost := r.station.server.Listener().Addr().String() - for idx := range r.lookups { - theR := r.lookups[idx] - theR.setTLS(r.station.server.IsSecure()) - if theR.GetDomain() == "" { // means local, no subdomain - theR.setHost(listeningHost) - } else { - // it's a subdomain route - theR.setHost(theR.GetDomain() + "." + listeningHost) - } - - } - } - -} - // notFound internal method, it justs takes the context from pool ( in order to have the custom errors available) and procedure a Not Found 404 error // this is being called when no route was found used on the ServeRequest. func (r *router) notFound(reqCtx *fasthttp.RequestCtx) { @@ -281,13 +259,14 @@ func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) { method := utils.BytesToString(reqCtx.Method()) host := utils.BytesToString(reqCtx.Host()) fulldomain := "" - if strings.Count(host, ".") >= 2 { + if strings.Count(host, ".") >= 2 && host != r.station.server.Host() { if portIdx := strings.Index(host, ":"); portIdx != -1 { fulldomain = host[0:portIdx] } else { fulldomain = host } } + path := utils.BytesToString(r.getRequestPath(reqCtx)) tree := r.garden.first for tree != nil { @@ -297,7 +276,6 @@ func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) { } else if strings.Index(tree.domain, PrefixDynamicSubdomain) != -1 { // it's a dynamic virtual subdomain path = PrefixDynamicSubdomain + path } - } if r.methodMatch(tree.method, method) { @@ -307,6 +285,7 @@ func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) { } tree = tree.next } + //not found, get the first's pool and use that to send a custom http error(if setted) r.notFound(reqCtx) } diff --git a/server/server.go b/server/server.go index 186f76a2..70bc8dd2 100644 --- a/server/server.go +++ b/server/server.go @@ -58,6 +58,33 @@ func (s *Server) Listener() net.Listener { return s.listener } +// Host returns the Listener().Addr().String(), if server is not listening it returns the config.ListeningAddr +func (s *Server) Host() (host string) { + if s.IsListening() { + return s.Listener().Addr().String() + } else { + return s.Config.ListeningAddr + } +} + +// Hostname returns the hostname part only, if host == 0.0.0.0:8080 it will return the 0.0.0.0 +// if server is not listening it returns the config.ListeningAddr's hostname part +func (s *Server) Hostname() (hostname string) { + if s.IsListening() { + fullhost := s.Listener().Addr().String() + hostname = fullhost[0:strings.IndexByte(fullhost, ':')] // no the port + } else { + fullhost := s.Config.ListeningAddr + if idx := strings.IndexByte(fullhost, ':'); idx > 1 { // at least after second char + hostname = hostname[0 : idx-1] + } else { + hostname = "0.0.0.0" + } + + } + return +} + //Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here. func (s *Server) Serve(l net.Listener) error { s.listener = l