mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
Update to version 8.5.8 | Read HISTORY.md
Former-commit-id: 82128ce7a2896a9a8bafd7a5268b0b42057fc21a
This commit is contained in:
parent
5a8b17f0e8
commit
2a0c6dade6
|
@ -17,6 +17,13 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
||||||
|
|
||||||
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you.
|
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you.
|
||||||
|
|
||||||
|
# Th, 09 November 2017 | v8.5.8
|
||||||
|
|
||||||
|
- **IMPROVE** the `Single Page Application builder`[*](https://github.com/kataras/blob/master/core/router/router_spa_wrapper.go) and fix https://github.com/kataras/iris/issues/803 reported by @ionutvilie, a new example is located [here](_examples/file-server/embedded-single-page-application-with-other-routes).
|
||||||
|
* `app.SPA` now returns the `*SPABuilder` and you can change some of its fields manually, i.e;
|
||||||
|
* `IndexNames` defaulted to empty but can be seted to `[]{"index.html"}` or call the **new** `AddIndexName` from the `app.SPA` manually if dynamic view on root has registered, see [here](_examples/file-server/single-page-application/embedded-single-page-application) how.
|
||||||
|
* `AssetValidator` exists as it was and it's checked before the spa file server but it allows everything by-default because the real validation happens internally; if body was written or not, if not then reset the context's response writer and execute the router, as previously, otherwise release the context and send the response to the client.
|
||||||
|
|
||||||
# Tu, 07 November 2017 | v8.5.7
|
# Tu, 07 November 2017 | v8.5.7
|
||||||
|
|
||||||
Nothing crazy here, just one addition which may help some people;
|
Nothing crazy here, just one addition which may help some people;
|
||||||
|
|
|
@ -44,7 +44,7 @@ If you're coming from [nodejs](https://nodejs.org) world, Iris is the [expressjs
|
||||||
## Table Of Content
|
## Table Of Content
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-07-november-2017--v857)
|
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-09-november-2017--v858)
|
||||||
* [Getting started](#getting-started)
|
* [Getting started](#getting-started)
|
||||||
* [Learn](_examples/)
|
* [Learn](_examples/)
|
||||||
* [MVC (Model View Controller)](_examples/#mvc) **NEW**
|
* [MVC (Model View Controller)](_examples/#mvc) **NEW**
|
||||||
|
@ -145,11 +145,7 @@ func main() {
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
![overview screen](https://github.com/kataras/build-a-better-web-together/raw/master/overview_screen_1.png)
|
||||||
$ go run main.go
|
|
||||||
> Now listening on: http://localhost:8080
|
|
||||||
> Application started. Press CTRL+C to shut down.
|
|
||||||
```
|
|
||||||
|
|
||||||
> Wanna re-start your app automatically when source code changes happens? Install the [rizla](https://github.com/kataras/rizla) tool and run `rizla main.go` instead of `go run main.go`.
|
> Wanna re-start your app automatically when source code changes happens? Install the [rizla](https://github.com/kataras/rizla) tool and run `rizla main.go` instead of `go run main.go`.
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
8.5.7:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-07-november-2017--v857
|
8.5.8:https://github.com/kataras/iris/blob/master/HISTORY.md#th-09-november-2017--v858
|
|
@ -17,6 +17,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
|
||||||
- [POC: Isomorphic react/hot reloadable/redux/css-modules starter kit](https://github.com/kataras/iris-starter-kit)
|
- [POC: Isomorphic react/hot reloadable/redux/css-modules starter kit](https://github.com/kataras/iris-starter-kit)
|
||||||
- [Tutorial: DropzoneJS Uploader](tutorial/dropzonejs)
|
- [Tutorial: DropzoneJS Uploader](tutorial/dropzonejs)
|
||||||
- [Tutorial: Caddy](tutorial/caddy)
|
- [Tutorial: Caddy](tutorial/caddy)
|
||||||
|
- [Tutorial:Iris Go Framework + MongoDB](https://medium.com/go-language/iris-go-framework-mongodb-552e349eab9c)
|
||||||
|
|
||||||
### Structuring
|
### Structuring
|
||||||
|
|
||||||
|
@ -276,6 +277,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files to
|
||||||
- Single Page Applications
|
- Single Page Applications
|
||||||
* [single Page Application](file-server/single-page-application/basic/main.go)
|
* [single Page Application](file-server/single-page-application/basic/main.go)
|
||||||
* [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go)
|
* [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go)
|
||||||
|
* [embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
|
||||||
|
|
||||||
### How to Read from `context.Request() *http.Request`
|
### How to Read from `context.Request() *http.Request`
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
// Code generated by go-bindata.
|
||||||
|
// sources:
|
||||||
|
// public/app.js
|
||||||
|
// public/css/main.css
|
||||||
|
// public/index.html
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||||
|
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, gz)
|
||||||
|
clErr := gz.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if clErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type asset struct {
|
||||||
|
bytes []byte
|
||||||
|
info os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindataFileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode os.FileMode
|
||||||
|
modTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi bindataFileInfo) Name() string {
|
||||||
|
return fi.name
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Size() int64 {
|
||||||
|
return fi.size
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||||
|
return fi.mode
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) ModTime() time.Time {
|
||||||
|
return fi.modTime
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _publicAppJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2a\xcf\xcc\x4b\xc9\x2f\xd7\x4b\xcc\x49\x2d\x2a\xd1\x50\x4a\x2c\x28\xd0\xcb\x2a\x56\xc8\xc9\x4f\x4c\x49\x4d\x51\x48\x2b\xca\xcf\x55\x88\x51\xd2\x57\xd2\xb4\x06\x04\x00\x00\xff\xff\xa9\x06\xf7\xa3\x27\x00\x00\x00")
|
||||||
|
|
||||||
|
func publicAppJsBytes() ([]byte, error) {
|
||||||
|
return bindataRead(
|
||||||
|
_publicAppJs,
|
||||||
|
"public/app.js",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicAppJs() (*asset, error) {
|
||||||
|
bytes, err := publicAppJsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "public/app.js", size: 39, mode: os.FileMode(511), modTime: time.Unix(1499700236, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _publicCssMainCss = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\xe5\x52\x50\x50\x50\x48\x4a\x4c\xce\x4e\x2f\xca\x2f\xcd\x4b\xd1\x4d\xce\xcf\xc9\x2f\xb2\x52\x48\xca\x49\x4c\xce\xb6\xe6\xe5\xaa\xe5\xe5\x02\x04\x00\x00\xff\xff\x03\x25\x9c\x89\x29\x00\x00\x00")
|
||||||
|
|
||||||
|
func publicCssMainCssBytes() ([]byte, error) {
|
||||||
|
return bindataRead(
|
||||||
|
_publicCssMainCss,
|
||||||
|
"public/css/main.css",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicCssMainCss() (*asset, error) {
|
||||||
|
bytes, err := publicCssMainCssBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "public/css/main.css", size: 41, mode: os.FileMode(511), modTime: time.Unix(1499700236, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _publicIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x41\x0e\xc2\x20\x10\x45\xf7\x24\xdc\xe1\xa7\x07\x28\xe9\x7e\x64\xed\x35\x10\x46\xc1\x50\x21\x30\x0b\xbd\xbd\x29\xc5\xc4\xf5\x7f\x6f\xde\x50\x94\x3d\x5b\xad\xb4\xa2\xc8\x2e\x58\xad\x00\x80\x24\x49\x66\x7b\xe5\x9c\x0b\xee\xad\xec\xe8\xe2\x24\x79\x54\xf7\x60\x32\xe7\xaa\x15\x99\xe9\x68\x45\xb7\x12\x3e\x3f\x3b\x6e\x16\x7f\x6e\x7a\x05\x7e\xaf\x47\x08\x64\xe2\x36\xf8\x49\x76\xdf\x52\x15\xf4\xe6\x2f\x8b\x71\xb5\xae\xcf\xbe\x58\x80\xcc\x39\x8c\xc6\xbc\x3c\x72\xc7\xb3\xdf\x00\x00\x00\xff\xff\x7e\xad\xd1\x97\xb3\x00\x00\x00")
|
||||||
|
|
||||||
|
func publicIndexHtmlBytes() ([]byte, error) {
|
||||||
|
return bindataRead(
|
||||||
|
_publicIndexHtml,
|
||||||
|
"public/index.html",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicIndexHtml() (*asset, error) {
|
||||||
|
bytes, err := publicIndexHtmlBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "public/index.html", size: 179, mode: os.FileMode(511), modTime: time.Unix(1510219864, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset loads and returns the asset for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func Asset(name string) ([]byte, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.bytes, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustAsset is like Asset but panics when Asset would return an error.
|
||||||
|
// It simplifies safe initialization of global variables.
|
||||||
|
func MustAsset(name string) []byte {
|
||||||
|
a, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
panic("asset: Asset(" + name + "): " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetInfo loads and returns the asset info for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func AssetInfo(name string) (os.FileInfo, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.info, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetNames returns the names of the assets.
|
||||||
|
func AssetNames() []string {
|
||||||
|
names := make([]string, 0, len(_bindata))
|
||||||
|
for name := range _bindata {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
|
var _bindata = map[string]func() (*asset, error){
|
||||||
|
"public/app.js": publicAppJs,
|
||||||
|
"public/css/main.css": publicCssMainCss,
|
||||||
|
"public/index.html": publicIndexHtml,
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetDir returns the file names below a certain
|
||||||
|
// directory embedded in the file by go-bindata.
|
||||||
|
// For example if you run go-bindata on data/... and data contains the
|
||||||
|
// following hierarchy:
|
||||||
|
// data/
|
||||||
|
// foo.txt
|
||||||
|
// img/
|
||||||
|
// a.png
|
||||||
|
// b.png
|
||||||
|
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||||
|
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||||
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||||
|
// AssetDir("") will return []string{"data"}.
|
||||||
|
func AssetDir(name string) ([]string, error) {
|
||||||
|
node := _bintree
|
||||||
|
if len(name) != 0 {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
pathList := strings.Split(cannonicalName, "/")
|
||||||
|
for _, p := range pathList {
|
||||||
|
node = node.Children[p]
|
||||||
|
if node == nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Func != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
rv := make([]string, 0, len(node.Children))
|
||||||
|
for childName := range node.Children {
|
||||||
|
rv = append(rv, childName)
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bintree struct {
|
||||||
|
Func func() (*asset, error)
|
||||||
|
Children map[string]*bintree
|
||||||
|
}
|
||||||
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
|
"public": &bintree{nil, map[string]*bintree{
|
||||||
|
"app.js": &bintree{publicAppJs, map[string]*bintree{}},
|
||||||
|
"css": &bintree{nil, map[string]*bintree{
|
||||||
|
"main.css": &bintree{publicCssMainCss, map[string]*bintree{}},
|
||||||
|
}},
|
||||||
|
"index.html": &bintree{publicIndexHtml, map[string]*bintree{}},
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// RestoreAsset restores an asset under the given directory
|
||||||
|
func RestoreAsset(dir, name string) error {
|
||||||
|
data, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := AssetInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreAssets restores an asset under the given directory recursively
|
||||||
|
func RestoreAssets(dir, name string) error {
|
||||||
|
children, err := AssetDir(name)
|
||||||
|
// File
|
||||||
|
if err != nil {
|
||||||
|
return RestoreAsset(dir, name)
|
||||||
|
}
|
||||||
|
// Dir
|
||||||
|
for _, child := range children {
|
||||||
|
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _filePath(dir, name string) string {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kataras/iris"
|
||||||
|
|
||||||
|
// $ go get -u github.com/jteeuwen/go-bindata/...
|
||||||
|
// $ go-bindata ./public/...
|
||||||
|
// $ go build
|
||||||
|
// $ ./embedded-single-page-application-with-other-routes
|
||||||
|
|
||||||
|
func newApp() *iris.Application {
|
||||||
|
app := iris.New()
|
||||||
|
app.OnErrorCode(404, func(ctx iris.Context) {
|
||||||
|
ctx.Writef("404 not found here")
|
||||||
|
})
|
||||||
|
|
||||||
|
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
|
||||||
|
app.SPA(assetHandler)
|
||||||
|
|
||||||
|
// Note:
|
||||||
|
// if you want a dynamic index page then see the file-server/embedded-single-page-application
|
||||||
|
// which is registering a view engine based on bindata as well and a root route.
|
||||||
|
|
||||||
|
app.Get("/ping", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("pong")
|
||||||
|
})
|
||||||
|
app.Get("/.well-known", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("well-known")
|
||||||
|
})
|
||||||
|
app.Get(".well-known/ready", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("ready")
|
||||||
|
})
|
||||||
|
app.Get(".well-known/live", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("live")
|
||||||
|
})
|
||||||
|
app.Get(".well-known/metrics", func(ctx iris.Context) {
|
||||||
|
ctx.Writef("metrics")
|
||||||
|
})
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := newApp()
|
||||||
|
|
||||||
|
// http://localhost:8080/index.html
|
||||||
|
// http://localhost:8080/app.js
|
||||||
|
// http://localhost:8080/css/main.css
|
||||||
|
//
|
||||||
|
// http://localhost:8080/ping
|
||||||
|
// http://localhost:8080/.well-known
|
||||||
|
// http://localhost:8080/.well-known/ready
|
||||||
|
// http://localhost:8080/.well-known/live
|
||||||
|
// http://localhost:8080/.well-known/metrics
|
||||||
|
//
|
||||||
|
// Remember: we could use the root wildcard `app.Get("/{param:path}")` and serve the files manually as well.
|
||||||
|
app.Run(iris.Addr(":8080"))
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
window.alert("app.js loaded from \"/");
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Hello from static page</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1> Hello from index.html </h1>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/app.js"> </script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -25,7 +25,11 @@ func newApp() *iris.Application {
|
||||||
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
|
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
|
||||||
// as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
|
// as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
|
||||||
// example too
|
// example too
|
||||||
app.SPA(assetHandler)
|
|
||||||
|
// 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
|
||||||
|
// the return value of `app.SPA` to modify the `IndexNames` by;
|
||||||
|
app.SPA(assetHandler).AddIndexName("index.html")
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,3 +44,10 @@ func (c *Pool) Release(ctx Context) {
|
||||||
ctx.EndRequest()
|
ctx.EndRequest()
|
||||||
c.pool.Put(ctx)
|
c.pool.Put(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReleaseLight will just release the object back to the pool, but the
|
||||||
|
// clean method is caller's responsibility now, currently this is only used
|
||||||
|
// on `SPABuilder`.
|
||||||
|
func (c *Pool) ReleaseLight(ctx Context) {
|
||||||
|
c.pool.Put(ctx)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the string representation of the current local Iris Web Framework version.
|
// Version is the string representation of the current local Iris Web Framework version.
|
||||||
Version = "8.5.7"
|
Version = "8.5.8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckForUpdates checks for any available updates
|
// CheckForUpdates checks for any available updates
|
||||||
|
|
|
@ -21,6 +21,49 @@ import (
|
||||||
"github.com/kataras/iris/context"
|
"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
|
// StaticEmbeddedHandler returns a Handler which can serve
|
||||||
// embedded into executable files.
|
// embedded into executable files.
|
||||||
//
|
//
|
||||||
|
@ -766,7 +809,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
||||||
// can't use Redirect() because that would make the path absolute,
|
// can't use Redirect() because that would make the path absolute,
|
||||||
// which would be a problem running under StripPrefix
|
// which would be a problem running under StripPrefix
|
||||||
if strings.HasSuffix(ctx.Request().URL.Path, indexPage) {
|
if strings.HasSuffix(ctx.Request().URL.Path, indexPage) {
|
||||||
localRedirect(ctx, "./")
|
localRedirect(ctx.ResponseWriter(), ctx.Request(), "./")
|
||||||
return "", http.StatusMovedPermanently
|
return "", http.StatusMovedPermanently
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,12 +830,12 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
||||||
url := ctx.Request().URL.Path
|
url := ctx.Request().URL.Path
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
if url[len(url)-1] != '/' {
|
if url[len(url)-1] != '/' {
|
||||||
localRedirect(ctx, path.Base(url)+"/")
|
localRedirect(ctx.ResponseWriter(), ctx.Request(), path.Base(url)+"/")
|
||||||
return "", http.StatusMovedPermanently
|
return "", http.StatusMovedPermanently
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if url[len(url)-1] == '/' {
|
if url[len(url)-1] == '/' {
|
||||||
localRedirect(ctx, "../"+path.Base(url))
|
localRedirect(ctx.ResponseWriter(), ctx.Request(), "../"+path.Base(url))
|
||||||
return "", http.StatusMovedPermanently
|
return "", http.StatusMovedPermanently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -802,7 +845,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
url := ctx.Request().URL.Path
|
url := ctx.Request().URL.Path
|
||||||
if url[len(url)-1] != '/' {
|
if url[len(url)-1] != '/' {
|
||||||
localRedirect(ctx, path.Base(url)+"/")
|
localRedirect(ctx.ResponseWriter(), ctx.Request(), path.Base(url)+"/")
|
||||||
return "", http.StatusMovedPermanently
|
return "", http.StatusMovedPermanently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,12 +928,14 @@ func toHTTPError(err error) (msg string, httpStatus int) {
|
||||||
|
|
||||||
// localRedirect gives a Moved Permanently response.
|
// localRedirect gives a Moved Permanently response.
|
||||||
// It does not convert relative paths to absolute paths like Redirect does.
|
// It does not convert relative paths to absolute paths like Redirect does.
|
||||||
func localRedirect(ctx context.Context, newPath string) {
|
func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) {
|
||||||
if q := ctx.Request().URL.RawQuery; q != "" {
|
if q := r.URL.RawQuery; q != "" {
|
||||||
newPath += "?" + q
|
newPath += "?" + q
|
||||||
}
|
}
|
||||||
ctx.Header("Location", newPath)
|
w.Header().Set("Location", newPath)
|
||||||
ctx.StatusCode(http.StatusMovedPermanently)
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
|
// ctx.Header("Location", newPath)
|
||||||
|
// ctx.StatusCode(http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsDotDot(v string) bool {
|
func containsDotDot(v string) bool {
|
||||||
|
|
|
@ -19,6 +19,15 @@ type SPABuilder struct {
|
||||||
AssetValidators []AssetValidator
|
AssetValidators []AssetValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddIndexName will add an index name.
|
||||||
|
// If path == $filename then it redirects to "/".
|
||||||
|
//
|
||||||
|
// It can be called after the `BuildWrapper ` as well but BEFORE the server start.
|
||||||
|
func (s *SPABuilder) AddIndexName(filename string) *SPABuilder {
|
||||||
|
s.IndexNames = append(s.IndexNames, filename)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// NewSPABuilder returns a new Single Page Application builder
|
// 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 expected to do when serving files and routes at the same time
|
||||||
// from the root "/" path.
|
// from the root "/" path.
|
||||||
|
@ -32,11 +41,13 @@ func NewSPABuilder(assetHandler context.Handler) *SPABuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SPABuilder{
|
return &SPABuilder{
|
||||||
IndexNames: []string{"index.html"},
|
IndexNames: nil,
|
||||||
|
// IndexNames is empty by-default,
|
||||||
|
// if the user wants to redirect to "/" from "/index.html" she/he can chage that to []string{"index.html"} manually.
|
||||||
AssetHandler: assetHandler,
|
AssetHandler: assetHandler,
|
||||||
AssetValidators: []AssetValidator{
|
AssetValidators: []AssetValidator{
|
||||||
func(path string) bool {
|
func(path string) bool {
|
||||||
return strings.Contains(path, ".")
|
return true // returns true by-default
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -61,23 +72,23 @@ func (s *SPABuilder) isAsset(reqPath string) bool {
|
||||||
func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
|
func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
|
||||||
|
|
||||||
fileServer := s.AssetHandler
|
fileServer := s.AssetHandler
|
||||||
indexNames := s.IndexNames
|
|
||||||
|
|
||||||
wrapper := func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
|
wrapper := func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
|
||||||
path := r.URL.Path
|
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) {
|
if !s.isAsset(path) {
|
||||||
// it's not asset, execute the registered route's handlers
|
// it's not asset, execute the registered route's handlers
|
||||||
router(w, r)
|
router(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := cPool.Acquire(w, r)
|
for _, index := range s.IndexNames {
|
||||||
|
|
||||||
for _, index := range indexNames {
|
|
||||||
if strings.HasSuffix(path, index) {
|
if strings.HasSuffix(path, index) {
|
||||||
localRedirect(ctx, "./")
|
localRedirect(w, r, "./")
|
||||||
cPool.Release(ctx)
|
|
||||||
// "/" should be manually registered.
|
// "/" should be manually registered.
|
||||||
// We don't setup an index handler here,
|
// We don't setup an index handler here,
|
||||||
// let full control to the user
|
// let full control to the user
|
||||||
|
@ -86,9 +97,28 @@ func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute file server for path
|
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)
|
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)
|
cPool.Release(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
|
||||||
|
|
||||||
Current Version
|
Current Version
|
||||||
|
|
||||||
8.5.7
|
8.5.8
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
|
||||||
|
|
3
iris.go
3
iris.go
|
@ -343,10 +343,11 @@ var (
|
||||||
// Use that instead of `StaticWeb` for root "/" file server.
|
// Use that instead of `StaticWeb` for root "/" file server.
|
||||||
//
|
//
|
||||||
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application
|
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application
|
||||||
func (app *Application) SPA(assetHandler context.Handler) {
|
func (app *Application) SPA(assetHandler context.Handler) *router.SPABuilder {
|
||||||
s := router.NewSPABuilder(assetHandler)
|
s := router.NewSPABuilder(assetHandler)
|
||||||
wrapper := s.BuildWrapper(app.ContextPool)
|
wrapper := s.BuildWrapper(app.ContextPool)
|
||||||
app.Router.WrapRouter(wrapper)
|
app.Router.WrapRouter(wrapper)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
|
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
|
||||||
|
|
Loading…
Reference in New Issue
Block a user