mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Update to 7.2.0. Read https://github.com/kataras/iris/blob/master/HISTORY.md#th-15-june-2017--v720
Fix https://github.com/iris-contrib/community-board/issues/12 . Read more: https://github.com/kataras/iris/blob/master/HISTORY.md#th-15-june-2017--v720 Former-commit-id: 398cb69fdc7e5367b147693371287ffb7b912feb
This commit is contained in:
parent
a10e80842f
commit
e0128d204d
|
@ -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 ./...
|
48
HISTORY.md
48
HISTORY.md
|
@ -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).
|
||||||
|
|
|
@ -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".
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
BIN
_examples/beginner/file-server/basic/assets/favicon.ico
Normal file
BIN
_examples/beginner/file-server/basic/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
30
_examples/beginner/file-server/basic/main.go
Normal file
30
_examples/beginner/file-server/basic/main.go
Normal 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 |
9190
_examples/beginner/file-server/embedding-files-into-app/assets/js/jquery-2.1.1.js
vendored
Normal file
9190
_examples/beginner/file-server/embedding-files-into-app/assets/js/jquery-2.1.1.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -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"))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, "/")...)...)
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
window.alert("app.js loaded from \"/");
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
|
@ -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>
|
|
@ -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"))
|
|
||||||
}
|
|
|
@ -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"))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
window.alert("app.js loaded from \"/");
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
|
@ -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>
|
|
@ -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"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
92
_examples/beginner/routing/custom-wrapper/main.go
Normal file
92
_examples/beginner/routing/custom-wrapper/main.go
Normal 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
|
||||||
|
}
|
60
_examples/beginner/routing/custom-wrapper/main_test.go
Normal file
60
_examples/beginner/routing/custom-wrapper/main_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
1
_examples/beginner/routing/custom-wrapper/public/app.js
Normal file
1
_examples/beginner/routing/custom-wrapper/public/app.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
window.alert("app.js loaded from \"/");
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
14
_examples/beginner/routing/custom-wrapper/public/index.html
Normal file
14
_examples/beginner/routing/custom-wrapper/public/index.html
Normal 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>
|
|
@ -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
|
@ -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.
|
|
25
_examples/intermediate/subdomains/www/hosts
Normal file
25
_examples/intermediate/subdomains/www/hosts
Normal 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-
|
65
_examples/intermediate/subdomains/www/main.go
Normal file
65
_examples/intermediate/subdomains/www/main.go
Normal 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)
|
||||||
|
}
|
59
_examples/intermediate/subdomains/www/main_test.go
Normal file
59
_examples/intermediate/subdomains/www/main_test.go
Normal 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
25
cache/cache.go
vendored
|
@ -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
197
cache/cache_test.go
vendored
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
23
cache/client/handler.go
vendored
23
cache/client/handler.go
vendored
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
96
core/router/spa.go
Normal 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
6
doc.go
|
@ -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
|
||||||
|
|
|
@ -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
19
iris.go
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user