kataras 2017-06-15 20:02:08 +03:00
parent a10e80842f
commit e0128d204d
58 changed files with 11054 additions and 505 deletions

View File

@ -18,4 +18,8 @@ script:
# When I'll push my internal tests to github I'll do it
# to do the test coverage all folders except vendor.
# For now keep it commented.
- go test -v ./...
# - cd ./_examples
# - go get ./...
# - go test -v -cover ./...
# - cd ./...
- go test -v -cover ./...

View File

@ -30,6 +30,54 @@ Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.co
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
# Th, 15 June 2017 | v7.2.0
### File server
- **Fix** [that](https://github.com/iris-contrib/community-board/issues/12).
- `app.StaticHandler(requestPath string, systemPath string, showList bool, gzip bool)` -> `app.StaticHandler(systemPath,showList bool, gzip bool)`
- **New** feature for Single Page Applications, `app.SPA(assetHandler context.Handler)` implemented.
- **New** `app.StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string)` added in order to be able to pass that on `app.SPA(app.StaticEmbeddedHandler("./public", Asset, AssetNames))`.
- **Fix** `app.StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string)`.
Examples:
- [Embedding Files Into Executable App](_examples/beginner/file-server/embedding-files-into-app)
- [Single Page Application](_examples/beginner/file-server/single-page-applications)
- [Embedding Single Page Application](_examples/beginner/file-server/embedding-single-page-applications)
> [app.StaticWeb](_examples/beginner/file-server/basic/main.go) doesn't works for root request path "/" anymore, use the new `app.SPA` instead.
### WWW subdomain entry
- [Example](_examples/intermediate/subdomains/www/main.go) added to copy all application's routes, including parties, to the `www.mydomain.com`
### Wrapping the Router
- [Example](_examples/beginner/routing/custom-wrapper/main.go) added to show you how you can use the `app.WrapRouter`
to implement a similar to `app.SPA` functionality, don't panic, it's easier than it sounds.
### Testing
- `httptest.New(app *iris.Application, t *testing.T)` -> `httptest.New(t *testing.T, app *iris.Application)`.
- **New** `httptest.NewLocalListener() net.Listener` added.
- **New** `httptest.NewLocalTLSListener(tcpListener net.Listener) net.Listener` added.
Useful for testing tls-enabled servers:
Proxies are trying to understand local addresses in order to allow `InsecureSkipVerify`.
- `host.ProxyHandler(target *url.URL) *httputil.ReverseProxy`.
- `host.NewProxy(hostAddr string, target *url.URL) *Supervisor`.
Tests [here](core/host/proxy_test.go).
# Tu, 13 June 2017 | v7.1.1
Fix [that](https://github.com/iris-contrib/community-board/issues/11).

View File

@ -6,7 +6,7 @@ A fast, cross-platform and efficient web framework with robust set of well-desig
[![Report card](https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris)
[![Support forum](https://img.shields.io/badge/support-page-ec2eb4.svg?style=flat-square)](http://support.iris-go.com)
[![Examples](https://img.shields.io/badge/howto-examples-3362c2.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples#table-of-contents)
[![Godocs](https://img.shields.io/badge/7.1.1-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris)
[![Godocs](https://img.shields.io/badge/7.2.0-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris)
[![Chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris)
[![Buy me a cup of coffee](https://img.shields.io/badge/support-%20open--source-F4A460.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9InJnYigyMjAsMjIwLDIyMCkiIGQ9Ik04ODYuNiwzMDUuM2MtNDUuNywyMDMuMS0xODcsMzEwLjMtNDA5LjYsMzEwLjNoLTc0LjFsLTUxLjUsMzI2LjloLTYybC0zLjIsMjEuMWMtMi4xLDE0LDguNiwyNi40LDIyLjYsMjYuNGgxNTguNWMxOC44LDAsMzQuNy0xMy42LDM3LjctMzIuMmwxLjUtOGwyOS45LTE4OS4zbDEuOS0xMC4zYzIuOS0xOC42LDE4LjktMzIuMiwzNy43LTMyLjJoMjMuNWMxNTMuNSwwLDI3My43LTYyLjQsMzA4LjktMjQyLjdDOTIxLjYsNDA2LjgsOTE2LjcsMzQ4LjYsODg2LjYsMzA1LjN6Ii8%2BPHBhdGggZmlsbD0icmdiKDIyMCwyMjAsMjIwKSIgZD0iTTc5MS45LDgzLjlDNzQ2LjUsMzIuMiw2NjQuNCwxMCw1NTkuNSwxMEgyNTVjLTIxLjQsMC0zOS44LDE1LjUtNDMuMSwzNi44TDg1LDg1MWMtMi41LDE1LjksOS44LDMwLjIsMjUuOCwzMC4ySDI5OWw0Ny4zLTI5OS42bC0xLjUsOS40YzMuMi0yMS4zLDIxLjQtMzYuOCw0Mi45LTM2LjhINDc3YzE3NS41LDAsMzEzLTcxLjIsMzUzLjItMjc3LjVjMS4yLTYuMSwyLjMtMTIuMSwzLjEtMTcuOEM4NDUuMSwxODIuOCw4MzMuMiwxMzAuOCw3OTEuOSw4My45TDc5MS45LDgzLjl6Ii8%2BPC9zdmc%2B)](https://github.com/kataras/iris#buy-me-a-cup-of-coffee)
@ -409,7 +409,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports
Version
------------
Current: **7.1.1**
Current: **7.2.0**
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".

View File

@ -25,6 +25,7 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [Basic](beginner/routing/basic/main.go)
* [Dynamic Path](beginner/routing/dynamic-path/main.go)
* [Reverse routing](beginner/routing/reverse/main.go)
* [Custom wrapper](beginner/routing/custom-wrapper/main.go)
* [Transform any third-party handler to iris-compatible handler](beginner/convert-handlers)
* [From func(http.ResponseWriter, *http.Request, http.HandlerFunc)](beginner/convert-handlers/negroni-like/main.go)
* [From http.Handler or http.HandlerFunc](beginner/convert-handlers/nethttp/main.go)
@ -34,7 +35,11 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [Read JSON](beginner/read-json/main.go)
* [Read Form](beginner/read-form/main.go)
* [Favicon](beginner/favicon/main.go)
* [File Server](beginner/file-server/main.go)
* [File Server](beginner/file-server)
* [Basic](beginner/file-server/basic/main.go)
* [Embedding Files Into App Executable File](beginner/file-server/embedding-files-into-app/main.go)
* [Single Page Application](beginner/file-server/single-page-application/main.go)
* [Embedding Single Page Application](beginner/file-server/embedding-single-page-application/main.go)
* [Send Files](beginner/send-files/main.go)
* [Stream Writer](beginner/stream-writer/main.go)
* [Send An E-mail](beginner/e-mail/main.go)
@ -51,7 +56,6 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [HTTP Testing](intermediate/httptest/main_test.go)
* [Watch & Compile Typescript source files](intermediate/typescript/main.go)
* [Cloud Editor](intermediate/cloud-editor/main.go)
* [Serve Embedded Files](intermediate/serve-embedded-files/main.go)
* [HTTP Access Control](intermediate/cors/main.go)
* [Cache Markdown](intermediate/cache-markdown/main.go)
* [Localization and Internationalization](intermediate/i18n/main.go)
@ -86,13 +90,14 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [Ridiculous Simple](intermediate/websockets/ridiculous-simple/main.go)
* [Overview](intermediate/websockets/overview/main.go)
* [Connection List](intermediate/websockets/connectionlist/main.go)
* [Native Messages](intermediate/websockets/naive-messages/main.go)
* [Native Messages](intermediate/websockets/native-messages/main.go)
* [Secure](intermediate/websockets/secure/main.go)
* [Custom Go Client](intermediate/websockets/custom-go-client/main.go)
* [Subdomains](intermediate/subdomains)
* [Single](intermediate/subdomains/single/main.go)
* [Multi](intermediate/subdomains/multi/main.go)
* [Wildcard](intermediate/subdomains/wildcard/main.go)
* [WWW](intermediate/subdomains/www/main.go)
* [Level: Advanced](advanced)
* [Online Visitors](advanced/online-visitors/main.go)
* [URL Shortener using BoltDB](advanced/url-shortener/main.go)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,30 @@
package main
import (
"github.com/kataras/iris"
)
func main() {
app := iris.New()
app.Favicon("./assets/favicon.ico")
// first parameter is the request path
// second is the system directory
//
// app.StaticWeb("/css", "./assets/css")
// app.StaticWeb("/js", "./assets/js")
//
app.StaticWeb("/static", "./assets")
// http://localhost:8080/static/css/main.css
// http://localhost:8080/static/js/jquery-2.1.1.js
// http://localhost:8080/static/favicon.ico
app.Run(iris.Addr(":8080"))
// Note:
// Routing doesn't allows something .StaticWeb("/", "./assets")
//
// To see how you can wrap the router in order to achieve
// wildcard on root path, see "single-page-application".
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
package main
import (
"github.com/kataras/iris"
)
// Follow these steps first:
// $ go get -u github.com/jteeuwen/go-bindata/...
// $ go-bindata ./assets/...
// $ go build
// $ ./embedding-files-into-app
// "physical" files are not used, you can delete the "assets" folder and run the example.
func newApp() *iris.Application {
app := iris.New()
app.StaticEmbedded("/static", "./assets", Asset, AssetNames)
return app
}
func main() {
app := newApp()
// http://localhost:8080/static/css/bootstrap.min.css
// http://localhost:8080/static/js/jquery-2.1.1.js
// http://localhost:8080/static/favicon.ico
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,60 @@
package main
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
type resource string
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
filename = r.strip("/static")
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
return string(b)
}
var urls = []resource{
"/static/css/bootstrap.min.css",
"/static/js/jquery-2.1.1.js",
"/static/favicon.ico",
}
// if bindata's values matches with the assets/... contents
// and secondly if the StaticEmbedded had successfully registered
// the routes and gave the correct response.
func TestEmbeddingFilesIntoApp(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
for _, u := range urls {
url := u.String()
contents := u.loadFromBase("./assets")
e.GET(url).Expect().
Status(iris.StatusOK).
Body().Equal(contents)
}
}

View File

@ -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(438), modTime: time.Unix(1497458456, 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(438), modTime: time.Unix(1497455997, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _publicIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\x8e\x41\x0e\xc2\x20\x10\x45\xf7\x24\xdc\xe1\xa7\x07\x80\x74\x3f\xb2\x76\xe9\xc2\x0b\x60\x41\xc1\x50\x21\xc0\x42\xd3\xf4\xee\x06\x4a\x97\x93\xf7\x66\xde\x90\xab\x6b\x50\x9c\x71\x46\xce\x6a\xa3\x38\x03\x00\xaa\xbe\x06\xab\xb6\x0d\xe2\xa6\x5f\x56\xdc\xdb\x88\x7d\x27\x79\x00\xce\x48\x0e\x9d\x33\x7a\x44\xf3\x3b\x17\xdd\xac\x70\xb5\x21\x44\x3c\x73\x5c\xe1\x3f\xc6\x7e\x45\x6b\x80\xa4\x9b\xbb\x3f\xcc\xb2\x64\x9f\x2a\x4a\x5e\x2e\x93\xd4\x29\x89\x77\x99\x14\x40\xf2\x00\xbd\x31\x2e\xf7\x5c\xfb\xf3\x1f\x00\x00\xff\xff\x25\xe9\x37\x57\xae\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: 174, mode: os.FileMode(438), modTime: time.Unix(1497460815, 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, "/")...)...)
}

View File

@ -0,0 +1,49 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/view"
)
// $ go get -u github.com/jteeuwen/go-bindata/...
// $ go-bindata ./public/...
// $ go build
// $ ./embedding-single-page-application
var page = struct {
Title string
}{"Welcome"}
func newApp() *iris.Application {
app := iris.New()
app.AttachView(view.HTML("./public", ".html").Binary(Asset, AssetNames))
app.Get("/", func(ctx context.Context) {
ctx.ViewData("Page", page)
ctx.View("index.html")
})
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
app.SPA(assetHandler)
return app
}
func main() {
app := newApp()
// http://localhost:8080
// http://localhost:8080/index.html
// http://localhost:8080/app.js
// http://localhost:8080/css/main.css
app.Run(iris.Addr(":8080"))
}
// Note that app.Use/UseGlobal/Done will be executed
// only to the registered routes like our index (app.Get("/", ..)).
// The file server is clean, but you can still add middleware to that by wrapping its "assetHandler".
//
// With this method, unlike StaticWeb("/" , "./public") which is not working by-design anymore,
// all custom http errors and all routes are working fine with a file server that is registered
// to the root path of the server.

View File

@ -0,0 +1,61 @@
package main
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
type resource string
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
if filename == "/" {
filename = "/index.html"
}
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
return string(b)
}
var urls = []resource{
"/",
"/index.html",
"/app.js",
"/css/main.css",
}
func TestSPAEmbedded(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
for _, u := range urls {
url := u.String()
contents := u.loadFromBase("./public")
contents = strings.Replace(contents, "{{ .Page.Title }}", page.Title, 1)
e.GET(url).Expect().
Status(iris.StatusOK).
Body().Equal(contents)
}
}

View File

@ -0,0 +1 @@
window.alert("app.js loaded from \"/");

View File

@ -0,0 +1,3 @@
body {
background-color: black;
}

View File

@ -0,0 +1,14 @@
<html>
<head>
<title>{{ .Page.Title }}</title>
</head>
<body>
<h1> Hello from index.html </h1>
<script src="/app.js"> </script>
</body>
</html>

View File

@ -1,16 +0,0 @@
package main
import (
"github.com/kataras/iris"
)
func main() {
app := iris.New()
// first parameter is the request path
// second is the operating system directory
app.StaticWeb("/static", "./assets")
// http://localhost:8080/static/css/main.css
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,44 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/view"
)
// same as embedding-single-page-application but without go-bindata, the files are "physical" stored in the
// current system directory.
var page = struct {
Title string
}{"Welcome"}
func newApp() *iris.Application {
app := iris.New()
app.AttachView(view.HTML("./public", ".html"))
app.Get("/", func(ctx context.Context) {
ctx.ViewData("Page", page)
ctx.View("index.html")
})
// or just serve index.html as it is:
// app.Get("/", func(ctx context.Context) {
// ctx.ServeFile("index.html", false)
// })
assetHandler := app.StaticHandler("./public", false, false)
app.SPA(assetHandler)
return app
}
func main() {
app := newApp()
// http://localhost:8080
// http://localhost:8080/index.html
// http://localhost:8080/app.js
// http://localhost:8080/css/main.css
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,61 @@
package main
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
type resource string
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
if filename == "/" {
filename = "/index.html"
}
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
return string(b)
}
var urls = []resource{
"/",
"/index.html",
"/app.js",
"/css/main.css",
}
func TestSPA(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
for _, u := range urls {
url := u.String()
contents := u.loadFromBase("./public")
contents = strings.Replace(contents, "{{ .Page.Title }}", page.Title, 1)
e.GET(url).Expect().
Status(iris.StatusOK).
Body().Equal(contents)
}
}

View File

@ -0,0 +1 @@
window.alert("app.js loaded from \"/");

View File

@ -0,0 +1,3 @@
body {
background-color: black;
}

View File

@ -0,0 +1,14 @@
<html>
<head>
<title>{{ .Page.Title }}</title>
</head>
<body>
<h1> Hello from index.html </h1>
<script src="/app.js"> </script>
</body>
</html>

View File

@ -1,8 +1,12 @@
package main
import (
"net/url"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/host"
)
func main() {
@ -16,6 +20,12 @@ func main() {
ctx.Writef("Hello from the SECURE server on path /mypath")
})
// to start a new server listening at :80 and redirects
// to the secure address, then:
target, _ := url.Parse("https://127.0.1:443")
go host.NewProxy("127.0.0.1:80", target).ListenAndServe()
// start the server (HTTPS) on port 443, this is a blocking func
app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key"))
}

View File

@ -168,3 +168,5 @@ func notFoundHandler(ctx context.Context) {
// A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed.
// If route failed to be registered, the app will panic without any warnings
// if you didn't catch the second return value(error) on .Handle/.Get....
// See "file-server/single-page-application" to see how another feature, "WrapRouter", works.

View File

@ -0,0 +1,92 @@
package main
import (
"net/http"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
// In this example you'll just see one use case of .WrapRouter.
// You can use the .WrapRouter to add custom logic when or when not the router should
// be executed in order to execute the registered routes' handlers.
//
// To see how you can serve files on root "/" without a custom wrapper
// just navigate to the "beginner/file-server/single-page-application" example.
//
// This is just for the proof of concept, you can skip this tutorial if it's too much for you.
func newApp() *iris.Application {
app := iris.New()
app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) {
ctx.HTML("<b>Resource Not found</b>")
})
app.Get("/", func(ctx context.Context) {
ctx.ServeFile("./public/index.html", false)
})
app.Get("/profile/{username}", func(ctx context.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("username"))
})
// serve files from the root "/", if we used .StaticWeb it could override
// all the routes because of the underline need of wildcard.
// Here we will see how you can by-pass this behavior
// by creating a new file server handler and
// setting up a wrapper for the router(like a "low-level" middleware)
// in order to manually check if we want to process with the router as normally
// or execute the file server handler instead.
// use of the .StaticHandler
// which is the same as StaticWeb but it doesn't
// registers the route, it just returns the handler.
fileServer := app.StaticHandler("./public", false, false)
// wrap the router with a native net/http handler.
// if url does not contain any "." (i.e: .css, .js...)
// (depends on the app , you may need to add more file-server exceptions),
// then the handler will execute the router that is responsible for the
// registered routes (look "/" and "/profile/{username}")
// if not then it will serve the files based on the root "/" path.
app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
path := r.URL.Path
// Note that if path has suffix of "index.html" it will auto-permant redirect to the "/",
// so our first handler will be executed instead.
if !strings.Contains(path, ".") { // if it's not a resource then continue to the router as normally.
router(w, r)
return
}
// acquire and release a context in order to use it to execute
// our file server
// remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
ctx := app.ContextPool.Acquire(w, r)
fileServer(ctx)
app.ContextPool.Release(ctx)
})
return app
}
func main() {
app := newApp()
// http://localhost:8080
// http://localhost:8080/index.html
// http://localhost:8080/app.js
// http://localhost:8080/css/main.css
// http://localhost:8080/profile/kataras
app.Run(iris.Addr(":8080"))
// Note: In this example we just saw one use case,
// you may want to .WrapRouter or .Downgrade in order to bypass the iris' default router, i.e:
// you can use that method to setup custom proxies too.
//
// If you just want to serve static files on other path than root
// you can just use the StaticWeb, i.e:
// .StaticWeb("/static", "./public")
// ________________________________requestPath, systemPath
}

View File

@ -0,0 +1,60 @@
package main
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
type resource string
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
if filename == "/" {
filename = "/index.html"
}
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
return string(b)
}
var urls = []resource{
"/",
"/index.html",
"/app.js",
"/css/main.css",
}
func TestCustomWrapper(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
for _, u := range urls {
url := u.String()
contents := u.loadFromBase("./public")
e.GET(url).Expect().
Status(iris.StatusOK).
Body().Equal(contents)
}
}

View File

@ -0,0 +1 @@
window.alert("app.js loaded from \"/");

View File

@ -0,0 +1,3 @@
body {
background-color: black;
}

View File

@ -0,0 +1,14 @@
<html>
<head>
<title>{{ .Page.Title }}</title>
</head>
<body>
<h1> Hello from index.html </h1>
<script src="/app.js"> </script>
</body>
</html>

View File

@ -11,7 +11,7 @@ import (
// $ go test -v
func TestNewApp(t *testing.T) {
app := newApp()
e := httptest.New(app, t)
e := httptest.New(t, app)
// redirects to /admin without basic auth
e.GET("/").Expect().Status(iris.StatusUnauthorized)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,43 +0,0 @@
// for templates + go-bindata checkout the '_examples\intermediate\view\embedding-templates-into-app' folder.
package main
// First of all, execute: $ go get https://github.com/jteeuwen/go-bindata
// Secondly, execute the command: cd $GOPATH/src/github.com/kataras/iris/_examples/intermediate/serve-embedded-files && go-bindata ./assets/...
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
app.Get("/", func(ctx context.Context) {
ctx.HTML("<b> Hi from index</b>")
})
// executing this go-bindata command creates a source file named 'bindata.go' which
// gives you the Asset and AssetNames funcs which we will pass into .StaticAssets
// for more viist: https://github.com/jteeuwen/go-bindata
// Iris gives you a way to integrade these functions to your web app
// For the reason that you may use go-bindata to embed more than your assets,
// you should pass the 'virtual directory path', for example here is the : "./assets"
// and the request path, which these files will be served to,
// you can set as "/assets" or "/static" which resulting on http://localhost:8080/static/*anyfile.*extension
app.StaticEmbedded("/static", "./assets", Asset, AssetNames)
// that's all
// this will serve the ./assets (embedded) files to the /static request path for example the favicon.ico will be served as :
// http://localhost:8080/static/favicon.ico
// Methods: GET and HEAD
app.Run(iris.Addr(":8080"))
}
// Navigate to:
// http://localhost:8080/static/favicon.ico
// http://localhost:8080/static/js/jquery-2.1.1.js
// http://localhost:8080/static/css/bootstrap.min.css
// Now, these files are are stored into inside your executable program, no need to keep it in the same location with your assets folder.

View File

@ -0,0 +1,25 @@
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
127.0.0.1 localhost
::1 localhost
#-IRIS-For development machine, you have to configure your dns also for online, search google how to do it if you don't know
127.0.0.1 iris-go.com
127.0.0.1 www.iris-go.com
#-END IRIS-

View File

@ -0,0 +1,65 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func newApp() *iris.Application {
app := iris.New()
app.Get("/", info)
app.Get("/about", info)
app.Get("/contact", info)
usersAPI := app.Party("/api/users")
{
usersAPI.Get("/", info)
usersAPI.Get("/{id:int}", info)
usersAPI.Post("/", info)
usersAPI.Put("/{id:int}", info)
}
www := app.Party("www.")
{
// get all routes that are registered so far, including all "Parties":
currentRoutes := app.GetRoutes()
// register them to the www subdomain/vhost as well:
for _, r := range currentRoutes {
if _, err := www.Handle(r.Method, r.Path, r.Handlers...); err != nil {
app.Log("%s for www. failed: %v", r.Path, err)
}
}
}
return app
}
func main() {
app := newApp()
// http://iris-go.com
// http://iris-go.com/about
// http://iris-go.com/contact
// http://iris-go.com/api/users
// http://iris-go.com/api/users/42
// http://www.iris-go.com
// http://www.iris-go.com/about
// http://www.iris-go.com/contact
// http://www.iris-go.com/api/users
// http://www.iris-go.com/api/users/42
if err := app.Run(iris.Addr("iris-go.com:80")); err != nil {
panic(err)
}
}
func info(ctx context.Context) {
method := ctx.Method()
subdomain := ctx.Subdomain()
path := ctx.Path()
ctx.Writef("\nInfo\n\n")
ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s", method, subdomain, path)
}

View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
type testRoute struct {
path string
method string
subdomain string
}
func (r testRoute) response() string {
msg := fmt.Sprintf("\nInfo\n\nMethod: %s\nSubdomain: %s\nPath: %s", r.method, r.subdomain, r.path)
return msg
}
func TestSubdomainWWW(t *testing.T) {
app := newApp()
tests := []testRoute{
// host
{"/", "GET", ""},
{"/about", "GET", ""},
{"/contact", "GET", ""},
{"/api/users", "GET", ""},
{"/api/users/42", "GET", ""},
{"/api/users", "POST", ""},
{"/api/users/42", "PUT", ""},
// www sub domain
{"/", "GET", "www"},
{"/about", "GET", "www"},
{"/contact", "GET", "www"},
{"/api/users", "GET", "www"},
{"/api/users/42", "GET", "www"},
{"/api/users", "POST", "www"},
{"/api/users/42", "PUT", "www"},
}
host := "localhost:1111"
e := httptest.New(t, app, httptest.URL("http://"+host))
for _, test := range tests {
req := e.Request(test.method, test.path)
if subdomain := test.subdomain; subdomain != "" {
req.WithURL("http://" + subdomain + "." + host)
}
req.Expect().
Status(iris.StatusOK).
Body().Equal(test.response())
}
}

25
cache/cache.go vendored
View File

@ -17,7 +17,7 @@ Example code:
func main(){
app := iris.Default()
cachedHandler := cache.CacheHandler(h, 2 *time.Minute)
cachedHandler := cache.WrapHandler(h, 2 *time.Minute)
app.Get("/hello", cachedHandler)
app.Run(iris.Addr(":8080"))
}
@ -44,12 +44,12 @@ import (
//
// All type of responses are cached, templates, json, text, anything.
//
// You can add validators with this function
// You can add validators with this function.
func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handler {
return client.NewHandler(bodyHandler, expiration)
}
// CacheHandler accepts two parameters
// WrapHandler accepts two parameters
// first is the context.Handler which you want to cache its result
// the second is, optional, the cache Entry's expiration duration
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
@ -57,11 +57,26 @@ func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handle
//
// All type of responses are cached, templates, json, text, anything.
//
// it returns the context.Handler, for more options use the .Cache .
func CacheHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
// it returns a context.Handler, for more options use the .Cache .
func WrapHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
return Cache(bodyHandler, expiration).ServeHTTP
}
// Handler accepts one single parameter:
// the cache Entry's expiration duration
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
// returns context.Handler.
//
// It's the same as Cache and WrapHandler but it sets the "bodyHandler" to the next handler in the chain.
//
// All type of responses are cached, templates, json, text, anything.
//
// it returns a context.Handler, for more options use the .Cache .
func Handler(expiration time.Duration) context.Handler {
h := WrapHandler(nil, expiration)
return h
}
var (
// NoCache called when a particular handler is not valid for cache.
// If this function called inside a handler then the handler is not cached

197
cache/cache_test.go vendored Normal file
View File

@ -0,0 +1,197 @@
// black-box testing
package cache_test
import (
"net/http"
"sync/atomic"
"testing"
"time"
"github.com/kataras/iris/cache"
"github.com/kataras/iris/cache/client/rule"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/iris-contrib/httpexpect"
"github.com/kataras/iris/httptest"
)
var (
cacheDuration = 2 * time.Second
expectedBodyStr = "Imagine it as a big message to achieve x20 response performance!"
errTestFailed = errors.New("expected the main handler to be executed %d times instead of %d")
)
func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, nocache string) error {
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
time.Sleep(cacheDuration / 5) // lets wait for a while, cache should be saved and ready
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter := atomic.LoadUint32(counterPtr)
if counter > 1 {
// n should be 1 because it doesn't changed after the first call
return errTestFailed.Format(1, counter)
}
time.Sleep(cacheDuration)
// cache should be cleared now
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
time.Sleep(cacheDuration / 5)
// let's call again , the cache should be saved
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter = atomic.LoadUint32(counterPtr)
if counter != 2 {
return errTestFailed.Format(2, counter)
}
// we have cache response saved for the "/" path, we have some time more here, but here
// we will make the requestS with some of the deniers options
e.GET("/").WithHeader("max-age", "0").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
e.GET("/").WithHeader("Authorization", "basic or anything").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter = atomic.LoadUint32(counterPtr)
if counter != 4 {
return errTestFailed.Format(4, counter)
}
if nocache != "" {
// test the NoCache, first sleep to pass the cache expiration,
// second add to the cache with a valid request and response
// third, do it with the "/nocache" path (static for now, pure test design) given by the consumer
time.Sleep(cacheDuration)
// cache should be cleared now, this should work because we are not in the "nocache" path
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 5
time.Sleep(cacheDuration / 5)
// let's call the "nocache", the expiration is not passed so but the "nocache"
// route's path has the cache.NoCache so it should be not cached and the counter should be ++
e.GET(nocache).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter should be 6
counter = atomic.LoadUint32(counterPtr)
if counter != 6 { // 4 before, 5 with the first call to store the cache, and six with the no cache, again original handler executation
return errTestFailed.Format(6, counter)
}
// let's call again the "/", the expiration is not passed so it should be cached
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter = atomic.LoadUint32(counterPtr)
if counter != 6 {
return errTestFailed.Format(6, counter)
}
// but now check for the No
}
return nil
}
func TestNoCache(t *testing.T) {
app := iris.New()
var n uint32
app.Get("/", cache.WrapHandler(func(ctx context.Context) {
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
}, cacheDuration))
app.Get("/nocache", cache.WrapHandler(func(ctx context.Context) {
cache.NoCache(ctx) // <----
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
}, cacheDuration))
e := httptest.New(t, app)
if err := runTest(e, &n, expectedBodyStr, "/nocache"); err != nil {
t.Fatalf(t.Name()+": %v", err)
}
}
func TestCache(t *testing.T) {
app := iris.New()
var n uint32
app.Use(cache.Handler(cacheDuration))
app.Get("/", func(ctx context.Context) {
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
})
e := httptest.New(t, app)
if err := runTest(e, &n, expectedBodyStr, ""); err != nil {
t.Fatalf(t.Name()+": %v", err)
}
}
func TestCacheHandlerParallel(t *testing.T) {
t.Parallel()
TestCache(t)
}
func TestCacheValidator(t *testing.T) {
app := iris.New()
var n uint32
h := func(ctx context.Context) {
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
}
validCache := cache.Cache(h, cacheDuration)
app.Get("/", validCache.ServeHTTP)
managedCache := cache.Cache(h, cacheDuration)
managedCache.AddRule(rule.Validator([]rule.PreValidator{
func(ctx context.Context) bool {
if ctx.Request().URL.Path == "/invalid" {
return false // should always invalid for cache, don't bother to go to try to get or set cache
}
return true
},
}, nil))
managedCache2 := cache.Cache(func(ctx context.Context) {
atomic.AddUint32(&n, 1)
ctx.Header("DONT", "DO not cache that response even if it was claimed")
ctx.Write([]byte(expectedBodyStr))
}, cacheDuration)
managedCache2.AddRule(rule.Validator(nil,
[]rule.PostValidator{
func(ctx context.Context) bool {
if ctx.ResponseWriter().Header().Get("DONT") != "" {
return false // it's passed the Claim and now Valid checks if the response contains a header of "DONT"
}
return true
},
},
))
app.Get("/valid", validCache.ServeHTTP)
app.Get("/invalid", managedCache.ServeHTTP)
app.Get("/invalid2", managedCache2.ServeHTTP)
e := httptest.New(t, app)
// execute from cache the next time
e.GET("/valid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
time.Sleep(cacheDuration / 5) // lets wait for a while, cache should be saved and ready
e.GET("/valid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter := atomic.LoadUint32(&n)
if counter > 1 {
// n should be 1 because it doesn't changed after the first call
t.Fatal(errTestFailed.Format(1, counter))
}
// don't execute from cache, execute the original, counter should ++ here
e.GET("/invalid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 2
e.GET("/invalid2").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 3
counter = atomic.LoadUint32(&n)
if counter != 3 {
// n should be 1 because it doesn't changed after the first call
t.Fatalf(t.Name()+": %v", errTestFailed.Format(3, counter))
}
}

View File

@ -18,7 +18,8 @@ import (
// the validator for each of the incoming requests and post responses
type Handler struct {
// bodyHandler the original route's handler
// bodyHandler the original route's handler.
// If nil then it tries to take the next handler from the chain.
bodyHandler context.Handler
// Rule optional validators for pre cache and post cache actions
@ -71,8 +72,24 @@ func (h *Handler) AddRule(r rule.Rule) *Handler {
func (h *Handler) ServeHTTP(ctx context.Context) {
// check for pre-cache validators, if at least one of them return false
// for this specific request, then skip the whole cache
bodyHandler := h.bodyHandler
if bodyHandler == nil {
if nextHandler := ctx.NextHandler(); nextHandler != nil {
// skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
// even if it's not executed because it's cached.
ctx.Skip()
bodyHandler = nextHandler
} else {
ctx.StatusCode(500)
ctx.WriteString("cache: empty body handler")
ctx.StopExecution()
return
}
}
if !h.rule.Claim(ctx) {
h.bodyHandler(ctx)
bodyHandler(ctx)
return
}
@ -85,7 +102,7 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
// because the net/http doesn't give us
// a built'n way to get the status code & body
recorder := ctx.Recorder()
h.bodyHandler(ctx)
bodyHandler(ctx)
// now that we have recordered the response,
// we are ready to check if that specific response is valid to be stored.

View File

@ -267,7 +267,8 @@ type Context interface {
// Host returns the host part of the current url.
Host() string
// Subdomain returns the subdomain (string) of this request, if any.
// Subdomain returns the subdomain of this request, if any.
// Note that this is a fast method which does not cover all cases.
Subdomain() (subdomain string)
// RemoteAddr tries to return the real client's request IP.
RemoteAddr() string
@ -400,9 +401,9 @@ type Context interface {
//
// Returns the number of bytes written and any write error encountered.
WriteString(body string) (int, error)
// SetClientCachedBody like SetBody but it sends with an expiration datetime
// WriteWithExpiration like SetBody but it sends with an expiration datetime
// which is managed by the client-side (all major web browsers supports this)
WriteWithExpiration(status int, bodyContent []byte, cType string, modtime time.Time) error
WriteWithExpiration(bodyContent []byte, cType string, modtime time.Time) error
// StreamWriter registers the given stream writer for populating
// response body.
//
@ -907,7 +908,6 @@ func (ctx *context) NextHandler() Handler {
if ctx.IsStopped() {
return nil
}
nextIndex := ctx.currentHandlerIndex + 1
// check if it has a next middleware
if nextIndex < len(ctx.handlers) {
@ -1048,13 +1048,21 @@ func (ctx *context) Host() string {
return h
}
// Subdomain returns the subdomain (string) of this request, if any.
// Subdomain returns the subdomain of this request, if any.
// Note that this is a fast method which does not cover all cases.
func (ctx *context) Subdomain() (subdomain string) {
host := ctx.Host()
if index := strings.IndexByte(host, '.'); index > 0 {
subdomain = host[0:index]
}
// listening on iris-go.com:80
// subdomain = iris-go, but it's wrong, it should return ""
vhost := ctx.Application().ConfigurationReadOnly().GetVHost()
if strings.Contains(vhost, subdomain) { // then it's not subdomain
return ""
}
return
}
@ -1422,7 +1430,7 @@ func (ctx *context) staticCachePassed(modtime time.Time) bool {
// WriteWithExpiration like Write but it sends with an expiration datetime
// which is managed by the client-side (all major web browsers supports this)
func (ctx *context) WriteWithExpiration(status int, bodyContent []byte, cType string, modtime time.Time) error {
func (ctx *context) WriteWithExpiration(bodyContent []byte, cType string, modtime time.Time) error {
if ctx.staticCachePassed(modtime) {
return nil
}
@ -1431,7 +1439,6 @@ func (ctx *context) WriteWithExpiration(status int, bodyContent []byte, cType st
ctx.writer.Header().Set(contentTypeHeaderKey, cType)
ctx.writer.Header().Set(lastModifiedHeaderKey, modtimeFormatted)
ctx.StatusCode(status)
_, err := ctx.writer.Write(bodyContent)
return err

View File

@ -25,7 +25,7 @@ func TestFromStd(t *testing.T) {
app.Get("/handler", h)
app.Get("/func", hFunc)
e := httptest.New(app, t)
e := httptest.New(t, app)
e.GET("/handler").
Expect().Status(iris.StatusOK).Body().Equal(expected)
@ -56,7 +56,7 @@ func TestFromStdWithNext(t *testing.T) {
app := iris.New()
app.Get("/handlerwithnext", h, next)
e := httptest.New(app, t)
e := httptest.New(t, app)
e.GET("/handlerwithnext").
Expect().Status(iris.StatusForbidden)

View File

@ -67,7 +67,7 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
// It uses the httputil.NewSingleHostReverseProxy.
//
// Usage:
// target,_ := url.Parse("https://mydomain.com")
// target, _ := url.Parse("https://mydomain.com")
// proxy := NewProxy("mydomain.com:80", target)
// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server.
func NewProxy(hostAddr string, target *url.URL) *Supervisor {

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
}

6
doc.go
View File

@ -685,10 +685,10 @@ Static Files
// 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(reqPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
Example code:
@ -720,7 +720,7 @@ Example code:
app.Run(iris.Addr(":8080"))
}
More examples can be found here: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server
Middleware Ecosystem

View File

@ -66,16 +66,9 @@ func DefaultConfiguration() *Configuration {
return &Configuration{URL: "", Debug: false}
}
// New Prepares and returns a new test framework based on the app
// is useful when you need to have more than one test framework for the same iris instance
// usage:
// iris.Default.Get("/mypath", func(ctx context.Context){ctx.Write("my body")})
// ...
// e := httptest.New(iris.Default, t)
// e.GET("/mypath").Expect().Status(iris.StatusOK).Body().Equal("my body")
//
// You can find example on the https://github.com/kataras/iris/glob/master/context_test.go
func New(app *iris.Application, t *testing.T, setters ...OptionSetter) *httpexpect.Expect {
// New Prepares and returns a new test framework based on the "app".
// You can find example on the https://github.com/kataras/iris/tree/master/_examples/intermediate/httptest
func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpect.Expect {
conf := DefaultConfiguration()
for _, setter := range setters {
setter.Set(conf)
@ -103,7 +96,8 @@ func New(app *iris.Application, t *testing.T, setters ...OptionSetter) *httpexpe
return httpexpect.WithConfig(testConfiguration)
}
// NewInsecure same as New but receives a single host instead of the whole framework
// NewInsecure same as New but receives a single host instead of the whole framework.
// Useful for testing running TLS servers.
func NewInsecure(t *testing.T, setters ...OptionSetter) *httpexpect.Expect {
conf := DefaultConfiguration()
for _, setter := range setters {

19
iris.go
View File

@ -41,7 +41,7 @@ const (
// Version is the current version number of the Iris Web framework.
//
// Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
Version = "7.1.1"
Version = "7.2.0"
)
const (
@ -411,6 +411,23 @@ func (app *Application) SessionManager() (sessions.Sessions, error) {
return app.sessions, nil
}
// 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.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server/single-page-application
func (app *Application) SPA(assetHandler context.Handler) {
s := router.NewSPABuilder(assetHandler)
wrapper := s.BuildWrapper(app.ContextPool)
app.Router.WrapRouter(wrapper)
}
// ConfigurationReadOnly returns a structure which doesn't allow writing.
func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
return app.config

View File

@ -32,13 +32,12 @@ func ToHandler(handler interface{}) context.Handler {
// Cache provides cache capabilities to a route's handler.
// Usage:
// Get("/", Cache(func(ctx context.Context){
// Get("/", iris.Cache(time.Duration(10*time.Second)), func(ctx context.Context){
// ctx.Writef("Hello, world!") // or a template or anything else
// }, time.Duration(10*time.Second))) // duration of expiration
// if <=time.Second then it tries to find it though request header's "cache-control" maxage value.
// })
//
// Deprecated. Use "github.com/kataras/iris/cache" sub-package instead.
var Cache = cache.CacheHandler
// Deprecated. Use "github.com/kataras/iris/cache" sub-package which contains the full features instead.
var Cache = cache.Handler
// CheckErr is the old `Must`. It panics on errors as expected with
// the old listen functions, change of this method will affect only ListenXXX functions.

View File

@ -46,7 +46,7 @@ func h(ctx context.Context) {
}
func TestBasicAuth(t *testing.T) {
app := buildApp()
e := httptest.New(app, t)
e := httptest.New(t, app)
// redirects to /admin without basic auth
e.GET("/").Expect().Status(iris.StatusUnauthorized)