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 # When I'll push my internal tests to github I'll do it
# to do the test coverage all folders except vendor. # to do the test coverage all folders except vendor.
# For now keep it commented. # 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! 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 # Tu, 13 June 2017 | v7.1.1
Fix [that](https://github.com/iris-contrib/community-board/issues/11). 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) [![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) [![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) [![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) [![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) [![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 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". 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) * [Basic](beginner/routing/basic/main.go)
* [Dynamic Path](beginner/routing/dynamic-path/main.go) * [Dynamic Path](beginner/routing/dynamic-path/main.go)
* [Reverse routing](beginner/routing/reverse/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) * [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 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) * [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 JSON](beginner/read-json/main.go)
* [Read Form](beginner/read-form/main.go) * [Read Form](beginner/read-form/main.go)
* [Favicon](beginner/favicon/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) * [Send Files](beginner/send-files/main.go)
* [Stream Writer](beginner/stream-writer/main.go) * [Stream Writer](beginner/stream-writer/main.go)
* [Send An E-mail](beginner/e-mail/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) * [HTTP Testing](intermediate/httptest/main_test.go)
* [Watch & Compile Typescript source files](intermediate/typescript/main.go) * [Watch & Compile Typescript source files](intermediate/typescript/main.go)
* [Cloud Editor](intermediate/cloud-editor/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) * [HTTP Access Control](intermediate/cors/main.go)
* [Cache Markdown](intermediate/cache-markdown/main.go) * [Cache Markdown](intermediate/cache-markdown/main.go)
* [Localization and Internationalization](intermediate/i18n/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) * [Ridiculous Simple](intermediate/websockets/ridiculous-simple/main.go)
* [Overview](intermediate/websockets/overview/main.go) * [Overview](intermediate/websockets/overview/main.go)
* [Connection List](intermediate/websockets/connectionlist/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) * [Secure](intermediate/websockets/secure/main.go)
* [Custom Go Client](intermediate/websockets/custom-go-client/main.go) * [Custom Go Client](intermediate/websockets/custom-go-client/main.go)
* [Subdomains](intermediate/subdomains) * [Subdomains](intermediate/subdomains)
* [Single](intermediate/subdomains/single/main.go) * [Single](intermediate/subdomains/single/main.go)
* [Multi](intermediate/subdomains/multi/main.go) * [Multi](intermediate/subdomains/multi/main.go)
* [Wildcard](intermediate/subdomains/wildcard/main.go) * [Wildcard](intermediate/subdomains/wildcard/main.go)
* [WWW](intermediate/subdomains/www/main.go)
* [Level: Advanced](advanced) * [Level: Advanced](advanced)
* [Online Visitors](advanced/online-visitors/main.go) * [Online Visitors](advanced/online-visitors/main.go)
* [URL Shortener using BoltDB](advanced/url-shortener/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 package main
import ( import (
"net/url"
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/host"
) )
func main() { func main() {
@ -16,6 +20,12 @@ func main() {
ctx.Writef("Hello from the SECURE server on path /mypath") 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 // 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")) 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. // 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 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.... // 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 // $ go test -v
func TestNewApp(t *testing.T) { func TestNewApp(t *testing.T) {
app := newApp() app := newApp()
e := httptest.New(app, t) e := httptest.New(t, app)
// redirects to /admin without basic auth // redirects to /admin without basic auth
e.GET("/").Expect().Status(iris.StatusUnauthorized) 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(){ func main(){
app := iris.Default() app := iris.Default()
cachedHandler := cache.CacheHandler(h, 2 *time.Minute) cachedHandler := cache.WrapHandler(h, 2 *time.Minute)
app.Get("/hello", cachedHandler) app.Get("/hello", cachedHandler)
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))
} }
@ -44,12 +44,12 @@ import (
// //
// All type of responses are cached, templates, json, text, anything. // 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 { func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handler {
return client.NewHandler(bodyHandler, expiration) 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 // first is the context.Handler which you want to cache its result
// the second is, optional, the cache Entry's expiration duration // 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 // 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. // All type of responses are cached, templates, json, text, anything.
// //
// it returns the context.Handler, for more options use the .Cache . // it returns a context.Handler, for more options use the .Cache .
func CacheHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler { func WrapHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
return Cache(bodyHandler, expiration).ServeHTTP 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 ( var (
// NoCache called when a particular handler is not valid for cache. // NoCache called when a particular handler is not valid for cache.
// If this function called inside a handler then the handler is not cached // 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 // the validator for each of the incoming requests and post responses
type Handler struct { 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 bodyHandler context.Handler
// Rule optional validators for pre cache and post cache actions // 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) { func (h *Handler) ServeHTTP(ctx context.Context) {
// check for pre-cache validators, if at least one of them return false // check for pre-cache validators, if at least one of them return false
// for this specific request, then skip the whole cache // 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) { if !h.rule.Claim(ctx) {
h.bodyHandler(ctx) bodyHandler(ctx)
return return
} }
@ -85,7 +102,7 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
// because the net/http doesn't give us // because the net/http doesn't give us
// a built'n way to get the status code & body // a built'n way to get the status code & body
recorder := ctx.Recorder() recorder := ctx.Recorder()
h.bodyHandler(ctx) bodyHandler(ctx)
// now that we have recordered the response, // now that we have recordered the response,
// we are ready to check if that specific response is valid to be stored. // 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 returns the host part of the current url.
Host() string 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) Subdomain() (subdomain string)
// RemoteAddr tries to return the real client's request IP. // RemoteAddr tries to return the real client's request IP.
RemoteAddr() string RemoteAddr() string
@ -400,9 +401,9 @@ type Context interface {
// //
// Returns the number of bytes written and any write error encountered. // Returns the number of bytes written and any write error encountered.
WriteString(body string) (int, error) 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) // 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 // StreamWriter registers the given stream writer for populating
// response body. // response body.
// //
@ -907,7 +908,6 @@ func (ctx *context) NextHandler() Handler {
if ctx.IsStopped() { if ctx.IsStopped() {
return nil return nil
} }
nextIndex := ctx.currentHandlerIndex + 1 nextIndex := ctx.currentHandlerIndex + 1
// check if it has a next middleware // check if it has a next middleware
if nextIndex < len(ctx.handlers) { if nextIndex < len(ctx.handlers) {
@ -1048,13 +1048,21 @@ func (ctx *context) Host() string {
return h 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) { func (ctx *context) Subdomain() (subdomain string) {
host := ctx.Host() host := ctx.Host()
if index := strings.IndexByte(host, '.'); index > 0 { if index := strings.IndexByte(host, '.'); index > 0 {
subdomain = host[0:index] 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 return
} }
@ -1422,7 +1430,7 @@ func (ctx *context) staticCachePassed(modtime time.Time) bool {
// WriteWithExpiration like Write but it sends with an expiration datetime // WriteWithExpiration like Write but it sends with an expiration datetime
// which is managed by the client-side (all major web browsers supports this) // 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) { if ctx.staticCachePassed(modtime) {
return nil 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(contentTypeHeaderKey, cType)
ctx.writer.Header().Set(lastModifiedHeaderKey, modtimeFormatted) ctx.writer.Header().Set(lastModifiedHeaderKey, modtimeFormatted)
ctx.StatusCode(status)
_, err := ctx.writer.Write(bodyContent) _, err := ctx.writer.Write(bodyContent)
return err return err

View File

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

View File

@ -67,7 +67,7 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
// It uses the httputil.NewSingleHostReverseProxy. // It uses the httputil.NewSingleHostReverseProxy.
// //
// Usage: // Usage:
// target,_ := url.Parse("https://mydomain.com") // target, _ := url.Parse("https://mydomain.com")
// proxy := NewProxy("mydomain.com:80", target) // proxy := NewProxy("mydomain.com:80", target)
// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server. // proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server.
func NewProxy(hostAddr string, target *url.URL) *Supervisor { 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 err
} }
} }
return nil return nil
} }
@ -353,21 +354,20 @@ func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) (
// Note: // Note:
// The only difference from package-level `StaticHandler` // The only difference from package-level `StaticHandler`
// is that this `StaticHandler`` receives a request path which // is that this `StaticHandler`` receives a request path which
// is appended to the party's relative path and stripped here, // is appended to the party's relative path and stripped here.
// so `iris.StripPath` is useless and should not being used here.
// //
// Usage: // Usage:
// app := iris.New() // app := iris.New()
// ... // ...
// mySubdomainFsServer := app.Party("mysubdomain.") // 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 */ // /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h) // mySubdomainFsServer.Get("/static", h)
// ... // ...
// //
func (rb *APIBuilder) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler { func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
return StripPrefix(rb.relativePath+reqPath, // Note: this doesn't need to be here but we'll keep it for consistently
StaticHandler(systemPath, showList, enableGzip)) return StaticHandler(systemPath, showList, enableGzip)
} }
// StaticServe serves a directory as web resource // 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) { func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) (*Route, error) {
modtime := time.Now() modtime := time.Now()
h := func(ctx context.Context) { h := func(ctx context.Context) {
if err := ctx.WriteWithExpiration(http.StatusOK, content, cType, modtime); err != nil { if err := ctx.WriteWithExpiration(content, cType, modtime); err != nil {
ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error()) ctx.NotFound()
// ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error())
} }
} }
return rb.registerResourceRoute(reqPath, h) 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 // 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" // 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" // 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. // 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) { func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error) {
paramName := "path" fullpath := joinPath(rb.relativePath, requestPath)
requestPath = joinPath(fullpath, WildcardParam("file"))
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()
}
h := StripPrefix(fullpath, rb.StaticEmbeddedHandler(vdir, assetFn, namesFn))
return rb.registerResourceRoute(requestPath, h) 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.ResponseWriter().Header().Set(lastModifiedHeaderKey, modtime)
ctx.StatusCode(http.StatusOK) ctx.StatusCode(http.StatusOK)
if _, err := ctx.Write(cacheFav); err != nil { 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 // ending in "/index.html" to the same path, without the final
// "index.html". // "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. // Returns the GET *Route.
func (rb *APIBuilder) StaticWeb(reqPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) { func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) {
h := rb.StaticHandler(reqPath, systemPath, false, false, exceptRoutes...)
paramName := "file" 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) { handler := func(ctx context.Context) {
h(ctx) h(ctx)
// re-check the content type here for any case, // re-check the content type here for any case,
@ -614,8 +566,8 @@ func (rb *APIBuilder) StaticWeb(reqPath string, systemPath string, exceptRoutes
} }
} }
} }
requestPath = joinPath(fullpath, WildcardParam(paramName))
return rb.registerResourceRoute(routePath, handler) return rb.registerResourceRoute(requestPath, handler)
} }
// OnErrorCode registers an error http status code // OnErrorCode registers an error http status code

View File

@ -24,6 +24,84 @@ import (
"github.com/kataras/iris/core/errors" "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 // 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 // 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 // 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[contentEncodingHeader] = nil
// headers[contentLength] = nil // headers[contentLength] = nil
} }
// ctx.Application().Log(errMsg)
ctx.StatusCode(prevStatusCode) ctx.StatusCode(prevStatusCode)
return return
} }
@ -741,16 +820,13 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
f, err := fs.Open(name) f, err := fs.Open(name)
if err != nil { if err != nil {
msg, code := toHTTPError(err) return err.Error(), 404
return msg, code
} }
defer f.Close() defer f.Close()
d, err := f.Stat() d, err := f.Stat()
if err != nil { if err != nil {
msg, code := toHTTPError(err) return err.Error(), 404
return msg, code
} }
if redirect { 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 // na to kanw na exei to node to diko tou wildcard parameter name
// kai sto telos na pernei auto, me vasi to *paramname // kai sto telos na pernei auto, me vasi to *paramname
// alla edw mesa 9a ginete register vasi tou last / // 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: loop:
for _, n := range *nodes { for _, n := range *nodes {
@ -97,14 +106,14 @@ loop:
children: Nodes{ children: Nodes{
{ {
s: n.s[i:], s: n.s[i:],
wildcardParamName: n.wildcardParamName, wildcardParamName: wildcardParamName,
paramNames: n.paramNames, paramNames: n.paramNames,
children: n.children, children: n.children,
handlers: n.handlers, handlers: n.handlers,
}, },
{ {
s: path[i:], s: path[i:],
wildcardParamName: n.wildcardParamName, wildcardParamName: wildcardParamName,
paramNames: paramNames, paramNames: paramNames,
handlers: handlers, handlers: handlers,
}, },
@ -117,12 +126,12 @@ loop:
if len(path) < len(n.s) { if len(path) < len(n.s) {
*n = node{ *n = node{
s: n.s[:len(path)], s: n.s[:len(path)],
wildcardParamName: n.wildcardParamName, wildcardParamName: wildcardParamName,
paramNames: paramNames, paramNames: paramNames,
children: Nodes{ children: Nodes{
{ {
s: n.s[len(path):], s: n.s[len(path):],
wildcardParamName: n.wildcardParamName, wildcardParamName: wildcardParamName,
paramNames: n.paramNames, paramNames: n.paramNames,
children: n.children, children: n.children,
handlers: n.handlers, handlers: n.handlers,
@ -144,7 +153,7 @@ loop:
return nil return nil
} }
if len(n.handlers) > 0 { // n.handlers already setted if len(n.handlers) > 0 { // n.handlers already setted
return ErrDublicate return ErrDublicate.Append("for: %s", n.s)
} }
n.paramNames = paramNames n.paramNames = paramNames
n.handlers = handlers n.handlers = handlers
@ -152,15 +161,6 @@ loop:
return 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{ n := &node{
s: path, s: path,
wildcardParamName: wildcardParamName, wildcardParamName: wildcardParamName,

View File

@ -84,20 +84,19 @@ type Party interface {
// //
// Note: // Note:
// The only difference from package-level `StaticHandler` // The only difference from package-level `StaticHandler`
// is that this `StaticHandler`` receives a request path which // is that this `StaticHandler` receives a request path which
// is appended to the party's relative path and stripped here, // is appended to the party's relative path and stripped here.
// so `iris.StripPath` is useless and should not being used here.
// //
// Usage: // Usage:
// app := iris.New() // app := iris.New()
// ... // ...
// mySubdomainFsServer := app.Party("mysubdomain.") // 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 */ // /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h) // 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 // StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions // it's the simpliest form of the Static* functions
@ -114,6 +113,7 @@ type Party interface {
// //
// Returns the GET *Route. // Returns the GET *Route.
StaticContent(requestPath string, cType string, content []byte) (*Route, error) StaticContent(requestPath string, cType string, content []byte) (*Route, error)
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly // 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" // 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" // 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 // ending in "/index.html" to the same path, without the final
// "index.html". // "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. // Returns the GET *Route.
StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) 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 path = cleanPath(path) // maybe unnecessary here but who cares in this moment
defaultName := method + subdomain + path defaultName := method + subdomain + path
formattedPath := formatPath(path) formattedPath := formatPath(path)
route := &Route{ route := &Route{

View File

@ -102,6 +102,12 @@ func (router *Router) Downgraded() bool {
return router.mainHandler != nil && router.requestHandler == nil 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. // WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware // Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS. // 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. // That means that the second wrapper will wrap the first, and so on.
// //
// Before build. // 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() router.mu.Lock()
defer router.mu.Unlock() 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 // ending in "/index.html" to the same path, without the final
// "index.html". // "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. // Returns the GET *Route.
StaticWeb(reqPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
Example code: Example code:
@ -720,7 +720,7 @@ Example code:
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))
} }
More examples can be found here: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server
Middleware Ecosystem Middleware Ecosystem

View File

@ -66,16 +66,9 @@ func DefaultConfiguration() *Configuration {
return &Configuration{URL: "", Debug: false} return &Configuration{URL: "", Debug: false}
} }
// New Prepares and returns a new test framework based on the app // 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 // You can find example on the https://github.com/kataras/iris/tree/master/_examples/intermediate/httptest
// usage: func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpect.Expect {
// 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 {
conf := DefaultConfiguration() conf := DefaultConfiguration()
for _, setter := range setters { for _, setter := range setters {
setter.Set(conf) setter.Set(conf)
@ -103,7 +96,8 @@ func New(app *iris.Application, t *testing.T, setters ...OptionSetter) *httpexpe
return httpexpect.WithConfig(testConfiguration) 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 { func NewInsecure(t *testing.T, setters ...OptionSetter) *httpexpect.Expect {
conf := DefaultConfiguration() conf := DefaultConfiguration()
for _, setter := range setters { 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. // 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. // Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
Version = "7.1.1" Version = "7.2.0"
) )
const ( const (
@ -411,6 +411,23 @@ func (app *Application) SessionManager() (sessions.Sessions, error) {
return app.sessions, nil 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. // ConfigurationReadOnly returns a structure which doesn't allow writing.
func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly { func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
return app.config return app.config

View File

@ -32,13 +32,12 @@ func ToHandler(handler interface{}) context.Handler {
// Cache provides cache capabilities to a route's handler. // Cache provides cache capabilities to a route's handler.
// Usage: // 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 // 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. // Deprecated. Use "github.com/kataras/iris/cache" sub-package which contains the full features instead.
var Cache = cache.CacheHandler var Cache = cache.Handler
// CheckErr is the old `Must`. It panics on errors as expected with // 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. // 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) { func TestBasicAuth(t *testing.T) {
app := buildApp() app := buildApp()
e := httptest.New(app, t) e := httptest.New(t, app)
// redirects to /admin without basic auth // redirects to /admin without basic auth
e.GET("/").Expect().Status(iris.StatusUnauthorized) e.GET("/").Expect().Status(iris.StatusUnauthorized)