1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-09 13:05:56 +00:00
kataras
2017-06-15 20:02:08 +03:00
parent a10e80842f
commit e0128d204d
58 changed files with 11054 additions and 505 deletions

View File

@@ -320,6 +320,7 @@ func (rb *APIBuilder) Any(registeredPath string, handlers ...context.Handler) er
return err
}
}
return nil
}
@@ -353,21 +354,20 @@ func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) (
// 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,
// so `iris.StripPath` is useless and should not being used here.
// is appended to the party's relative path and stripped here.
//
// Usage:
// app := iris.New()
// ...
// mySubdomainFsServer := app.Party("mysubdomain.")
// h := mySubdomainFsServer.StaticHandler("/static", "./static_files", false, false)
// h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
func (rb *APIBuilder) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
return StripPrefix(rb.relativePath+reqPath,
StaticHandler(systemPath, showList, enableGzip))
func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
// Note: this doesn't need to be here but we'll keep it for consistently
return StaticHandler(systemPath, showList, enableGzip)
}
// StaticServe serves a directory as web resource
@@ -414,14 +414,27 @@ func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) (*Ro
func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) (*Route, error) {
modtime := time.Now()
h := func(ctx context.Context) {
if err := ctx.WriteWithExpiration(http.StatusOK, content, cType, modtime); err != nil {
ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error())
if err := ctx.WriteWithExpiration(content, cType, modtime); err != nil {
ctx.NotFound()
// ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error())
}
}
return rb.registerResourceRoute(reqPath, h)
}
// StaticEmbeddedHandler returns a Handler which can serve
// embedded into executable files.
//
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func (rb *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
// Notes:
// This doesn't need to be APIBuilder's scope,
// but we'll keep it here for consistently.
return StaticEmbeddedHandler(vdir, assetFn, namesFn)
}
// 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"
@@ -430,78 +443,12 @@ func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte
//
// Returns the GET *Route.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/intermediate/serve-embedded-files
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error) {
paramName := "path"
requestPath = joinPath(requestPath, WildcardParam(paramName))
if len(vdir) > 0 {
if vdir[0] == '.' { // first check for .wrong
vdir = vdir[1:]
}
if vdir[0] == '/' || vdir[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
vdir = vdir[1:]
}
}
// collect the names we are care for, because not all Asset used here, we need the vdir's assets.
allNames := namesFn()
var names []string
for _, path := range allNames {
// check if path is the path name we care for
if !strings.HasPrefix(path, vdir) {
continue
}
names = append(names, cleanPath(path))
// path = strings.Replace(path, "\\", "/", -1) // replace system paths with double slashes
// path = strings.Replace(path, "./", "/", -1) // replace ./assets/favicon.ico to /assets/favicon.ico in order to be ready for compare with the reqPath later
// path = path[len(vdir):] // set it as the its 'relative' ( we should re-setted it when assetFn will be used)
// names = append(names, path)
}
if len(names) == 0 {
return nil, errors.New("unable to locate any embedded files located to the (virtual) directory: " + vdir)
}
modtime := time.Now()
h := func(ctx context.Context) {
reqPath := ctx.Params().Get(paramName)
for _, path := range names {
// in order to map "/" as "/index.html"
// as requested here: https://github.com/kataras/iris/issues/633#issuecomment-281691851
if path == "/index.html" {
if reqPath[len(reqPath)-1] == '/' {
reqPath = "/index.html"
}
}
if path != reqPath {
continue
}
cType := TypeByExtension(path)
fullpath := vdir + path
buf, err := assetFn(fullpath)
if err != nil {
continue
}
if err := ctx.WriteWithExpiration(http.StatusOK, buf, cType, modtime); err != nil {
ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution()
}
return
}
// not found or error
ctx.NotFound()
}
fullpath := joinPath(rb.relativePath, requestPath)
requestPath = joinPath(fullpath, WildcardParam("file"))
h := StripPrefix(fullpath, rb.StaticEmbeddedHandler(vdir, assetFn, namesFn))
return rb.registerResourceRoute(requestPath, h)
}
@@ -569,7 +516,8 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
ctx.ResponseWriter().Header().Set(lastModifiedHeaderKey, modtime)
ctx.StatusCode(http.StatusOK)
if _, err := ctx.Write(cacheFav); err != nil {
ctx.Application().Log("error while trying to serve the favicon: %s", err.Error())
// ctx.Application().Log("error while trying to serve the favicon: %s", err.Error())
ctx.StatusCode(http.StatusInternalServerError)
}
}
@@ -596,13 +544,17 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// StaticWeb calls the StaticHandler(requestPath, systemPath, listingDirectories: false, gzip: false ).
// StaticWeb calls the StaticHandler(systemPath, listingDirectories: false, gzip: false ).
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticWeb(reqPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) {
h := rb.StaticHandler(reqPath, systemPath, false, false, exceptRoutes...)
func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) {
paramName := "file"
routePath := joinPath(reqPath, WildcardParam(paramName))
fullpath := joinPath(rb.relativePath, requestPath)
h := StripPrefix(fullpath, rb.StaticHandler(systemPath, false, false, exceptRoutes...))
handler := func(ctx context.Context) {
h(ctx)
// re-check the content type here for any case,
@@ -614,8 +566,8 @@ func (rb *APIBuilder) StaticWeb(reqPath string, systemPath string, exceptRoutes
}
}
}
return rb.registerResourceRoute(routePath, handler)
requestPath = joinPath(fullpath, WildcardParam(paramName))
return rb.registerResourceRoute(requestPath, handler)
}
// OnErrorCode registers an error http status code

View File

@@ -24,6 +24,84 @@ import (
"github.com/kataras/iris/core/errors"
)
// StaticEmbeddedHandler returns a Handler which can serve
// embedded into executable files.
//
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
// Depends on the command the user gave to the go-bindata
// the assset path (names) may be or may not be prepended with a slash.
// What we do: we remove the ./ from the vdir which should be
// the same with the asset path (names).
// we don't pathclean, because that will prepend a slash
// go-bindata should give a correct path format.
// On serve time we check the "paramName" (which is the path after the "requestPath")
// so it has the first directory part missing, we use the "vdir" to complete it
// and match with the asset path (names).
if len(vdir) > 0 {
if vdir[0] == '.' {
vdir = vdir[1:]
}
if vdir[0] == '/' || vdir[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
vdir = vdir[1:]
}
}
// collect the names we are care for,
// because not all Asset used here, we need the vdir's assets.
allNames := namesFn()
var names []string
for _, path := range allNames {
// i.e: path = public/css/main.css
// check if path is the path name we care for
if !strings.HasPrefix(path, vdir) {
continue
}
names = append(names, path)
}
modtime := time.Now()
h := func(ctx context.Context) {
reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir)
// i.e : /css/main.css
for _, path := range names {
// in order to map "/" as "/index.html"
if path == "/index.html" && reqPath == "/" {
reqPath = "/index.html"
}
if path != vdir+reqPath {
continue
}
cType := TypeByFilename(path)
buf, err := assetFn(path) // remove the first slash
if err != nil {
continue
}
if err := ctx.WriteWithExpiration(buf, cType, modtime); err != nil {
ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution()
}
return
}
// not found or error
ctx.NotFound()
}
return h
}
// Prioritize is a middleware which executes a route against this path
// when the request's Path has a prefix of the route's STATIC PART
// is not executing ExecRoute to determinate if it's valid, for performance reasons
@@ -230,6 +308,7 @@ func (w *fsHandler) Build() context.Handler {
// headers[contentEncodingHeader] = nil
// headers[contentLength] = nil
}
// ctx.Application().Log(errMsg)
ctx.StatusCode(prevStatusCode)
return
}
@@ -741,16 +820,13 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
f, err := fs.Open(name)
if err != nil {
msg, code := toHTTPError(err)
return msg, code
return err.Error(), 404
}
defer f.Close()
d, err := f.Stat()
if err != nil {
msg, code := toHTTPError(err)
return msg, code
return err.Error(), 404
}
if redirect {

View File

@@ -76,6 +76,15 @@ func (nodes *Nodes) add(path string, paramNames []string, handlers context.Handl
// na to kanw na exei to node to diko tou wildcard parameter name
// kai sto telos na pernei auto, me vasi to *paramname
// alla edw mesa 9a ginete register vasi tou last /
// set the wildcard param name to the root and its children.
wildcardIdx := strings.IndexByte(path, '*')
wildcardParamName := ""
if wildcardIdx > 0 {
wildcardParamName = path[wildcardIdx+1:]
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
}
loop:
for _, n := range *nodes {
@@ -97,14 +106,14 @@ loop:
children: Nodes{
{
s: n.s[i:],
wildcardParamName: n.wildcardParamName,
wildcardParamName: wildcardParamName,
paramNames: n.paramNames,
children: n.children,
handlers: n.handlers,
},
{
s: path[i:],
wildcardParamName: n.wildcardParamName,
wildcardParamName: wildcardParamName,
paramNames: paramNames,
handlers: handlers,
},
@@ -117,12 +126,12 @@ loop:
if len(path) < len(n.s) {
*n = node{
s: n.s[:len(path)],
wildcardParamName: n.wildcardParamName,
wildcardParamName: wildcardParamName,
paramNames: paramNames,
children: Nodes{
{
s: n.s[len(path):],
wildcardParamName: n.wildcardParamName,
wildcardParamName: wildcardParamName,
paramNames: n.paramNames,
children: n.children,
handlers: n.handlers,
@@ -144,7 +153,7 @@ loop:
return nil
}
if len(n.handlers) > 0 { // n.handlers already setted
return ErrDublicate
return ErrDublicate.Append("for: %s", n.s)
}
n.paramNames = paramNames
n.handlers = handlers
@@ -152,15 +161,6 @@ loop:
return
}
// set the wildcard param name to the root.
wildcardIdx := strings.IndexByte(path, '*')
wildcardParamName := ""
if wildcardIdx > 0 {
wildcardParamName = path[wildcardIdx+1:]
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
}
n := &node{
s: path,
wildcardParamName: wildcardParamName,

View File

@@ -84,20 +84,19 @@ type Party interface {
//
// 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,
// so `iris.StripPath` is useless and should not being used here.
// 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", "./static_files", false, false)
// h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
StaticHandler(requestPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler
StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
@@ -114,6 +113,7 @@ type Party interface {
//
// Returns the GET *Route.
StaticContent(requestPath string, cType string, content []byte) (*Route, error)
// 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"
@@ -152,7 +152,7 @@ type Party interface {
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// StaticWeb calls the StaticHandler(requestPath, systemPath, listingDirectories: false, gzip: false ).
// StaticWeb calls the StaticHandler(systemPath, listingDirectories: false, gzip: false ).
//
// Returns the GET *Route.
StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)

View File

@@ -47,7 +47,6 @@ func NewRoute(method, subdomain, unparsedPath string,
path = cleanPath(path) // maybe unnecessary here but who cares in this moment
defaultName := method + subdomain + path
formattedPath := formatPath(path)
route := &Route{

View File

@@ -102,6 +102,12 @@ func (router *Router) Downgraded() bool {
return router.mainHandler != nil && router.requestHandler == nil
}
// WrapperFunc is used as an expected input parameter signature
// for the WrapRouter. It's a "low-level" signature which is compatible
// with the net/http.
// It's being used to run or no run the router based on a custom logic.
type WrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRouter http.HandlerFunc)
// WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
@@ -111,7 +117,7 @@ func (router *Router) Downgraded() bool {
// That means that the second wrapper will wrap the first, and so on.
//
// Before build.
func (router *Router) WrapRouter(wrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRouter http.HandlerFunc)) {
func (router *Router) WrapRouter(wrapperFunc WrapperFunc) {
router.mu.Lock()
defer router.mu.Unlock()

96
core/router/spa.go Normal file
View File

@@ -0,0 +1,96 @@
package router
import (
"net/http"
"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 {
IndexNames []string
AssetHandler context.Handler
AssetValidators []AssetValidator
}
// NewSPABuilder returns a new Single Page Application builder
// It does what StaticWeb 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{
IndexNames: []string{"index.html"},
AssetHandler: assetHandler,
AssetValidators: []AssetValidator{
func(path string) bool {
return strings.Contains(path, ".")
},
},
}
}
func (s *SPABuilder) isAsset(reqPath string) bool {
for _, v := range s.AssetValidators {
if !v(reqPath) {
return false
}
}
return true
}
// BuildWrapper returns a wrapper which serves the single page application
// with the declared configuration.
//
// It should be passed to the router's `WrapRouter`:
// https://godoc.org/github.com/kataras/iris/core/router#Router.WrapRouter
//
// Example: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server/single-page-application-builder
func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
fileServer := s.AssetHandler
indexNames := s.IndexNames
wrapper := func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
path := r.URL.Path
if !s.isAsset(path) {
// it's not asset, execute the registered route's handlers
router(w, r)
return
}
ctx := cPool.Acquire(w, r)
for _, index := range indexNames {
if strings.HasSuffix(path, index) {
localRedirect(ctx, "./")
cPool.Release(ctx)
// "/" should be manually registered.
// We don't setup an index handler here,
// let full control to the user
// (use middleware, ctx.ServeFile or ctx.View and so on...)
return
}
}
// execute file server for path
fileServer(ctx)
cPool.Release(ctx)
}
return wrapper
}