mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
app. SPA from router wrapper to a simple handler, works faster now. Iris' router respects wildcards with other paths as well (unique) for almost a half year now... so we don't need a whole wrapper for those things anymore, fixes https://github.com/kataras/iris/issues/807
Former-commit-id: 5bd7f24997bb025a01bb92960a1bf255f073a228
This commit is contained in:
parent
2a0c6dade6
commit
9640f59961
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
.vscode
|
||||
_authortools
|
||||
/_examples/**/node_modules
|
|
@ -13,8 +13,7 @@ func newApp() *iris.Application {
|
|||
ctx.Writef("404 not found here")
|
||||
})
|
||||
|
||||
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
|
||||
app.SPA(assetHandler)
|
||||
app.StaticEmbedded("/", "./public", Asset, AssetNames)
|
||||
|
||||
// Note:
|
||||
// if you want a dynamic index page then see the file-server/embedded-single-page-application
|
||||
|
|
|
@ -25,6 +25,8 @@ func newApp() *iris.Application {
|
|||
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
|
||||
// as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
|
||||
// example too
|
||||
// or
|
||||
// app.StaticEmbedded if you don't want to redirect on index.html and simple serve your SPA app (recommended).
|
||||
|
||||
// public/index.html is a dynamic view, it's handlded by root,
|
||||
// and we don't want to be visible as a raw data, so we will
|
||||
|
|
|
@ -2573,7 +2573,7 @@ var errTransactionInterrupted = errors.New("transaction interrupted, recovery fr
|
|||
// it's not covers all paths,
|
||||
// such as databases, this should be managed by the libraries you use to make your database connection,
|
||||
// this transaction scope is only for context's response.
|
||||
// Transactions have their own middleware ecosystem also, look iris.go:UseTransaction.
|
||||
// Transactions have their own middleware ecosystem also.
|
||||
//
|
||||
// See https://github.com/kataras/iris/tree/master/_examples/ for more
|
||||
func (ctx *context) BeginTransaction(pipe func(t *Transaction)) {
|
||||
|
|
|
@ -21,49 +21,6 @@ import (
|
|||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
func calculateAssetValidator(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) AssetValidator {
|
||||
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)
|
||||
}
|
||||
|
||||
return func(reqPath string) bool {
|
||||
reqPath = strings.TrimPrefix(reqPath, "/"+vdir)
|
||||
|
||||
for _, path := range names {
|
||||
// in order to map "/" as "/index.html"
|
||||
if path == "/index.html" && reqPath == "/" {
|
||||
reqPath = "/index.html"
|
||||
}
|
||||
|
||||
if path == vdir+reqPath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StaticEmbeddedHandler returns a Handler which can serve
|
||||
// embedded into executable files.
|
||||
//
|
||||
|
@ -809,7 +766,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
// can't use Redirect() because that would make the path absolute,
|
||||
// which would be a problem running under StripPrefix
|
||||
if strings.HasSuffix(ctx.Request().URL.Path, indexPage) {
|
||||
localRedirect(ctx.ResponseWriter(), ctx.Request(), "./")
|
||||
localRedirect(ctx, "./")
|
||||
return "", http.StatusMovedPermanently
|
||||
}
|
||||
|
||||
|
@ -830,12 +787,12 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
url := ctx.Request().URL.Path
|
||||
if d.IsDir() {
|
||||
if url[len(url)-1] != '/' {
|
||||
localRedirect(ctx.ResponseWriter(), ctx.Request(), path.Base(url)+"/")
|
||||
localRedirect(ctx, path.Base(url)+"/")
|
||||
return "", http.StatusMovedPermanently
|
||||
}
|
||||
} else {
|
||||
if url[len(url)-1] == '/' {
|
||||
localRedirect(ctx.ResponseWriter(), ctx.Request(), "../"+path.Base(url))
|
||||
localRedirect(ctx, "../"+path.Base(url))
|
||||
return "", http.StatusMovedPermanently
|
||||
}
|
||||
}
|
||||
|
@ -845,7 +802,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
if d.IsDir() {
|
||||
url := ctx.Request().URL.Path
|
||||
if url[len(url)-1] != '/' {
|
||||
localRedirect(ctx.ResponseWriter(), ctx.Request(), path.Base(url)+"/")
|
||||
localRedirect(ctx, path.Base(url)+"/")
|
||||
return "", http.StatusMovedPermanently
|
||||
}
|
||||
}
|
||||
|
@ -928,14 +885,13 @@ func toHTTPError(err error) (msg string, httpStatus int) {
|
|||
|
||||
// localRedirect gives a Moved Permanently response.
|
||||
// It does not convert relative paths to absolute paths like Redirect does.
|
||||
func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) {
|
||||
if q := r.URL.RawQuery; q != "" {
|
||||
func localRedirect(ctx context.Context, newPath string) {
|
||||
if q := ctx.Request().URL.RawQuery; q != "" {
|
||||
newPath += "?" + q
|
||||
}
|
||||
w.Header().Set("Location", newPath)
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
// ctx.Header("Location", newPath)
|
||||
// ctx.StatusCode(http.StatusMovedPermanently)
|
||||
|
||||
ctx.Header("Location", newPath)
|
||||
ctx.StatusCode(http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
func containsDotDot(v string) bool {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
|
@ -29,7 +28,7 @@ func (s *SPABuilder) AddIndexName(filename string) *SPABuilder {
|
|||
}
|
||||
|
||||
// NewSPABuilder returns a new Single Page Application builder
|
||||
// It does what StaticWeb expected to do when serving files and routes at the same time
|
||||
// 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...
|
||||
|
@ -47,7 +46,7 @@ func NewSPABuilder(assetHandler context.Handler) *SPABuilder {
|
|||
AssetHandler: assetHandler,
|
||||
AssetValidators: []AssetValidator{
|
||||
func(path string) bool {
|
||||
return true // returns true by-default
|
||||
return true // returns true by-default, if false then it fires 404.
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -62,65 +61,31 @@ func (s *SPABuilder) isAsset(reqPath string) bool {
|
|||
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/file-server/single-page-application-builder
|
||||
func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
|
||||
// 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()
|
||||
|
||||
fileServer := s.AssetHandler
|
||||
|
||||
wrapper := func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
|
||||
path := r.URL.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
|
||||
router(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
for _, index := range s.IndexNames {
|
||||
if strings.HasSuffix(path, index) {
|
||||
localRedirect(w, r, "./")
|
||||
// "/" 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
|
||||
}
|
||||
}
|
||||
|
||||
ctx := cPool.Acquire(w, r)
|
||||
// convert to a recorder in order to not write the status and body directly but wait for a flush (EndRequest).
|
||||
rec := ctx.Recorder() // rec and context.ResponseWriter() is the same thing now.
|
||||
// execute the asset handler.
|
||||
fileServer(ctx)
|
||||
// check if body was written, if not then;
|
||||
// 1. reset the whole response writer, its status code, headers and body
|
||||
// 2. release only the object,
|
||||
// so it doesn't fires the status code's handler to the client
|
||||
// (we are eliminating the multiple response header calls this way)
|
||||
// 3. execute the router itself, if route found then it will serve that, otherwise 404 or 405.
|
||||
//
|
||||
// we could also use the ctx.ResponseWriter().Written() > 0.
|
||||
empty := len(rec.Body()) == 0
|
||||
if empty {
|
||||
rec.Reset()
|
||||
cPool.ReleaseLight(ctx)
|
||||
router(w, r)
|
||||
return
|
||||
}
|
||||
// if body was written from the file server then release the context as usual,
|
||||
// it will send everything to the client and reset the context.
|
||||
cPool.Release(ctx)
|
||||
// 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
|
||||
}
|
||||
|
||||
return wrapper
|
||||
for _, index := range s.IndexNames {
|
||||
if strings.HasSuffix(path, index) {
|
||||
localRedirect(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
|
||||
}
|
||||
}
|
||||
|
||||
s.AssetHandler(ctx)
|
||||
}
|
||||
|
|
13
iris.go
13
iris.go
|
@ -334,19 +334,14 @@ var (
|
|||
|
||||
// SPA accepts an "assetHandler" which can be the result of an
|
||||
// app.StaticHandler or app.StaticEmbeddedHandler.
|
||||
// It wraps the router and checks:
|
||||
// if it;s an asset, if the request contains "." (this behavior can be changed via /core/router.NewSPABuilder),
|
||||
// if the request is index, redirects back to the "/" in order to let the root handler to be executed,
|
||||
// if it's not an asset then it executes the router, so the rest of registered routes can be
|
||||
// executed without conflicts with the file server ("assetHandler").
|
||||
//
|
||||
// Use that instead of `StaticWeb` for root "/" file server.
|
||||
// Use that when you want to navigate from /index.html to / automatically
|
||||
// it's a helper function which just makes some checks based on the `IndexNames` and `AssetValidators`
|
||||
// before the assetHandler call.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application
|
||||
func (app *Application) SPA(assetHandler context.Handler) *router.SPABuilder {
|
||||
s := router.NewSPABuilder(assetHandler)
|
||||
wrapper := s.BuildWrapper(app.ContextPool)
|
||||
app.Router.WrapRouter(wrapper)
|
||||
app.APIBuilder.HandleMany("GET HEAD", "/{f:path}", s.Handler)
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user