package router import ( "embed" "errors" "fmt" stdfs "io/fs" "net/http" "os" "path" "path/filepath" "reflect" "runtime" "strings" "time" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/hero" "github.com/kataras/iris/v12/macro" macroHandler "github.com/kataras/iris/v12/macro/handler" "github.com/kataras/golog" ) // MethodNone is a Virtual method // to store the "offline" routes. const MethodNone = "NONE" // AllMethods contains the valid HTTP Methods: // "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", // "PATCH", "OPTIONS", "TRACE". var AllMethods = []string{ http.MethodGet, http.MethodHead, http.MethodPatch, http.MethodPut, http.MethodPost, http.MethodDelete, http.MethodOptions, http.MethodConnect, http.MethodTrace, } // RegisterMethods adds custom http methods to the "AllMethods" list. // Use it on initialization of your program. func RegisterMethods(newCustomHTTPVerbs ...string) { newMethods := append(AllMethods, newCustomHTTPVerbs...) AllMethods = removeDuplicates(newMethods) } // repository passed to all parties(subrouters), it's the object witch keeps // all the routes. type repository struct { routes []*Route paths map[string]*Route // only the fullname path part, required at CreateRoutes for registering index page. } func (repo *repository) get(routeName string) *Route { for _, r := range repo.routes { if r.Name == routeName { return r } } return nil } func (repo *repository) getRelative(r *Route) *Route { if r.tmpl.IsTrailing() || !macroHandler.CanMakeHandler(r.tmpl) { return nil } for _, route := range repo.routes { if r.Subdomain == route.Subdomain && r.StatusCode == route.StatusCode && r.Method == route.Method && r.FormattedPath == route.FormattedPath && !route.tmpl.IsTrailing() { return route } } return nil } func (repo *repository) getByPath(tmplPath string) *Route { if r, ok := repo.paths[tmplPath]; ok { return r } return nil } func (repo *repository) getAll() []*Route { return repo.routes } func (repo *repository) remove(routeName string) bool { for i, r := range repo.routes { if r.Name == routeName { lastIdx := len(repo.routes) - 1 if lastIdx == i { repo.routes = repo.routes[0:lastIdx] } else { cp := make([]*Route, 0, lastIdx) cp = append(cp, repo.routes[:i]...) repo.routes = append(cp, repo.routes[i+1:]...) } delete(repo.paths, r.tmpl.Src) return true } } return false } func (repo *repository) register(route *Route, rule RouteRegisterRule) (*Route, error) { for i, r := range repo.routes { // 14 August 2019 allow register same path pattern with different macro functions, // see #1058 if route.DeepEqual(r) { if rule == RouteSkip { return r, nil } else if rule == RouteError { return nil, fmt.Errorf("new route: %s conflicts with an already registered one: %s route", route.String(), r.String()) } else if rule == RouteOverlap { overlapRoute(r, route) return route, nil } else { // replace existing with the latest one, the default behavior. repo.routes = append(repo.routes[:i], repo.routes[i+1:]...) } continue } } // fmt.Printf("repo.routes append:\t%#+v\n\n", route) repo.routes = append(repo.routes, route) if route.StatusCode == 0 { // a common resource route, not a status code error handler. if repo.paths == nil { repo.paths = make(map[string]*Route) } repo.paths[route.tmpl.Src] = route } return route, nil } var defaultOverlapFilter = func(ctx *context.Context) bool { if ctx.IsStopped() { // It's stopped and the response can be overridden by a new handler. // An exception of compress writer, which does not implement Reseter (and it shouldn't): rs, ok := ctx.ResponseWriter().(context.ResponseWriterReseter) return ok && rs.Reset() } // It's not stopped, all OK no need to execute the alternative route. return false } func overlapRoute(r *Route, next *Route) { next.BuildHandlers() nextHandlers := next.Handlers[0:] isErrorRoutes := r.StatusCode > 0 && next.StatusCode > 0 decisionHandler := func(ctx *context.Context) { ctx.Next() if isErrorRoutes { // fixes issue #1602. // If it's an error we don't need to reset (see defaultOverlapFilter) // its status code(!) and its body, we just check if it was proceed or not. if !ctx.IsStopped() { return } } else { prevStatusCode := ctx.GetStatusCode() if !defaultOverlapFilter(ctx) { return } // set the status code that it was stopped with. // useful for dependencies with StopWithStatus(XXX) // instead of raw ctx.StopExecution(). // The func_result now also catch the last registered status code // of the chain, unless the controller returns an integer. // See _examples/authenticated-controller. if prevStatusCode > 0 { // An exception when stored error // exists and it's type of ErrNotFound. // Example: // Version was not found: // we need to be able to send the status on the last not found version // but reset the status code if a next available matched version was found. // see the versioning package. if !errors.Is(ctx.GetErr(), context.ErrNotFound) { ctx.StatusCode(prevStatusCode) } } } ctx.SetErr(nil) // clear any stored error. // Set the route to the next one and execute it. ctx.SetCurrentRoute(next.ReadOnly) ctx.HandlerIndex(0) ctx.Do(nextHandlers) } r.builtinBeginHandlers = append(context.Handlers{decisionHandler}, r.builtinBeginHandlers...) r.overlappedLink = next } // APIBuilder the visible API for constructing the router // and child routers. type APIBuilder struct { // the application logger. logger *golog.Logger // parent is the creator of this Party. // It is nil on Root. parent *APIBuilder // currently it's not used anywhere. // the per-party APIBuilder with DI. apiBuilderDI *APIContainer // the api builder global macros registry macros *macro.Macros // the per-party (and its children) values map // that may help on building the API // when source code is splitted between projects. // Initialized on Properties method. properties context.Map // the api builder global routes repository routes *repository // disables the debug logging of routes under a per-party and its children. routesNoLog bool // the per-party handlers, order // of handlers registration matters, // inherited by children unless Reset is called. middleware context.Handlers middlewareErrorCode context.Handlers // the global middleware handlers, order of call doesn't matters, order // of handlers registration matters. We need a secondary field for this // because `UseGlobal` registers handlers that should be executed // even before the `middleware` handlers, and in the same time keep the order // of handlers registration, so the same type of handlers are being called in order. beginGlobalHandlers context.Handlers // the per-party done handlers, order matters. doneHandlers context.Handlers // global done handlers, order doesn't matter. doneGlobalHandlers context.Handlers // the per-party relative path. relativePath string // allowMethods are filled with the `AllowMethods` method. // They are used to create new routes // per any party's (and its children) routes registered // if the method "x" wasn't registered already via the `Handle` (and its extensions like `Get`, `Post`...). allowMethods []string // the per-party (and its children) execution rules for begin, main and done handlers. handlerExecutionRules ExecutionRules // the per-party (and its children) route registration rule, see `SetRegisterRule`. routeRegisterRule RouteRegisterRule // routerFilterHandlers holds a reference // of the handlers used by the current and its parent Party's registered // router filters. Inherited by children unless `Reset` (see `UseRouter`), routerFilterHandlers context.Handlers // routerFilters field is shared across Parties. Each Party registers // one or more middlewares to run before the router itself using the `UseRouter` method. // Each Party calls the shared filter (`partyMatcher`) that decides if its `UseRouter` handlers // can be executed. By default it's based on party's static path and/or subdomain, // it can be modified through an `Application.SetPartyMatcher` call // once before or after routerFilters filled. // // The Key is the Party (instance of APIBuilder), // value wraps the partyFilter + the handlers registered through `UseRouter`. // See `GetRouterFilters` too. routerFilters map[Party]*Filter // partyMatcher field is shared across all Parties, // can be modified through the Application level only. // // It defaults to the internal, simple, "defaultPartyMatcher". // It applies when "routerFilters" are used. partyMatcher PartyMatcherFunc } var ( _ Party = (*APIBuilder)(nil) _ PartyMatcher = (*APIBuilder)(nil) _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handler (routerHandler) ) // NewAPIBuilder creates & returns a new builder // which is responsible to build the API and the router handler. func NewAPIBuilder(logger *golog.Logger) *APIBuilder { return &APIBuilder{ logger: logger, parent: nil, macros: macro.Defaults, relativePath: "/", routes: new(repository), apiBuilderDI: &APIContainer{Container: hero.New().WithLogger(logger)}, routerFilters: make(map[Party]*Filter), partyMatcher: defaultPartyMatcher, } } // Logger returns the Application Logger. func (api *APIBuilder) Logger() *golog.Logger { return api.logger } // IsRoot reports whether this Party is the root Application's one. // It will return false on all children Parties, no exception. func (api *APIBuilder) IsRoot() bool { return api.parent == nil } /* If requested: // GetRoot returns the very first Party (the Application). func (api *APIBuilder) GetRoot() *APIBuilder { root := api.parent for root != nil { root = api.parent } return root }*/ // ConfigureContainer accepts one or more functions that can be used // to configure dependency injection features of this Party // such as register dependency and register handlers that will automatically inject any valid dependency. // However, if the "builder" parameter is nil or not provided then it just returns the *APIContainer, // which automatically initialized on Party allocation. // // It returns the same `APIBuilder` featured with Dependency Injection. func (api *APIBuilder) ConfigureContainer(builder ...func(*APIContainer)) *APIContainer { if api.apiBuilderDI.Self == nil { api.apiBuilderDI.Self = api } for _, b := range builder { if b != nil { b(api.apiBuilderDI) } } return api.apiBuilderDI } // EnsureStaticBindings panics on struct handler (controller) // if at least one input binding depends on the request and not in a static structure. // Should be called before `RegisterDependency`. func (api *APIBuilder) EnsureStaticBindings() Party { diContainer := api.ConfigureContainer() diContainer.Container.DisableStructDynamicBindings = true return api } // RegisterDependency calls the `ConfigureContainer.RegisterDependency` method // with the provided value(s). See `HandleFunc` and `PartyConfigure` methods too. func (api *APIBuilder) RegisterDependency(dependencies ...interface{}) { diContainer := api.ConfigureContainer() for i, dependency := range dependencies { if dependency == nil { api.logger.Warnf("Party: %s: nil dependency on position: %d", api.relativePath, i) continue } diContainer.RegisterDependency(dependency) } } // HandleFunc registers a route on HTTP verb "method" and relative, to this Party, path. // It is like the `Handle` method but it accepts one or more "handlersFn" functions // that each one of them can accept any input arguments as the HTTP request and // output a result as the HTTP response. Specifically, // the input of the "handlersFn" can be any registered dependency // (see ConfigureContainer().RegisterDependency) // or leave the framework to parse the request and fill the values accordingly. // The output of the "handlersFn" can be any output result: // // custom structs , string, []byte, int, error, // a combination of the above, hero.Result(hero.View | hero.Response) and more. // // If more than one handler function is registered // then the execution happens without the nessecity of the `Context.Next` method, // simply, to stop the execution and not continue to the next "handlersFn" in chain // you should return an `iris.ErrStopExecution`. // // Example Code: // // The client's request body and server's response body Go types. // Could be any data structure. // // type ( // request struct { // Firstname string `json:"firstname"` // Lastname string `json:"lastname"` // } // // response struct { // ID uint64 `json:"id"` // Message string `json:"message"` // } // ) // // Register the route hander. // // HTTP VERB ROUTE PATH ROUTE HANDLER // app.HandleFunc("PUT", "/users/{id:uint64}", updateUser) // // Code the route handler function. // Path parameters and request body are binded // automatically. // The "id" uint64 binds to "{id:uint64}" route path parameter and // the "input" binds to client request data such as JSON. // // func updateUser(id uint64, input request) response { // // [custom logic...] // // return response{ // ID:id, // Message: "User updated successfully", // } // } // // Simulate a client request which sends data // to the server and prints out the response. // // curl --request PUT -d '{"firstname":"John","lastname":"Doe"}' \ // -H "Content-Type: application/json" \ // http://localhost:8080/users/42 // // { // "id": 42, // "message": "User updated successfully" // } // // See the `ConfigureContainer` for more features regrading // the dependency injection, mvc and function handlers. // // This method is just a shortcut of the `ConfigureContainer().Handle`. func (api *APIBuilder) HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route { return api.ConfigureContainer().Handle(method, relativePath, handlersFn...) } // UseFunc registers a function which can accept one or more // dependencies (see RegisterDependency) and returns an iris.Handler // or a result of and/or an error. // // This method is just a shortcut of the `ConfigureContainer().Use`. func (api *APIBuilder) UseFunc(handlersFn ...interface{}) { api.ConfigureContainer().Use(handlersFn...) } // GetRelPath returns the current party's relative path. // i.e: // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". // if r := app.Party("www.") or app.Subdomain("www") then the `r.GetRelPath()` is the "www.". func (api *APIBuilder) GetRelPath() string { return api.relativePath } // AllowMethods will re-register the future routes that will be registered // via `Handle`, `Get`, `Post`, ... to the given "methods" on that Party and its children "Parties", // duplicates are not registered. // // Call of `AllowMethod` will override any previous allow methods. func (api *APIBuilder) AllowMethods(methods ...string) Party { api.allowMethods = methods return api } // SetExecutionRules alters the execution flow of the route handlers outside of the handlers themselves. // // For example, if for some reason the desired result is the (done or all) handlers to be executed no matter what // even if no `ctx.Next()` is called in the previous handlers, including the begin(`Use`), // the main(`Handle`) and the done(`Done`) handlers themselves, then: // // Party#SetExecutionRules(iris.ExecutionRules { // Begin: iris.ExecutionOptions{Force: true}, // Main: iris.ExecutionOptions{Force: true}, // Done: iris.ExecutionOptions{Force: true}, // }) // // Note that if : true then the only remained way to "break" the handler chain is by `ctx.StopExecution()` now that `ctx.Next()` does not matter. // // These rules are per-party, so if a `Party` creates a child one then the same rules will be applied to that as well. // Reset of these rules (before `Party#Handle`) can be done with `Party#SetExecutionRules(iris.ExecutionRules{})`. // // The most common scenario for its use can be found inside Iris MVC Applications; // when we want the `Done` handlers of that specific mvc app's `Party` // to be executed but we don't want to add `ctx.Next()` on the `OurController#EndRequest`. // // Returns this Party. // // Example: https://github.com/kataras/iris/tree/master/_examples/mvc/middleware/without-ctx-next func (api *APIBuilder) SetExecutionRules(executionRules ExecutionRules) Party { api.handlerExecutionRules = executionRules return api } // RouteRegisterRule is a type of uint8. // Defines the register rule for new routes that already exists. // Available values are: RouteOverride, RouteSkip and RouteError. // // See `Party#SetRegisterRule`. type RouteRegisterRule uint8 const ( // RouteOverride replaces an existing route with the new one, the default rule. RouteOverride RouteRegisterRule = iota // RouteSkip keeps the original route and skips the new one. RouteSkip // RouteError log when a route already exists, shown after the `Build` state, // server never starts. RouteError // RouteOverlap will overlap the new route to the previous one. // If the route stopped and its response can be reset then the new route will be execute. RouteOverlap ) // SetRegisterRule sets a `RouteRegisterRule` for this Party and its children. // Available values are: // * RouteOverride (the default one) // * RouteSkip // * RouteError // * RouteOverlap. func (api *APIBuilder) SetRegisterRule(rule RouteRegisterRule) Party { api.routeRegisterRule = rule return api } // Handle registers a route to this Party. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // // Returns a *Route, app will throw any errors later on. func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route { return api.handle(0, method, relativePath, handlers...) } // handle registers a full route to this Party. // Use Handle or Get, Post, Put, Delete and et.c. instead. func (api *APIBuilder) handle(errorCode int, method string, relativePath string, handlers ...context.Handler) *Route { routes := api.createRoutes(errorCode, []string{method}, relativePath, handlers...) var route *Route // the last one is returned. var err error for _, route = range routes { if route == nil { continue } // global route.topLink = api.routes.getRelative(route) if route, err = api.routes.register(route, api.routeRegisterRule); err != nil { api.logger.Error(err) break } } return route } // HandleMany works like `Handle` but can receive more than one // paths separated by spaces and returns always a slice of *Route instead of a single instance of Route. // // It's useful only if the same handler can handle more than one request paths, // otherwise use `Party` which can handle many paths with different handlers and middlewares. // // Usage: // // app.HandleMany("GET", "/user /user/{id:uint64} /user/me", genericUserHandler) // // At the other side, with `Handle` we've had to write: // // app.Handle("GET", "/user", userHandler) // app.Handle("GET", "/user/{id:uint64}", userByIDHandler) // app.Handle("GET", "/user/me", userMeHandler) // // app.HandleMany("GET POST", "/path", handler) func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti string, handlers ...context.Handler) (routes []*Route) { // at least slash // a space // at least one other slash for the next path paths := splitPath(relativePathorMulti) methods := splitMethod(methodOrMulti) for _, p := range paths { if p != "" { for _, method := range methods { if method == "" { method = "ANY" } if method == "ANY" || method == "ALL" { routes = append(routes, api.Any(p, handlers...)...) continue } routes = append(routes, api.Handle(method, p, handlers...)) } } } return } // HandleDir registers a handler that serves HTTP requests // with the contents of a file system (physical or embedded). // // first parameter : the route path // second parameter : the file system needs to be served // third parameter : not required, the serve directory options. // // Alternatively, to get just the handler for that look the FileServer function instead. // // api.HandleDir("/static", iris.Dir("./assets"), iris.DirOptions{IndexName: "/index.html", Compress: true}) // // Returns all the registered routes, including GET index and path patterm and HEAD. // // Usage: // HandleDir("/public", "./assets", DirOptions{...}) or // HandleDir("/public", iris.Dir("./assets"), DirOptions{...}) // OR // //go:embed assets/* // var filesystem embed.FS // HandleDir("/public",filesystem, DirOptions{...}) // OR to pick a specific folder of the embedded filesystem: // import "io/fs" // subFilesystem, err := fs.Sub(filesystem, "assets") // HandleDir("/public",subFilesystem, DirOptions{...}) // // Examples: // https://github.com/kataras/iris/tree/master/_examples/file-server func (api *APIBuilder) HandleDir(requestPath string, fsOrDir interface{}, opts ...DirOptions) (routes []*Route) { options := DefaultDirOptions if len(opts) > 0 { options = opts[0] } var fs http.FileSystem switch v := fsOrDir.(type) { case string: fs = http.Dir(v) case http.FileSystem: fs = v case embed.FS: direEtries, err := v.ReadDir(".") if err != nil { panic(err) } if len(direEtries) == 0 { panic("HandleDir: no directories found under the embedded file system") } subfs, err := stdfs.Sub(v, direEtries[0].Name()) if err != nil { panic(err) } fs = http.FS(subfs) case stdfs.FS: fs = http.FS(v) default: panic(fmt.Sprintf(`HandleDir: unexpected "fsOrDir" argument type of %T (string or http.FileSystem)`, v)) } h := FileServer(fs, options) description := "file server" if d, ok := fs.(http.Dir); ok { description = string(d) } fileName, lineNumber := context.HandlerFileLine(h) // take those before StripPrefix. // if subdomain, we get the full path of the path only, // because a subdomain can have parties as well // and we need that path to call the `StripPrefix`. _, fullpath := splitSubdomainAndPath(joinPath(api.relativePath, requestPath)) if fullpath != "/" { h = StripPrefix(fullpath, h) } if api.GetRouteByPath(fullpath) == nil { // register index if not registered by the end-developer. routes = api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h) } requestPath = joinPath(requestPath, WildcardFileParam()) routes = append(routes, api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)...) for _, route := range routes { if route.Method == http.MethodHead { } else { route.Describe(description) route.SetSourceLine(fileName, lineNumber) } if _, err := api.routes.register(route, api.routeRegisterRule); err != nil { api.logger.Error(err) break } } return routes } // CreateRoutes returns a list of Party-based Routes. // It does NOT registers the route. Use `Handle, Get...` methods instead. // This method can be used for third-parties Iris helpers packages and tools // that want a more detailed view of Party-based Routes before take the decision to register them. func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handlers ...context.Handler) []*Route { return api.createRoutes(0, methods, relativePath, handlers...) } // RemoveRoute deletes a registered route by its name before `Application.Listen`. // The default naming for newly created routes is: method + subdomain + path. // Reports whether a route with that name was found and removed successfully. // // Note that this method applies to all Parties (sub routers) // even if each of the Parties have access to this method, // as the route name is unique per Iris Application. func (api *APIBuilder) RemoveRoute(routeName string) bool { return api.routes.remove(routeName) } func (api *APIBuilder) createRoutes(errorCode int, methods []string, relativePath string, handlers ...context.Handler) []*Route { if statusCodeSuccessful(errorCode) { errorCode = 0 } if errorCode == 0 { if len(methods) == 0 || methods[0] == "ALL" || methods[0] == "ANY" { // then use like it was .Any return api.Any(relativePath, handlers...) } } // no clean path yet because of subdomain indicator/separator which contains a dot. // but remove the first slash if the relative has already ending with a slash // it's not needed because later on we do normalize/clean the path, but better do it here too // for any future updates. if api.relativePath[len(api.relativePath)-1] == '/' { if relativePath[0] == '/' { relativePath = relativePath[1:] } } filename, line := getCaller() fullpath := api.relativePath + relativePath // for now, keep the last "/" if any, "/xyz/" if len(handlers) == 0 { api.logger.Errorf("missing handlers for route[%s:%d] %s: %s", filename, line, strings.Join(methods, ", "), fullpath) return nil } // note: this can not change the caller's handlers as they're but the entry values(handlers) // of `middleware`, `doneHandlers` and `handlers` can. // So if we just put `api.middleware` or `api.doneHandlers` // then the next `Party` will have those updated handlers // but dev may change the rules for that child Party, so we have to make clones of them here. var ( // global middleware to error handlers as well. beginHandlers = api.beginGlobalHandlers doneHandlers = api.doneGlobalHandlers ) if errorCode == 0 { beginHandlers = context.JoinHandlers(beginHandlers, api.middleware) doneHandlers = context.JoinHandlers(doneHandlers, api.doneHandlers) } else { beginHandlers = context.JoinHandlers(beginHandlers, api.middlewareErrorCode) } mainHandlers := context.Handlers(handlers) // before join the middleware + handlers + done handlers and apply the execution rules. mainHandlerName, mainHandlerIndex := context.MainHandlerName(mainHandlers) mainHandlerFileName, mainHandlerFileNumber := context.HandlerFileLineRel(handlers[mainHandlerIndex]) // re-calculate mainHandlerIndex in favor of the middlewares. mainHandlerIndex = len(beginHandlers) + mainHandlerIndex // TODO: for UseGlobal/DoneGlobal that doesn't work. applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers) // global begin handlers -> middleware that are registered before route registration // -> handlers that are passed to this Handle function. routeHandlers := context.JoinHandlers(beginHandlers, mainHandlers) // -> done handlers routeHandlers = context.JoinHandlers(routeHandlers, doneHandlers) // here we separate the subdomain and relative path subdomain, path := splitSubdomainAndPath(fullpath) // if allowMethods are empty, then simply register with the passed, main, method. methods = removeDuplicates(append(api.allowMethods, methods...)) routes := make([]*Route, len(methods)) for i, m := range methods { // single, empty method for error handlers. route, err := NewRoute(api, errorCode, m, subdomain, path, routeHandlers, *api.macros) if err != nil { // template path parser errors: api.logger.Errorf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path) continue } // The caller tiself, if anonymous, it's the first line of `app.X("/path", here)` route.RegisterFileName = filename route.RegisterLineNumber = line route.MainHandlerName = mainHandlerName route.MainHandlerIndex = mainHandlerIndex // The main handler source, could be the same as the register's if anonymous. route.SourceFileName = mainHandlerFileName route.SourceLineNumber = mainHandlerFileNumber // Add UseGlobal & DoneGlobal Handlers // route.Use(api.beginGlobalHandlers...) // route.Done(api.doneGlobalHandlers...) route.NoLog = api.routesNoLog routes[i] = route } return routes } func removeDuplicates(elements []string) (result []string) { seen := make(map[string]struct{}) for v := range elements { val := elements[v] if _, ok := seen[val]; !ok { seen[val] = struct{}{} result = append(result, val) } } return result } // Party returns a new child Party which inherites its // parent's options and middlewares. // A Party groups routes which may have the same prefix or subdomain and share same middlewares. // // To create a group of routes for subdomains // use the `Subdomain` or `WildcardSubdomain` methods // or pass a "relativePath" of "admin." or "*." respectfully. func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party { // if app.Party("/"), root party or app.Party("/user") == app.Party("/user") // then just add the middlewares and return itself. // if relativePath == "" || api.relativePath == relativePath { // api.Use(handlers...) // return api // } // ^ No, this is wrong, let the developer do its job, if she/he wants a copy let have it, // it's a pure check as well, a path can be the same even if it's the same as its parent, i.e. // app.Party("/user").Party("/user") should result in a /user/user, not a /user. parentPath := api.relativePath dot := string(SubdomainPrefix[0]) if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) { // if ends with . , i.e admin., it's subdomain-> parentPath = parentPath[1:] // remove first slash } // this is checked later on but for easier debug is better to do it here: if api.relativePath[len(api.relativePath)-1] == '/' && relativePath[0] == '/' { relativePath = relativePath[1:] // remove first slash if parent ended with / and new one started with /. } // if it's subdomain then it has priority, i.e: // api.relativePath == "admin." // relativePath == "panel." // then it should be panel.admin. // instead of admin.panel. if hasSubdomain(parentPath) && hasSubdomain(relativePath) { relativePath = relativePath + parentPath parentPath = "" } fullpath := parentPath + relativePath // append the parent's + child's handlers middleware := context.JoinHandlers(api.middleware, handlers) // the allow methods per party and its children. allowMethods := make([]string, len(api.allowMethods)) copy(allowMethods, api.allowMethods) // make a copy of the parent properties. properties := make(context.Map, len(api.properties)) for k, v := range api.properties { properties[k] = v } childAPI := &APIBuilder{ // global/api builder logger: api.logger, macros: api.macros, properties: properties, routes: api.routes, routesNoLog: api.routesNoLog, beginGlobalHandlers: api.beginGlobalHandlers, doneGlobalHandlers: api.doneGlobalHandlers, // per-party/children parent: api, middleware: middleware, middlewareErrorCode: context.JoinHandlers(api.middlewareErrorCode, context.Handlers{}), doneHandlers: api.doneHandlers[0:], routerFilters: api.routerFilters, routerFilterHandlers: api.routerFilterHandlers, partyMatcher: api.partyMatcher, relativePath: fullpath, allowMethods: allowMethods, handlerExecutionRules: api.handlerExecutionRules, routeRegisterRule: api.routeRegisterRule, apiBuilderDI: &APIContainer{ // attach a new Container with correct dynamic path parameter start index for input arguments // based on the fullpath. Container: api.apiBuilderDI.Container.Clone(), }, } return childAPI } // PartyFunc same as `Party`, groups routes that share a base path or/and same handlers. // However this function accepts a function that receives this created Party instead. // Returns the Party in order the caller to be able to use this created Party to continue the // top-bottom routes "tree". // // Note: `iris#Party` and `core/router#Party` describes the exactly same interface. // // Usage: // // app.PartyFunc("/users", func(u iris.Party){ // u.Use(authMiddleware, logMiddleware) // u.Get("/", getAllUsers) // u.Post("/", createOrUpdateUser) // u.Delete("/", deleteUser) // }) // // Look `Party` for more. func (api *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Party)) Party { p := api.Party(relativePath) partyBuilderFunc(p) return p } type ( // PartyConfigurator is an interface which all child parties that are registered // through `PartyConfigure` should implement. PartyConfigurator interface { Configure(parent Party) } // StrictlyPartyConfigurator is an optional interface which a `PartyConfigurator` // can implement to make sure that all exported fields having a not-nin, non-zero // value before server starts. // StrictlyPartyConfigurator interface { // Strict() bool // } // Good idea but a `mvc or bind:"required"` is a better one I think. ) // PartyConfigure like `Party` and `PartyFunc` registers a new children Party // but instead it accepts a struct value which should implement the PartyConfigurator interface. // // PartyConfigure accepts the relative path of the child party // (As an exception, if it's empty then all configurators are applied to the current Party) // and one or more Party configurators and // executes the PartyConfigurator's Configure method. // // If the end-developer registered one or more dependencies upfront through // RegisterDependencies or ConfigureContainer.RegisterDependency methods // and "p" is a pointer to a struct then try to bind the unset/zero exported fields // to the registered dependencies, just like we do with Controllers. // Useful when the api's dependencies amount are too much to pass on a function. // // Usage: // // app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...}) // // Where UsersAPI looks like: // // type UsersAPI struct { [...] } // func(api *UsersAPI) Configure(router iris.Party) { // router.Get("/{id:uuid}", api.getUser) // [...] // } // // Usage with (static) dependencies: // // app.RegisterDependency(userRepo, ...) // app.PartyConfigure("/users", new(api.UsersAPI)) func (api *APIBuilder) PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party { var child Party if relativePath == "" { child = api } else { child = api.Party(relativePath) } for _, p := range partyReg { if p == nil { continue } if len(api.apiBuilderDI.Container.Dependencies) > 0 { if typ := reflect.TypeOf(p); typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { api.apiBuilderDI.Container.Struct(p, -1) } } p.Configure(child) } return child } // Subdomain returns a new party which is responsible to register routes to // this specific "subdomain". // // If called from a child party then the subdomain will be prepended to the path instead of appended. // So if app.Subdomain("admin").Subdomain("panel") then the result is: "panel.admin.". func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party { if api.relativePath == SubdomainWildcardIndicator { // cannot concat wildcard subdomain with something else api.logger.Errorf("cannot concat parent wildcard subdomain with anything else -> %s , %s", api.relativePath, subdomain) return api } if l := len(subdomain); l < 1 { return api } else if subdomain[l-1] != '.' { subdomain += "." } return api.Party(subdomain, middleware...) } // WildcardSubdomain returns a new party which is responsible to register routes to // a dynamic, wildcard(ed) subdomain. A dynamic subdomain is a subdomain which // can reply to any subdomain requests. Server will accept any subdomain // (if not static subdomain found) and it will search and execute the handlers of this party. func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party { if hasSubdomain(api.relativePath) { // cannot concat static subdomain with a dynamic one, wildcard should be at the root level api.logger.Errorf("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s", api.relativePath) return api } return api.Subdomain(SubdomainWildcardIndicator, middleware...) } // Macros returns the macro collection that is responsible // to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path func (api *APIBuilder) Macros() *macro.Macros { return api.macros } // Properties returns the original Party's properties map, // it can be modified before server startup but not afterwards. func (api *APIBuilder) Properties() context.Map { if api.properties == nil { api.properties = make(context.Map) } return api.properties } // GetRoutes returns the routes information, // some of them can be changed at runtime some others not. // // Needs refresh of the router to Method or Path or Handlers changes to take place. func (api *APIBuilder) GetRoutes() []*Route { return api.routes.getAll() } // GetRoute returns the registered route based on its name, otherwise nil. // One note: "routeName" should be case-sensitive. func (api *APIBuilder) GetRoute(routeName string) *Route { return api.routes.get(routeName) } // GetRouteByPath returns the registered route based on the template path (`Route.Tmpl().Src`). func (api *APIBuilder) GetRouteByPath(tmplPath string) *Route { return api.routes.getByPath(tmplPath) } // GetRoutesReadOnly returns the registered routes with "read-only" access, // you cannot and you should not change any of these routes' properties on request state, // you can use the `GetRoutes()` for that instead. // // It returns interface-based slice instead of the real ones in order to apply // safe fetch between context(request-state) and the builded application. // // Look `GetRouteReadOnly` too. func (api *APIBuilder) GetRoutesReadOnly() []context.RouteReadOnly { routes := api.GetRoutes() readOnlyRoutes := make([]context.RouteReadOnly, len(routes)) for i, r := range routes { readOnlyRoutes[i] = r.ReadOnly } return readOnlyRoutes } // GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil. // One note: "routeName" should be case-sensitive. Used by the context to get the current route. // It returns an interface instead to reduce wrong usage and to keep the decoupled design between // the context and the routes. // Look `GetRoutesReadOnly` to fetch a list of all registered routes. // // Look `GetRoute` for more. func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly { r := api.GetRoute(routeName) if r == nil { return nil } return r.ReadOnly } // GetRouteReadOnlyByPath returns the registered read-only route based on the template path (`Route.Tmpl().Src`). func (api *APIBuilder) GetRouteReadOnlyByPath(tmplPath string) context.RouteReadOnly { r := api.GetRouteByPath(tmplPath) if r == nil { return nil } return r.ReadOnly } // SetRoutesNoLog disables (true) the verbose logging for the next registered // routes under this Party and its children. // // To disable logging for controllers under MVC Application, // see `mvc/Application.SetControllersNoLog` instead. // // Defaults to false when log level is "debug". func (api *APIBuilder) SetRoutesNoLog(disable bool) Party { api.routesNoLog = disable return api } type ( // PartyMatcherFunc used to build a filter which decides // if the given Party is responsible to fire its `UseRouter` handlers or not. // Can be customized through `SetPartyMatcher` method. See `Match` method too. PartyMatcherFunc func(*context.Context, Party) bool // PartyMatcher decides if `UseRouter` handlers should be executed or not. // A different interface becauwe we want to separate // the Party's public API from `UseRouter` internals. PartyMatcher interface { Match(ctx *context.Context) bool } // Filter is a wraper for a Router Filter contains information // for its Party's fullpath, subdomain the Party's // matcher and the associated handlers to be executed before main router's request handler. Filter struct { Matcher PartyMatcher // it's a Party, for freedom that can be changed through a custom matcher which accepts the same filter. Skippers map[*APIBuilder]struct{} // skip execution on these builders ( see `Reset`) Subdomain string Path string Handlers context.Handlers } ) // SetPartyMatcher accepts a function which runs against // a Party and should report whether its `UseRouter` handlers should be executed. // PartyMatchers are run through parent to children. // It modifies the default Party filter that decides // which `UseRouter` middlewares to run before the Router, // each one of those middlewares can skip `Context.Next` or call `Context.StopXXX` // to stop the main router from searching for a route match. // Can be called before or after `UseRouter`, it doesn't matter. func (api *APIBuilder) SetPartyMatcher(matcherFunc PartyMatcherFunc) { if matcherFunc == nil { matcherFunc = defaultPartyMatcher } api.partyMatcher = matcherFunc } // Match reports whether the `UseRouter` handlers should be executed. // Calls its parent's Match if possible. // Implements the `PartyMatcher` interface. func (api *APIBuilder) Match(ctx *context.Context) bool { return api.partyMatcher(ctx, api) } func defaultPartyMatcher(ctx *context.Context, p Party) bool { subdomain, path := splitSubdomainAndPath(p.GetRelPath()) staticPath := staticPath(path) hosts := subdomain != "" if p.IsRoot() { // ALWAYS executed first when registered // through an `Application.UseRouter` call. return true } if hosts { // Note(@kataras): do NOT try to implement something like party matcher for each party // separately. We will introduce a new problem with subdomain inside a subdomain: // they are not by prefix, so parenting calls will not help // e.g. admin. and control.admin, control.admin is a sub of the admin. if !canHandleSubdomain(ctx, subdomain) { return false } } // this is the longest static path. return strings.HasPrefix(ctx.Path(), staticPath) } // GetRouterFilters returns the global router filters. // Read `UseRouter` for more. // The map can be altered before router built. // The router internally prioritized them by the subdomains and // longest static path. // Implements the `RoutesProvider` interface. func (api *APIBuilder) GetRouterFilters() map[Party]*Filter { return api.routerFilters } // UseRouter upserts one or more handlers that will be fired // right before the main router's request handler. // // Use this method to register handlers, that can ran // independently of the incoming request's values, // that they will be executed ALWAYS against ALL children incoming requests. // Example of use-case: CORS. // // Note that because these are executed before the router itself // the Context should not have access to the `GetCurrentRoute` // as it is not decided yet which route is responsible to handle the incoming request. // It's one level higher than the `WrapRouter`. // The context SHOULD call its `Next` method in order to proceed to // the next handler in the chain or the main request handler one. func (api *APIBuilder) UseRouter(handlers ...context.Handler) { if len(handlers) == 0 || handlers[0] == nil { return } beginHandlers := context.Handlers(handlers) // respect any execution rules (begin). api.handlerExecutionRules.Begin.apply(&beginHandlers) beginHandlers = context.JoinHandlers(api.routerFilterHandlers, beginHandlers) if f := api.routerFilters[api]; f != nil && len(f.Handlers) > 0 { // exists. beginHandlers = context.UpsertHandlers(f.Handlers, beginHandlers) // remove dupls. } // we are not using the parent field here, // we need to have control over those values in order to be able to `Reset`. api.routerFilterHandlers = beginHandlers subdomain, path := splitSubdomainAndPath(api.relativePath) api.routerFilters[api] = &Filter{ Matcher: api, Subdomain: subdomain, Path: path, Handlers: beginHandlers, } } // GetDefaultErrorMiddleware returns the application's error pre handlers // registered through `UseError` for the default error handlers. // This is used when no matching error handlers registered // for a specific status code but `UseError` is called to register a middleware, // so the default error handler should make use of those middleware now. func (api *APIBuilder) GetDefaultErrorMiddleware() context.Handlers { return api.middlewareErrorCode } // UseError upserts one or more handlers that will be fired, // as middleware, before any error handler registered through `On(Any)ErrorCode`. // See `OnErrorCode` too. func (api *APIBuilder) UseError(handlers ...context.Handler) { api.middlewareErrorCode = context.UpsertHandlers(api.middlewareErrorCode, handlers) } // Use appends Handler(s) to the current Party's routes and child routes. // If the current Party is the root, then it registers the middleware to all child Parties' routes too. // The given "handlers" will be executed only on matched routes. // // Call order matters, it should be called right before the routes that they care about these handlers. // // If it's called after the routes then these handlers will never be executed. // Use `UseGlobal` if you want to register begin handlers(middleware) // that should be always run before all application's routes. // To register a middleware for error handlers, look `UseError` method instead. func (api *APIBuilder) Use(handlers ...context.Handler) { api.middleware = append(api.middleware, handlers...) } // UseOnce either inserts a middleware, // or on the basis of the middleware already existing, // replace that existing middleware instead. // To register a middleware for error handlers, look `UseError` method instead. func (api *APIBuilder) UseOnce(handlers ...context.Handler) { api.middleware = context.UpsertHandlers(api.middleware, handlers) } // UseGlobal registers handlers that should run at the very beginning. // It prepends those handler(s) to all routes, // including all parties, subdomains and errors. // It doesn't care about call order, it will prepend the handlers to all // existing routes and the future routes that may being registered. // // The given "handlers" will be executed only on matched routes and registered errors. // See `UseRouter` if you want to register middleware that will always run, even on 404 not founds. // // The difference from `.DoneGlobal` is that this/or these Handler(s) are being always running first. // Use of `ctx.Next()` of those handler(s) is necessary to call the main handler or the next middleware. // It's always a good practise to call it right before the `Application#Run` function. func (api *APIBuilder) UseGlobal(handlers ...context.Handler) { for _, r := range api.routes.routes { // r.beginHandlers = append(handlers, r.beginHandlers...) // ^ this is correct but we act global begin handlers as one chain, so // if called last more than one time, after all routes registered, we must somehow // register them by order, so: r.Use(handlers...) } // set as begin handlers for the next routes as well. api.beginGlobalHandlers = append(api.beginGlobalHandlers, handlers...) } // Done appends to the very end, Handler(s) to the current Party's routes and child routes. // The given "handlers" will be executed only on matched routes. // // Call order matters, it should be called right before the routes that they care about these handlers. // // The difference from .Use is that this/or these Handler(s) are being always running last. func (api *APIBuilder) Done(handlers ...context.Handler) { api.doneHandlers = append(api.doneHandlers, handlers...) } // DoneGlobal registers handlers that should run at the very end. // It appends those handler(s) to all routes, // including all parties, subdomains. // It doesn't care about call order, it will append the handlers to all // existing routes and the future routes that may being registered. // // The given "handlers" will be executed only on matched and registered error routes. // // The difference from `.UseGlobal` is that this/or these Handler(s) are being always running last. // Use of `ctx.Next()` at the previous handler is necessary. // It's always a good practise to call it right before the `Application#Run` function. func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { for _, r := range api.routes.routes { r.Done(handlers...) // append the handlers to the existing routes } // set as done handlers for the next routes as well. api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...) } // RemoveHandler deletes a handler from begin and done handlers // based on its name or the handler pc function. // Note that UseGlobal and DoneGlobal handlers cannot be removed // through this method as they were registered to the routes already. // // As an exception, if one of the arguments is a pointer to an int, // then this is used to set the total amount of removed handlers. // // Returns the Party itself for chain calls. // // Should be called before children routes regitration. func (api *APIBuilder) RemoveHandler(namesOrHandlers ...interface{}) Party { var counter *int for _, nameOrHandler := range namesOrHandlers { handlerName := "" switch h := nameOrHandler.(type) { case string: handlerName = h case context.Handler, func(*context.Context): handlerName = context.HandlerName(h) case *int: counter = h default: panic(fmt.Sprintf("remove handler: unexpected type of %T", h)) } api.middleware = removeHandler(handlerName, api.middleware, counter) api.doneHandlers = removeHandler(handlerName, api.doneHandlers, counter) } return api } // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, // and the execution rules. // Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. // // Returns this Party. func (api *APIBuilder) Reset() Party { api.middleware = api.middleware[0:0] api.middlewareErrorCode = api.middlewareErrorCode[0:0] api.ResetRouterFilters() api.doneHandlers = api.doneHandlers[0:0] api.handlerExecutionRules = ExecutionRules{} api.routeRegisterRule = RouteOverride // keep container as it's. return api } // ResetRouterFilters deactivates any previous registered // router filters and the parents ones for this Party. // // Returns this Party. func (api *APIBuilder) ResetRouterFilters() Party { api.routerFilterHandlers = api.routerFilterHandlers[0:0] delete(api.routerFilters, api) if api.parent == nil { // it's the root, stop, nothing else to do here. return api } // Set a filter with empty handlers, the router will find it, execute nothing // and continue with the request handling. This works on Reset() and no UseRouter // and with Reset().UseRouter. subdomain, path := splitSubdomainAndPath(api.relativePath) api.routerFilters[api] = &Filter{ Matcher: api, Handlers: nil, Subdomain: subdomain, Path: path, } return api } // None registers an "offline" route // see context.ExecRoute(routeName) and // party.Routes().Online(handleResultRouteInfo, "GET") and // Offline(handleResultRouteInfo) // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) None(relativePath string, handlers ...context.Handler) *Route { return api.Handle(MethodNone, relativePath, handlers...) } // Get registers a route for the Get HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Get(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodGet, relativePath, handlers...) } // Post registers a route for the Post HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Post(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodPost, relativePath, handlers...) } // Put registers a route for the Put HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Put(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodPut, relativePath, handlers...) } // Delete registers a route for the Delete HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Delete(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodDelete, relativePath, handlers...) } // Connect registers a route for the Connect HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Connect(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodConnect, relativePath, handlers...) } // Head registers a route for the Head HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Head(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodHead, relativePath, handlers...) } // Options registers a route for the Options HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Options(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodOptions, relativePath, handlers...) } // Patch registers a route for the Patch HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Patch(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodPatch, relativePath, handlers...) } // Trace registers a route for the Trace HTTP Method. // // Returns a *Route and an error which will be filled if route wasn't registered successfully. func (api *APIBuilder) Trace(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodTrace, relativePath, handlers...) } // Any registers a route for ALL of the HTTP methods: // Get // Post // Put // Delete // Head // Patch // Options // Connect // Trace func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (routes []*Route) { for _, m := range AllMethods { r := api.HandleMany(m, relativePath, handlers...) routes = append(routes, r...) } return } func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route { api.Head(reqPath, h) return api.Get(reqPath, h) } // StaticContent registers a GET and HEAD method routes to the requestPath // that are ready to serve raw static bytes, memory cached. // // Returns the GET *Route. func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byte) *Route { modtime := time.Now() h := func(ctx *context.Context) { ctx.ContentType(cType) if _, err := ctx.WriteWithExpiration(content, modtime); err != nil { ctx.StatusCode(http.StatusInternalServerError) // ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error()) } } return api.registerResourceRoute(reqPath, h) } // Favicon serves static favicon // accepts 2 parameters, second is optional // favPath (string), declare the system directory path of the __.ico // requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first, // you can declare your own path if you have more than one favicon (desktop, mobile and so on) // // this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico // (nothing special that you can't handle by yourself). // Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on). // // Returns the GET *Route. func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route { description := favPath favPath = Abs(favPath) f, err := os.Open(favPath) if err != nil { api.logger.Errorf("favicon: file or directory %s not found: %w", favPath, err) return nil } defer f.Close() fi, _ := f.Stat() if fi.IsDir() { // if it's dir the try to get the favicon.ico return api.Favicon(path.Join(favPath, "favicon.ico")) } // copy the bytes here in order to cache and not read the ico on each request. cacheFav := make([]byte, fi.Size()) if _, err = f.Read(cacheFav); err != nil { // Here we are before actually run the server. // So we could panic but we don't, // we just interrupt with a message // to the (user-defined) logger. api.logger.Errorf("favicon: couldn't read the data bytes for %s: %w", favPath, err) return nil } modtime := time.Now() cType := TypeByFilename(favPath) h := func(ctx *context.Context) { ctx.ContentType(cType) if _, err := ctx.WriteWithExpiration(cacheFav, modtime); err != nil { ctx.StatusCode(http.StatusInternalServerError) ctx.Application().Logger().Debugf("while trying to serve the favicon: %s", err.Error()) } } reqPath := "/favicon" + path.Ext(fi.Name()) // we could use the filename, but because standards is /favicon.ico if len(requestPath) > 0 && requestPath[0] != "" { reqPath = requestPath[0] } return api.registerResourceRoute(reqPath, h).Describe(description) } // OnErrorCode registers a handlers chain for this `Party` for a specific HTTP status code. // Read more at: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml // Look `UseError` and `OnAnyErrorCode` too. func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) (routes []*Route) { routes = append(routes, api.handle(statusCode, "", "/", handlers...)) if api.relativePath != "/" { routes = append(routes, api.handle(statusCode, "", "/{tail:path}", handlers...)) } return } // OnAnyErrorCode registers a handlers chain for all error codes // (4xxx and 5xxx, change the `context.ClientErrorCodes` and `context.ServerErrorCodes` variables to modify those) // Look `UseError` and `OnErrorCode` too. func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) (routes []*Route) { for _, statusCode := range context.ClientAndServerErrorCodes { routes = append(routes, api.OnErrorCode(statusCode, handlers...)...) } if n := len(routes); n > 1 { for _, r := range routes[1:n] { r.NoLog = true } routes[0].Title = "ERR" } return } // RegisterView registers and loads a view engine middleware for this group of routes. // It overrides any of the application's root registered view engines. // To register a view engine per handler chain see the `Context.ViewEngine` instead. // Read `Configuration.ViewEngineContextKey` documentation for more. func (api *APIBuilder) RegisterView(viewEngine context.ViewEngine) { if err := viewEngine.Load(); err != nil { api.logger.Error(err) return } handler := func(ctx *context.Context) { ctx.ViewEngine(viewEngine) ctx.Next() } api.Use(handler) api.UseError(handler) // Note (@kataras): It does not return the Party in order // to keep the iris.Application a compatible Party. } // FallbackView registers one or more fallback views for a template or a template layout. // Usage: // // FallbackView(iris.FallbackView("fallback.html")) // FallbackView(iris.FallbackViewLayout("layouts/fallback.html")) // OR // FallbackView(iris.FallbackViewFunc(ctx iris.Context, err iris.ErrViewNotExist) error { // err.Name is the previous template name. // err.IsLayout reports whether the failure came from the layout template. // err.Data is the template data provided to the previous View call. // [...custom logic e.g. ctx.View("fallback", err.Data)] // }) func (api *APIBuilder) FallbackView(provider context.FallbackViewProvider) { handler := func(ctx *context.Context) { ctx.FallbackView(provider) ctx.Next() } api.Use(handler) api.UseError(handler) } // Layout overrides the parent template layout with a more specific layout for this Party. // It returns the current Party. // // The "tmplLayoutFile" should be a relative path to the templates dir. // Usage: // // app := iris.New() // app.RegisterView(iris.$VIEW_ENGINE("./views", ".$extension")) // my := app.Party("/my").Layout("layouts/mylayout.html") // // my.Get("/", func(ctx iris.Context) { // ctx.View("page1.html") // }) // // Examples: https://github.com/kataras/iris/tree/master/_examples/view func (api *APIBuilder) Layout(tmplLayoutFile string) Party { handler := func(ctx *context.Context) { ctx.ViewLayout(tmplLayoutFile) ctx.Next() } api.Use(handler) api.UseError(handler) return api } // https://golang.org/doc/go1.9#callersframes func getCaller() (string, int) { var pcs [32]uintptr n := runtime.Callers(1, pcs[:]) frames := runtime.CallersFrames(pcs[:n]) wd, _ := os.Getwd() var ( frame runtime.Frame more = true ) for { if !more { break } frame, more = frames.Next() file := filepath.ToSlash(frame.File) // fmt.Printf("%s:%d | %s\n", file, frame.Line, frame.Function) if strings.Contains(file, "go/src/runtime/") { continue } if !strings.Contains(file, "_test.go") { if strings.Contains(file, "/kataras/iris") && !strings.Contains(file, "kataras/iris/_examples") && !strings.Contains(file, "kataras/iris/middleware") && !strings.Contains(file, "iris-contrib/examples") { continue } } if relFile, err := filepath.Rel(wd, file); err == nil { if !strings.HasPrefix(relFile, "..") { // Only if it's relative to this path, not parent. file = "./" + filepath.ToSlash(relFile) } } return file, frame.Line } return "???", 0 }