1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-09 13:05:56 +00:00

create the new FileServer and HandleDir, deprecate the rest APIBuilder/Party static methods and more

relative: https://github.com/kataras/iris/issues/1283 and removing pongo2 from vendor: https://github.com/kataras/iris/issues/1284

Former-commit-id: 3ec57b349f99faca2b8e36d9f7252db0b6ea080d
This commit is contained in:
Gerasimos (Makis) Maropoulos
2019-06-21 19:43:25 +03:00
parent 7f9e33cabb
commit d0104defa8
72 changed files with 1585 additions and 1826 deletions

View File

@@ -64,7 +64,7 @@ func (r *Reporter) AddErr(err error) bool {
}
if stackErr, ok := err.(StackError); ok {
r.addStack(stackErr.Stack())
r.addStack("", stackErr.Stack())
} else {
r.mu.Lock()
r.wrapper = r.wrapper.AppendErr(err)
@@ -108,7 +108,7 @@ func (r *Reporter) Describe(format string, err error) {
return
}
if stackErr, ok := err.(StackError); ok {
r.addStack(stackErr.Stack())
r.addStack(format, stackErr.Stack())
return
}
@@ -126,12 +126,15 @@ func (r *Reporter) Stack() []Error {
return r.wrapper.Stack
}
func (r *Reporter) addStack(stack []Error) {
func (r *Reporter) addStack(format string, stack []Error) {
for _, e := range stack {
if e.Error() == "" {
continue
}
r.mu.Lock()
if format != "" {
e = New(format).Format(e)
}
r.wrapper = r.wrapper.AppendErr(e)
r.mu.Unlock()
}

View File

@@ -21,15 +21,15 @@ var (
// "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD",
// "PATCH", "OPTIONS", "TRACE".
AllMethods = []string{
"GET",
"POST",
"PUT",
"DELETE",
"CONNECT",
"HEAD",
"PATCH",
"OPTIONS",
"TRACE",
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodDelete,
http.MethodConnect,
http.MethodHead,
http.MethodPatch,
http.MethodOptions,
http.MethodTrace,
}
)
@@ -37,20 +37,71 @@ var (
// all the routes.
type repository struct {
routes []*Route
pos map[string]int
}
func (r *repository) register(route *Route) {
for _, r := range r.routes {
if r.String() == route.String() {
return // do not register any duplicates, the sooner the better.
func (repo *repository) remove(route *Route) bool {
for i, r := range repo.routes {
if r == route {
return repo.removeByIndex(i)
}
}
r.routes = append(r.routes, route)
return false
}
func (r *repository) get(routeName string) *Route {
for _, r := range r.routes {
func (repo *repository) removeByPath(tmplPath string) bool {
if repo.pos != nil {
if idx, ok := repo.pos[tmplPath]; ok {
return repo.removeByIndex(idx)
}
}
return false
}
func (repo *repository) removeByName(routeName string) bool {
for i, r := range repo.routes {
if r.Name == routeName {
return repo.removeByIndex(i)
}
}
return false
}
func (repo *repository) removeByIndex(idx int) bool {
n := len(repo.routes)
if n == 0 {
return false
}
if idx >= n {
return false
}
if n == 1 && idx == 0 {
repo.routes = repo.routes[0:0]
repo.pos = nil
return true
}
r := repo.routes[idx]
if r == nil {
return false
}
repo.routes = append(repo.routes[:idx], repo.routes[idx+1:]...)
if repo.pos != nil {
delete(repo.pos, r.Path)
}
return true
}
func (repo *repository) get(routeName string) *Route {
for _, r := range repo.routes {
if r.Name == routeName {
return r
}
@@ -58,8 +109,37 @@ func (r *repository) get(routeName string) *Route {
return nil
}
func (r *repository) getAll() []*Route {
return r.routes
func (repo *repository) getByPath(tmplPath string) *Route {
if repo.pos != nil {
if idx, ok := repo.pos[tmplPath]; ok {
if len(repo.routes) > idx {
return repo.routes[idx]
}
}
}
return nil
}
func (repo *repository) getAll() []*Route {
return repo.routes
}
func (repo *repository) register(route *Route) {
for i, r := range repo.routes {
if route.Equal(r) {
// replace existing with the latest one.
repo.routes = append(repo.routes[:i], repo.routes[i+1:]...)
continue
}
}
repo.routes = append(repo.routes, route)
if repo.pos == nil {
repo.pos = make(map[string]int)
}
repo.pos[route.tmpl.Src] = len(repo.routes) - 1
}
// APIBuilder the visible API for constructing the router
@@ -181,17 +261,13 @@ func (api *APIBuilder) SetExecutionRules(executionRules ExecutionRules) Party {
return api
}
// Handle registers a route to the server's api.
// 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 {
func (api *APIBuilder) createRoutes(methods []string, relativePath string, handlers ...context.Handler) []*Route {
// if relativePath[0] != '/' {
// return nil, errors.New("path should start with slash and should not be empty")
// }
if method == "" || method == "ALL" || method == "ANY" { // then use like it was .Any
return api.Any(relativePath, handlers...)[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.
@@ -206,7 +282,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
fullpath := api.relativePath + relativePath // for now, keep the last "/" if any, "/xyz/"
if len(handlers) == 0 {
api.reporter.Add("missing handlers for route %s: %s", method, fullpath)
api.reporter.Add("missing handlers for route %s: %s", strings.Join(methods, ", "), fullpath)
return nil
}
@@ -222,7 +298,8 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
mainHandlers := context.Handlers(handlers)
// before join the middleware + handlers + done handlers and apply the execution rules.
possibleMainHandlerName := context.HandlerName(mainHandlers[0])
possibleMainHandlerName := context.MainHandlerName(mainHandlers)
// TODO: for UseGlobal/DoneGlobal that doesn't work.
applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers)
@@ -237,24 +314,36 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
subdomain, path := splitSubdomainAndPath(fullpath)
// if allowMethods are empty, then simply register with the passed, main, method.
methods := append(api.allowMethods, method)
methods = append(api.allowMethods, methods...)
var (
route *Route // the latest one is this route registered, see methods append.
err error // not used outside of loop scope.
)
routes := make([]*Route, len(methods), len(methods))
for _, m := range methods {
route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
for i, m := range methods {
route, err := NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
if err != nil { // template path parser errors:
api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
return nil // fail on first error.
api.reporter.Add("%v -> %s:%s:%s", err, m, subdomain, path)
continue
}
// Add UseGlobal & DoneGlobal Handlers
route.use(api.beginGlobalHandlers)
route.done(api.doneGlobalHandlers)
route.Use(api.beginGlobalHandlers...)
route.Done(api.doneGlobalHandlers...)
routes[i] = route
}
return routes
}
// Handle registers a route to the server's api.
// 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 {
routes := api.createRoutes([]string{method}, relativePath, handlers...)
var route *Route // the last one is returned.
for _, route = range routes {
// global
api.routes.register(route)
}
@@ -301,6 +390,61 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri
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 system or the embedded directory that needs to be served
// third parameter : not required, the directory options, set fields is optional.
//
// for more options look router.FileServer.
//
// api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
//
// Returns the GET *Route.
//
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (getRoute *Route) {
options := getDirOptions(opts...)
h := FileServer(directory, options)
// 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`.
if _, fullpath := splitSubdomainAndPath(joinPath(api.relativePath, requestPath)); fullpath != "/" {
h = StripPrefix(fullpath, h)
}
requestPath = joinPath(requestPath, WildcardFileParam())
routes := api.createRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)
getRoute = routes[0]
// we get all index, including sub directories even if those
// are already managed by the static handler itself.
staticSites := context.GetStaticSites(directory, getRoute.StaticPath(), options.IndexName)
for _, s := range staticSites {
// if the end-dev did manage that index route manually already
// then skip the auto-registration.
//
// Also keep note that end-dev is still able to replace this route and manage by him/herself
// later on by a simple `Handle/Get/` call, refer to `repository#register`.
if api.GetRouteByPath(s.RequestPath) != nil {
continue
}
routes = append(routes, api.createRoutes([]string{http.MethodGet}, s.RequestPath, h)...)
getRoute.StaticSites = append(getRoute.StaticSites, s)
}
for _, route := range routes {
route.MainHandlerName = `HandleDir(directory: "` + directory + `")`
api.routes.register(route)
}
return getRoute
}
// Party groups routes which may have the same prefix and share same handlers,
// returns that new rich subrouter.
//
@@ -432,19 +576,9 @@ func (api *APIBuilder) GetRoute(routeName string) *Route {
return api.routes.get(routeName)
}
// 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 routeReadOnlyWrapper{r}
// 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,
@@ -465,6 +599,31 @@ func (api *APIBuilder) GetRoutesReadOnly() []context.RouteReadOnly {
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 routeReadOnlyWrapper{r}
}
// 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 routeReadOnlyWrapper{r}
}
// 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.
//
@@ -488,7 +647,7 @@ func (api *APIBuilder) Use(handlers ...context.Handler) {
// 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.use(handlers) // prepend the handlers to the existing routes
r.Use(handlers...) // prepend the handlers to the existing routes
}
// set as begin handlers for the next routes as well.
api.beginGlobalHandlers = append(api.beginGlobalHandlers, handlers...)
@@ -514,7 +673,7 @@ func (api *APIBuilder) Done(handlers ...context.Handler) {
// 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
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...)
@@ -616,51 +775,9 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
return
}
func (api *APIBuilder) registerResourceRoute(target, reqPath string, h context.Handler) *Route {
head := api.Head(reqPath, h)
head.StaticTarget = target
get := api.Get(reqPath, h)
get.StaticTarget = target
return get
}
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
// Note:
// The only difference from package-level `StaticHandler`
// is that this `StaticHandler`` receives a request path which
// is appended to the party's relative path and stripped here.
//
// Usage:
// app := iris.New()
// ...
// mySubdomainFsServer := app.Party("mysubdomain.")
// h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
func (api *APIBuilder) StaticHandler(systemPath string, showList bool, gzip bool) context.Handler {
// Note: this doesn't need to be here but we'll keep it for consistently
return StaticHandler(systemPath, showList, gzip)
}
// StaticServe serves a directory as web resource.
// Same as `StaticWeb`.
// DEPRECATED; use `StaticWeb` or `StaticHandler` (for more options) instead.
func (api *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Route {
var reqPath string
if len(requestPath) == 0 {
reqPath = strings.Replace(systemPath, string(os.PathSeparator), "/", -1) // replaces any \ to /
reqPath = strings.Replace(reqPath, "//", "/", -1) // for any case, replaces // to /
reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
} else {
reqPath = requestPath[0]
}
return api.StaticWeb(reqPath, systemPath)
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
@@ -677,58 +794,7 @@ func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byt
}
}
return api.registerResourceRoute(StaticContentTarget, reqPath, h)
}
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets" (no trailing slash),
// Third parameter is the Asset function
// Forth parameter is the AssetNames function.
//
// Returns the GET *Route.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
func (api *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
return api.staticEmbedded(requestPath, vdir, assetFn, namesFn, false)
}
// StaticEmbeddedGzip registers a route which can serve embedded gziped files
// that are embedded using the https://github.com/kataras/bindata tool and only.
// It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
// it sends gzip response only, so the client must be aware that is expecting a gzip body
// (browsers and most modern browsers do that, so you can use it without fair).
//
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets" (no trailing slash),
// Third parameter is the GzipAsset function
// Forth parameter is the GzipAssetNames function.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route {
return api.staticEmbedded(requestPath, vdir, gzipAssetFn, gzipNamesFn, true)
}
// look fs.go#StaticEmbeddedHandler
func (api *APIBuilder) staticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string, assetsGziped bool) *Route {
fullpath := joinPath(api.relativePath, requestPath)
// if subdomain,
// here 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(fullpath)
paramName := "file"
requestPath = joinPath(requestPath, WildcardParam(paramName))
h := StaticEmbeddedHandler(vdir, assetFn, namesFn, assetsGziped)
if fullpath != "/" {
h = StripPrefix(fullpath, h)
}
// it handles the subdomain(root Party) of this party as well, if any.
return api.registerResourceRoute(vdir, requestPath, h)
return api.registerResourceRoute(reqPath, h)
}
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
@@ -787,48 +853,7 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
reqPath = requestPath[0]
}
return api.registerResourceRoute(favPath, reqPath, h)
}
// StaticWeb returns a handler that serves HTTP requests
// with the contents of the file system rooted at directory.
//
// first parameter: the route path
// second parameter: the system directory
//
// for more options look router.StaticHandler.
//
// api.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "/index.html", if `index.html` should be served then register a
// new route for it, i.e
// `app.Get("/static", func(ctx iris.Context){ ctx.ServeFile("./static/index.html", false) })`.
//
// StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`.
//
// Returns the GET *Route.
func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
fullpath := joinPath(api.relativePath, requestPath)
// if subdomain,
// here 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(fullpath)
paramName := "file"
requestPath = joinPath(requestPath, WildcardParam(paramName))
h := NewStaticHandlerBuilder(systemPath).Listing(false).Build()
if fullpath != "/" {
h = StripPrefix(fullpath, h)
}
// it handles the subdomain(root Party) of this party as well, if any.
return api.registerResourceRoute(systemPath, requestPath, h)
return api.registerResourceRoute(reqPath, h)
}
// OnErrorCode registers an error http status code

89
core/router/deprecated.go Normal file
View File

@@ -0,0 +1,89 @@
package router
import (
"runtime"
"strings"
"github.com/kataras/iris/context"
)
/*
Relative to deprecation:
- party.go#L138-154
- deprecated_example_test.go
*/
// 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])
for {
frame, more := frames.Next()
if (!strings.Contains(frame.File, "github.com/kataras/iris") ||
strings.Contains(frame.File, "github.com/kataras/iris/_examples") ||
strings.Contains(frame.File, "github.com/iris-contrib/examples") ||
(strings.Contains(frame.File, "github.com/kataras/iris/core/router") && !strings.Contains(frame.File, "deprecated.go"))) &&
!strings.HasSuffix(frame.Func.Name(), ".getCaller") && !strings.Contains(frame.File, "/go/src/testing") {
return frame.File, frame.Line
}
if !more {
break
}
}
return "?", 0
}
// StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead.
func (api *APIBuilder) StaticWeb(requestPath string, directory string) *Route {
file, line := getCaller()
api.reporter.Add(`StaticWeb is DEPRECATED and it will be removed eventually.
Source: %s:%d
Use .HandleDir("%s", "%s") instead.`, file, line, requestPath, directory)
return nil
}
// StaticHandler is DEPRECATED.
// Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead.
//
// Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic
func (api *APIBuilder) StaticHandler(directory string, showList bool, gzip bool) context.Handler {
file, line := getCaller()
api.reporter.Add(`StaticHandler is DEPRECATED and it will be removed eventually.
Source: %s:%d
Use iris.FileServer("%s", iris.DirOptions{ShowList: %v, Gzip: %v}) instead.`, file, line, directory, showList, gzip)
return FileServer(directory, DirOptions{ShowList: showList, Gzip: gzip})
}
// StaticEmbedded is DEPRECATED.
// Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
func (api *APIBuilder) StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
file, line := getCaller()
api.reporter.Add(`StaticEmbedded is DEPRECATED and it will be removed eventually.
It is also miss the AssetInfo bindata function, which is required now.
Source: %s:%d
Use .HandleDir("%s", "%s", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory)
return nil
}
// StaticEmbeddedGzip is DEPRECATED.
// Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
file, line := getCaller()
api.reporter.Add(`StaticEmbeddedGzip is DEPRECATED and it will be removed eventually.
It is also miss the AssetInfo bindata function, which is required now.
Source: %s:%d
Use .HandleDir("%s", "%s", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory)
return nil
}

View File

@@ -0,0 +1,67 @@
package router
import (
"fmt"
)
func ExampleParty_StaticWeb() {
api := NewAPIBuilder()
api.StaticWeb("/static", "./assets")
err := api.GetReport()
if err == nil {
panic("expected report for deprecation")
}
fmt.Print(err)
// Output: StaticWeb is DEPRECATED and it will be removed eventually.
// Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:9
// Use .HandleDir("/static", "./assets") instead.
}
func ExampleParty_StaticHandler() {
api := NewAPIBuilder()
api.StaticHandler("./assets", false, true)
err := api.GetReport()
if err == nil {
panic("expected report for deprecation")
}
fmt.Print(err)
// Output: StaticHandler is DEPRECATED and it will be removed eventually.
// Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:24
// Use iris.FileServer("./assets", iris.DirOptions{ShowList: false, Gzip: true}) instead.
}
func ExampleParty_StaticEmbedded() {
api := NewAPIBuilder()
api.StaticEmbedded("/static", "./assets", nil, nil)
err := api.GetReport()
if err == nil {
panic("expected report for deprecation")
}
fmt.Print(err)
// Output: StaticEmbedded is DEPRECATED and it will be removed eventually.
// It is also miss the AssetInfo bindata function, which is required now.
// Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:39
// Use .HandleDir("/static", "./assets", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
}
func ExampleParty_StaticEmbeddedGzip() {
api := NewAPIBuilder()
api.StaticEmbeddedGzip("/static", "./assets", nil, nil)
err := api.GetReport()
if err == nil {
panic("expected report for deprecation")
}
fmt.Print(err)
// Output: StaticEmbeddedGzip is DEPRECATED and it will be removed eventually.
// It is also miss the AssetInfo bindata function, which is required now.
// Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:55
// Use .HandleDir("/static", "./assets", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
}

File diff suppressed because it is too large Load Diff

View File

@@ -76,11 +76,14 @@ func NewDefaultHandler() RequestHandler {
type RoutesProvider interface { // api builder
GetRoutes() []*Route
GetRoute(routeName string) *Route
// GetStaticSites() []*StaticSite
// Macros() *macro.Macros
}
func (h *routerHandler) Build(provider RoutesProvider) error {
registeredRoutes := provider.GetRoutes()
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
rp := errors.NewReporter()
registeredRoutes := provider.GetRoutes()
// sort, subdomains go first.
sort.Slice(registeredRoutes, func(i, j int) bool {
@@ -111,11 +114,8 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
// the rest are handled inside the node
return lsub1 > lsub2
})
rp := errors.NewReporter()
for _, r := range registeredRoutes {
// build the r.Handlers based on begin and done handlers, if any.
r.BuildHandlers()
@@ -133,6 +133,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
rp.Add("%v -> %s", err, r.String())
continue
}
golog.Debugf(r.Trace())
}

View File

@@ -120,6 +120,39 @@ type Party interface {
// in order to handle more than one paths for the same controller instance.
HandleMany(method string, relativePath string, handlers ...context.Handler) []*Route
// 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 system or the embedded directory that needs to be served
// third parameter : not required, the directory options, set fields is optional.
//
// for more options look router.FileServer.
//
// api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
//
// Returns the GET *Route.
//
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
HandleDir(requestPath, directory string, opts ...DirOptions) *Route
// StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead.
StaticWeb(requestPath string, directory string) *Route
// StaticHandler is DEPRECATED.
// Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead.
//
// Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic
StaticHandler(directory string, showList bool, gzip bool) context.Handler
// StaticEmbedded is DEPRECATED.
// Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
// StaticEmbeddedGzip is DEPRECATED.
// Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
// None registers an "offline" route
// see context.ExecRoute(routeName) and
// party.Routes().Online(handleResultregistry.*Route, "GET") and
@@ -167,61 +200,11 @@ type Party interface {
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
Any(registeredPath string, handlers ...context.Handler) []*Route
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
// Note:
// The only difference from package-level `StaticHandler`
// is that this `StaticHandler` receives a request path which
// is appended to the party's relative path and stripped here.
//
// Usage:
// app := iris.New()
// ...
// mySubdomainFsServer := app.Party("mysubdomain.")
// h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
StaticHandler(systemPath string, showList bool, gzip bool) context.Handler
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath,
// the same path will be used to register the GET and HEAD method routes.
// If second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache).
//
// Returns the GET *Route.
StaticServe(systemPath string, requestPath ...string) *Route
// 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.
StaticContent(requestPath string, cType string, content []byte) *Route
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets"
// Third parameter is the Asset function
// Forth parameter is the AssetNames function.
//
// Returns the GET *Route.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
// StaticEmbeddedGzip registers a route which can serve embedded gziped files
// that are embedded using the https://github.com/kataras/bindata tool and only.
// It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
// it sends gzip response only, so the client must be aware that is expecting a gzip body
// (browsers and most modern browsers do that, so you can use it without fair).
//
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
@@ -234,24 +217,6 @@ type Party interface {
//
// Returns the GET *Route.
Favicon(favPath string, requestPath ...string) *Route
// StaticWeb returns a handler that serves HTTP requests
// with the contents of the file system rooted at directory.
//
// first parameter: the route path
// second parameter: the system directory
//
// for more options look router.StaticHandler.
//
// router.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`.
//
// Returns the GET *Route.
StaticWeb(requestPath string, systemPath string) *Route
// Layout overrides the parent template layout with a more specific layout for this Party.
// It returns the current Party.

View File

@@ -25,6 +25,15 @@ func WildcardParam(name string) string {
return prefix(name, WildcardParamStart)
}
// WildcardFileParam wraps a named parameter "file" with the trailing "path" macro parameter type.
// At build state this "file" parameter is prefixed with the request handler's `WildcardParamStart`.
// Created mostly for routes that serve static files to be visibly collected by
// the `Application#GetRouteReadOnly` via the `Route.Tmpl().Src` instead of
// the underline request handler's representation (`Route.Path()`).
func WildcardFileParam() string {
return "{file:path}"
}
func convertMacroTmplToNodePath(tmpl macro.Template) string {
routePath := tmpl.Src
if len(routePath) > 1 && routePath[len(routePath)-1] == '/' {

View File

@@ -29,25 +29,19 @@ type Route struct {
// Execution happens after Begin and main Handler(s), can be empty.
doneHandlers context.Handlers
Path string `json:"path"` // "/api/user/{id:uint64}"
Path string `json:"path"` // the underline router's representation, i.e "/api/user/:id"
// FormattedPath all dynamic named parameters (if any) replaced with %v,
// used by Application to validate param values of a Route based on its name.
FormattedPath string `json:"formattedPath"`
// StaticTarget if not empty, refers to the system (or virtual if embedded) directory
// that this route is serving static files/resources from
// or to a single static filename if this route created via `APIBuilder#Favicon`
// or to a `StaticContentTarget` type if this rotue created by `APIBuilder#StaticContent`.
//
// If a route is serving static files via `APIBuilder`
// there are two routes with the same dir/filename set to this field,
// one for "HEAD" and the other for the "GET" http method.
StaticTarget string
// StaticSites if not empty, refers to the system (or virtual if embedded) directory
// and sub directories that this "GET" route was registered to serve files and folders
// that contain index.html (a site). The index handler may registered by other
// route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites []context.StaticSite `json:"staticSites"`
}
// StaticContentTarget used whenever a `Route#StaticTarget` refers to a raw []byte static content instead of a directory or a file.
const StaticContentTarget = "content"
// NewRoute returns a new route based on its method,
// subdomain, the path (unparsed or original),
// handlers and the macro container which all routes should share.
@@ -69,7 +63,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
}
path = cleanPath(path) // maybe unnecessary here but who cares in this moment
path = cleanPath(path) // maybe unnecessary here.
defaultName := method + subdomain + tmpl.Src
formattedPath := formatPath(path)
@@ -87,26 +81,26 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
return route, nil
}
// use adds explicit begin handlers(middleware) to this route,
// It's being called internally, it's useless for outsiders
// because `Handlers` field is exported.
// The callers of this function are: `APIBuilder#UseGlobal` and `APIBuilder#Done`.
// Use adds explicit begin handlers to this route.
// Alternatively the end-dev can prepend to the `Handlers` field.
// Should be used before the `BuildHandlers` which is
// called by the framework itself on `Application#Run` (build state).
//
// BuildHandlers should be called to build the route's `Handlers`.
func (r *Route) use(handlers context.Handlers) {
// Used internally at `APIBuilder#UseGlobal` -> `beginGlobalHandlers` -> `APIBuilder#Handle`.
func (r *Route) Use(handlers ...context.Handler) {
if len(handlers) == 0 {
return
}
r.beginHandlers = append(r.beginHandlers, handlers...)
}
// use adds explicit done handlers to this route.
// It's being called internally, it's useless for outsiders
// because `Handlers` field is exported.
// The callers of this function are: `APIBuilder#UseGlobal` and `APIBuilder#Done`.
// Done adds explicit finish handlers to this route.
// Alternatively the end-dev can append to the `Handlers` field.
// Should be used before the `BuildHandlers` which is
// called by the framework itself on `Application#Run` (build state).
//
// BuildHandlers should be called to build the route's `Handlers`.
func (r *Route) done(handlers context.Handlers) {
// Used internally at `APIBuilder#DoneGlobal` -> `doneGlobalHandlers` -> `APIBuilder#Handle`.
func (r *Route) Done(handlers ...context.Handler) {
if len(handlers) == 0 {
return
}
@@ -161,6 +155,13 @@ func (r Route) String() string {
r.Method, r.Subdomain, r.Tmpl().Src)
}
// Equal compares the method, subdomaind and the
// underline representation of the route's path,
// instead of the `String` function which returns the front representation.
func (r *Route) Equal(other *Route) bool {
return r.Method == other.Method && r.Subdomain == other.Subdomain && r.Path == other.Path
}
// Tmpl returns the path template,
// it contains the parsed template
// for the route's path.
@@ -235,12 +236,12 @@ func (r Route) StaticPath() string {
if bidx == -1 || len(src) <= bidx {
return src // no dynamic part found
}
if bidx == 0 { // found at first index,
// but never happens because of the prepended slash
if bidx <= 1 { // found at first{...} or second index (/{...}),
// although first index should never happen because of the prepended slash.
return "/"
}
return src[:bidx]
return src[:bidx-1] // (/static/{...} -> /static)
}
// ResolvePath returns the formatted path's %v replaced with the args.
@@ -272,10 +273,15 @@ func (r Route) Trace() string {
}
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
mainHandlerName := r.MainHandlerName
if !strings.HasSuffix(mainHandlerName, ")") {
mainHandlerName += "()"
}
if l := r.RegisteredHandlersLen(); l > 1 {
printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1)
printfmt += fmt.Sprintf("-> %s and %d more", mainHandlerName, l-1)
} else {
printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName)
printfmt += fmt.Sprintf("-> %s", mainHandlerName)
}
// printfmt := fmt.Sprintf("%s: %s >> %s", r.Method, r.Subdomain+r.Tmpl().Src, r.MainHandlerName)
@@ -316,3 +322,7 @@ func (rd routeReadOnlyWrapper) Tmpl() macro.Template {
func (rd routeReadOnlyWrapper) MainHandlerName() string {
return rd.Route.MainHandlerName
}
func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite {
return rd.Route.StaticSites
}

56
core/router/route_test.go Normal file
View File

@@ -0,0 +1,56 @@
// white-box testing
package router
import (
"github.com/kataras/iris/macro"
"testing"
)
func TestRouteStaticPath(t *testing.T) {
var tests = []struct {
tmpl string
static string
}{
{
tmpl: "/files/{file:path}",
static: "/files",
},
{
tmpl: "/path",
static: "/path",
},
{
tmpl: "/path/segment",
static: "/path/segment",
},
{
tmpl: "/path/segment/{n:int}",
static: "/path/segment",
},
{
tmpl: "/path/{n:uint64}/{n:int}",
static: "/path",
},
{
tmpl: "/path/{n:uint64}/static",
static: "/path",
},
{
tmpl: "/{name}",
static: "/",
},
{
tmpl: "/",
static: "/",
},
}
for i, tt := range tests {
route := Route{tmpl: macro.Template{Src: tt.tmpl}}
if expected, got := tt.static, route.StaticPath(); expected != got {
t.Fatalf("[%d:%s] expected static path to be: '%s' but got: '%s'", i, tt.tmpl, expected, got)
}
}
}

View File

@@ -1,167 +0,0 @@
package router
import (
"strings"
"github.com/kataras/iris/context"
)
// AssetValidator returns true if "filename"
// is asset, i.e: strings.Contains(filename, ".").
type AssetValidator func(filename string) bool
// SPABuilder helps building a single page application server
// which serves both routes and files from the root path.
type SPABuilder struct {
// Root defaults to "/", it's the root path that explicitly set-ed,
// this can be changed if more than SPAs are used on the same
// iris router instance.
Root string
// emptyRoot can be changed with `ChangeRoot` only,
// is, statically, true if root is empty
// and if root is empty then let 404 fire from server-side anyways if
// the passed `AssetHandler` returns 404 for a specific request path.
// Defaults to false.
emptyRoot bool
IndexNames []string
AssetHandler context.Handler
AssetValidators []AssetValidator
}
// AddIndexName will add an index name.
// If path == $filename then it redirects to Root, which defaults to "/".
//
// It can be called BEFORE the server start.
func (s *SPABuilder) AddIndexName(filename string) *SPABuilder {
s.IndexNames = append(s.IndexNames, filename)
return s
}
// ChangeRoot modifies the `Root` request path that is
// explicitly set-ed if the `AssetHandler` gave a Not Found (404)
// previously, if request's path is the passed "path"
// then it explicitly sets that and it retries executing the `AssetHandler`.
//
// Empty Root means that let 404 fire from server-side anyways.
//
// Change it ONLY if you use more than one typical SPAs on the same Iris Application instance.
func (s *SPABuilder) ChangeRoot(path string) *SPABuilder {
s.Root = path
s.emptyRoot = path == ""
return s
}
// NewSPABuilder returns a new Single Page Application builder
// It does what StaticWeb or StaticEmbedded expected to do when serving files and routes at the same time
// from the root "/" path.
//
// Accepts a static asset handler, which can be an app.StaticHandler, app.StaticEmbeddedHandler...
func NewSPABuilder(assetHandler context.Handler) *SPABuilder {
if assetHandler == nil {
assetHandler = func(ctx context.Context) {
ctx.Writef("empty asset handler")
}
}
return &SPABuilder{
Root: "/",
IndexNames: nil,
// "IndexNames" are empty by-default,
// if the user wants to redirect to "/" from "/index.html" she/he can chage that to []string{"index.html"} manually
// or use the `StaticHandler` as "AssetHandler" which does that already.
AssetHandler: assetHandler,
AssetValidators: []AssetValidator{
func(path string) bool {
return true // returns true by-default, if false then it fires 404.
},
},
}
}
func (s *SPABuilder) isAsset(reqPath string) bool {
for _, v := range s.AssetValidators {
if !v(reqPath) {
return false
}
}
return true
}
// Handler serves the asset handler but in addition, it makes some checks before that,
// based on the `AssetValidators` and `IndexNames`.
func (s *SPABuilder) Handler(ctx context.Context) {
path := ctx.Path()
// make a validator call, by-default all paths are valid and this codeblock doesn't mean anything
// but for cases that users wants to bypass an asset she/he can do that by modifiying the `APIBuilder#AssetValidators` field.
//
// It's here for backwards compatibility as well, see #803.
if !s.isAsset(path) {
// it's not asset, execute the registered route's handlers
ctx.NotFound()
return
}
for _, index := range s.IndexNames {
if strings.HasSuffix(path, index) {
if s.emptyRoot {
ctx.NotFound()
return
}
localRedirect(ctx, "."+s.Root)
// s.Root should be manually registered to a route
// (not always, only if custom handler used).
// We don't setup an index handler here,
// let full control to the developer via "AssetHandler"
// (use of middleware, manually call of the ctx.ServeFile or ctx.View etc.)
return
}
}
s.AssetHandler(ctx)
if context.StatusCodeNotSuccessful(ctx.GetStatusCode()) && !s.emptyRoot && path != s.Root {
// If file was not something like a javascript file, or a css or anything that
// the passed `AssetHandler` scan-ed then re-execute the `AssetHandler`
// using the `Root` as the request path (virtually).
//
// If emptyRoot is true then
// fire the response as it's, "AssetHandler" is fully responsible for it,
// client-side's router for invalid paths will not work here else read below.
//
// Author's notes:
// the server doesn't need to know all client routes,
// client-side router is responsible for any kind of invalid paths,
// so explicit set to root path.
//
// The most simple solution was to use a
// func(ctx iris.Context) { ctx.ServeFile("$PATH/index.html") } as the "AssetHandler"
// but many developers use the `StaticHandler` (as shown in the examples)
// but it was not working as expected because it (correctly) fires
// a 404 not found if a file based on the request path didn't found.
//
// We can't just do it before the "AssetHandler"'s execution
// for two main reasons:
// 1. if it's a file serve handler, like `StaticHandler` then it will never serve
// the corresponding files!
// 2. it may manually handle those things,
// don't forget that "AssetHandler" can be
// ANY iris handler, so we can't be sure what the developer may want to do there.
//
// "AssetHandler" as the "StaticHandler" a retry doesn't hurt,
// it will give us a 404 if the file didn't found very fast WITHOUT moving to the
// rest of its validation and serving implementation.
//
// Another idea would be to modify the "AssetHandler" on every `ChangeRoot`
// call, which may give us some performance (ns) benefits
// but this could be bad if root is set-ed before the "AssetHandler",
// so keep it as it's.
rootURL, err := ctx.Request().URL.Parse(s.Root)
if err == nil {
ctx.Request().URL = rootURL
s.AssetHandler(ctx)
}
}
}