mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
Merge remote-tracking branch 'refs/remotes/origin/dev'
This commit is contained in:
commit
ec5a71e0af
25
HISTORY.md
25
HISTORY.md
|
@ -2,6 +2,31 @@
|
||||||
|
|
||||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||||
|
|
||||||
|
## 3.0.0-rc.4 -> 3.0.0-pre.release
|
||||||
|
|
||||||
|
- `context.PostFormValue` -> `context.FormValueString`, old func stays until the next revision
|
||||||
|
- `context.PostFormMulti` -> `context.FormValues` , old func stays until the next revision
|
||||||
|
|
||||||
|
- Added `context.VisitAllCookies(func(key,value string))` to visit all your cookies (because `context.Request.Header.VisitAllCookie` has a bug(I can't fix/pr it because the author is away atm))
|
||||||
|
- Added `context.GetFlashes` to get all available flash messages for a particular request
|
||||||
|
- Fix flash message removed after the first `GetFlash` call in the same request
|
||||||
|
|
||||||
|
**NEW FEATURE**: Built'n support for multi listening servers per iris station, secondary and virtual servers with one-line using the `iris.AddServer` & `iris.Go` to start all servers.
|
||||||
|
|
||||||
|
- `iris.SecondaryListen` -> `iris.AddServer`, old func stays until the next revision
|
||||||
|
- Added `iris.Servers` with this field you can manage your servers very easy
|
||||||
|
- Added `iris.AddServer/iris.ListenTo/iris.Go`, but funcs like `Listen/ListenTLS/ListenUNIX` will stay forever
|
||||||
|
- Added `config.Server.Virtual(bool), config.Server.RedirectTo(string) and config.Server.MaxRequestBodySize(int64)`
|
||||||
|
- Added `iris.Available (channel bool)`
|
||||||
|
- `iris.HTTPServer` -> `iris.Servers.Main()` to get the main server, which is always the last registered server (if more than one used), old field removed
|
||||||
|
- `iris.Config.MaxRequestBodySize` -> `config.Server.MaxRequestBodySize`, old field removed
|
||||||
|
|
||||||
|
**NEW FEATURE**: Build'n support for your API's end-to-end tests
|
||||||
|
|
||||||
|
- Added `tester := iris.Tester(*testing.T)` , look inside: [http_test.go](https://github.com/kataras/iris/blob/master/http_test.go) & [./context_test.go](https://github.com/kataras/iris/blob/master/context_test.go) for `Tester` usage, you can also look inside the [httpexpect's repo](https://github.com/gavv/httpexpect/blob/master/example/iris_test.go) for extended examples with Iris.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-rc.3 -> 3.0.0-rc.4
|
## 3.0.0-rc.3 -> 3.0.0-rc.4
|
||||||
|
|
||||||
**NEW FEATURE**: **Handlebars** template engine support with all Iris' view engine's functions/helpers support, as requested [here](https://github.com/kataras/iris/issues/239):
|
**NEW FEATURE**: **Handlebars** template engine support with all Iris' view engine's functions/helpers support, as requested [here](https://github.com/kataras/iris/issues/239):
|
||||||
|
|
|
@ -139,7 +139,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v3.0.0-rc.4**
|
Current: **v3.0.0-pre.release**
|
||||||
> Iris is an active project
|
> Iris is an active project
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ License can be found [here](LICENSE).
|
||||||
[Travis]: http://travis-ci.org/kataras/iris
|
[Travis]: http://travis-ci.org/kataras/iris
|
||||||
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
||||||
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
||||||
[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.4-blue.svg?style=flat-square
|
[Release Widget]: https://img.shields.io/badge/release-v3.0.0--pre.release-blue.svg?style=flat-square
|
||||||
[Release]: https://github.com/kataras/iris/releases
|
[Release]: https://github.com/kataras/iris/releases
|
||||||
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
||||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
[Chat]: https://kataras.rocket.chat/channel/iris
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import "github.com/imdario/mergo"
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default values for base Iris conf
|
// Default values for base Iris conf
|
||||||
const (
|
const (
|
||||||
DefaultDisablePathCorrection = false
|
DefaultDisablePathCorrection = false
|
||||||
DefaultDisablePathEscape = false
|
DefaultDisablePathEscape = false
|
||||||
DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -54,13 +50,6 @@ type (
|
||||||
// Default is false
|
// Default is false
|
||||||
DisableBanner bool
|
DisableBanner bool
|
||||||
|
|
||||||
// MaxRequestBodySize Maximum request body size.
|
|
||||||
//
|
|
||||||
// The server rejects requests with bodies exceeding this limit.
|
|
||||||
//
|
|
||||||
// By default request body size is 4MB.
|
|
||||||
MaxRequestBodySize int64
|
|
||||||
|
|
||||||
// ProfilePath a the route path, set it to enable http pprof tool
|
// ProfilePath a the route path, set it to enable http pprof tool
|
||||||
// Default is empty, if you set it to a $path, these routes will handled:
|
// Default is empty, if you set it to a $path, these routes will handled:
|
||||||
// $path/cmdline
|
// $path/cmdline
|
||||||
|
@ -103,13 +92,6 @@ type (
|
||||||
// Websocket contains the configs for Websocket's server integration
|
// Websocket contains the configs for Websocket's server integration
|
||||||
Websocket *Websocket
|
Websocket *Websocket
|
||||||
|
|
||||||
// Server contains the configs for the http server
|
|
||||||
// Server configs are the only one which are setted inside base Iris package (from Listen, ListenTLS, ListenUNIX) NO from users
|
|
||||||
//
|
|
||||||
// this field is useful only when you need to READ which is the server's address, certfile & keyfile or unix's mode.
|
|
||||||
//
|
|
||||||
Server Server
|
|
||||||
|
|
||||||
// Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself
|
// Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself
|
||||||
Tester Tester
|
Tester Tester
|
||||||
}
|
}
|
||||||
|
@ -142,14 +124,12 @@ func Default() Iris {
|
||||||
DisablePathCorrection: DefaultDisablePathCorrection,
|
DisablePathCorrection: DefaultDisablePathCorrection,
|
||||||
DisablePathEscape: DefaultDisablePathEscape,
|
DisablePathEscape: DefaultDisablePathEscape,
|
||||||
DisableBanner: false,
|
DisableBanner: false,
|
||||||
MaxRequestBodySize: DefaultMaxRequestBodySize,
|
|
||||||
ProfilePath: "",
|
ProfilePath: "",
|
||||||
Logger: DefaultLogger(),
|
Logger: DefaultLogger(),
|
||||||
Sessions: DefaultSessions(),
|
Sessions: DefaultSessions(),
|
||||||
Render: DefaultRender(),
|
Render: DefaultRender(),
|
||||||
Websocket: DefaultWebsocket(),
|
Websocket: DefaultWebsocket(),
|
||||||
Server: DefaultServer(),
|
Tester: DefaultTester(),
|
||||||
Tester: Tester{Debug: false},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,17 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
"github.com/kataras/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default values for base Server conf
|
||||||
const (
|
const (
|
||||||
// DefaultServerHostname returns the default hostname which is 127.0.0.1
|
// DefaultServerHostname returns the default hostname which is 127.0.0.1
|
||||||
DefaultServerHostname = "127.0.0.1"
|
DefaultServerHostname = "127.0.0.1"
|
||||||
// DefaultServerPort returns the default port which is 8080
|
// DefaultServerPort returns the default port which is 8080
|
||||||
DefaultServerPort = 8080
|
DefaultServerPort = 8080
|
||||||
|
// DefaultMaxRequestBodySize is 4MB
|
||||||
|
DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -30,6 +34,12 @@ type Server struct {
|
||||||
KeyFile string
|
KeyFile string
|
||||||
// Mode this is for unix only
|
// Mode this is for unix only
|
||||||
Mode os.FileMode
|
Mode os.FileMode
|
||||||
|
// MaxRequestBodySize Maximum request body size.
|
||||||
|
//
|
||||||
|
// The server rejects requests with bodies exceeding this limit.
|
||||||
|
//
|
||||||
|
// By default request body size is 4MB.
|
||||||
|
MaxRequestBodySize int64
|
||||||
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
|
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
|
||||||
//
|
//
|
||||||
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
|
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
|
||||||
|
@ -37,11 +47,14 @@ type Server struct {
|
||||||
//
|
//
|
||||||
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
|
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
|
||||||
RedirectTo string
|
RedirectTo string
|
||||||
|
// Virtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
|
||||||
|
Virtual bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultServer returns the default configs for the server
|
// DefaultServer returns the default configs for the server
|
||||||
func DefaultServer() Server {
|
func DefaultServer() Server {
|
||||||
return Server{ListeningAddr: DefaultServerAddr}
|
return Server{ListeningAddr: DefaultServerAddr,
|
||||||
|
MaxRequestBodySize: DefaultMaxRequestBodySize}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges the default with the given config and returns the result
|
// Merge merges the default with the given config and returns the result
|
||||||
|
@ -57,3 +70,12 @@ func (c Server) Merge(cfg []Server) (config Server) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MergeSingle merges the default with the given config and returns the result
|
||||||
|
func (c Server) MergeSingle(cfg Server) (config Server) {
|
||||||
|
|
||||||
|
config = cfg
|
||||||
|
mergo.Merge(&config, c)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ type (
|
||||||
Redis struct {
|
Redis struct {
|
||||||
// Network "tcp"
|
// Network "tcp"
|
||||||
Network string
|
Network string
|
||||||
// Addr "127.0.01:6379"
|
// Addr "127.0.0.1:6379"
|
||||||
Addr string
|
Addr string
|
||||||
// Password string .If no password then no 'AUTH'. Default ""
|
// Password string .If no password then no 'AUTH'. Default ""
|
||||||
Password string
|
Password string
|
||||||
|
|
|
@ -2,5 +2,13 @@ package config
|
||||||
|
|
||||||
// Tester configuration
|
// Tester configuration
|
||||||
type Tester struct {
|
type Tester struct {
|
||||||
Debug bool
|
ListeningAddr string
|
||||||
|
ExplicitURL bool
|
||||||
|
Debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTester returns the default configuration for a tester
|
||||||
|
// the ListeningAddr is used as virtual only when no running server is founded
|
||||||
|
func DefaultTester() Tester {
|
||||||
|
return Tester{ListeningAddr: "iris-go.com:1993", ExplicitURL: false, Debug: false}
|
||||||
}
|
}
|
||||||
|
|
139
context.go
139
context.go
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Context.go Implements: ./context/context.go ,
|
Context.go Implements: ./context/context.go
|
||||||
files: context_renderer.go, context_storage.go, context_request.go, context_response.go
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package iris
|
package iris
|
||||||
|
@ -56,6 +55,9 @@ const (
|
||||||
stopExecutionPosition = 255
|
stopExecutionPosition = 255
|
||||||
// used inside GetFlash to store the lifetime request flash messages
|
// used inside GetFlash to store the lifetime request flash messages
|
||||||
flashMessagesStoreContextKey = "_iris_flash_messages_"
|
flashMessagesStoreContextKey = "_iris_flash_messages_"
|
||||||
|
flashMessageCookiePrefix = "_iris_flash_message_"
|
||||||
|
cookieHeaderID = "Cookie: "
|
||||||
|
cookieHeaderIDLen = len(cookieHeaderID)
|
||||||
)
|
)
|
||||||
|
|
||||||
// this pool is used everywhere needed in the iris for example inside party-> Static
|
// this pool is used everywhere needed in the iris for example inside party-> Static
|
||||||
|
@ -218,7 +220,7 @@ func (ctx *Context) HostString() string {
|
||||||
func (ctx *Context) VirtualHostname() string {
|
func (ctx *Context) VirtualHostname() string {
|
||||||
realhost := ctx.HostString()
|
realhost := ctx.HostString()
|
||||||
hostname := realhost
|
hostname := realhost
|
||||||
virtualhost := ctx.framework.HTTPServer.VirtualHostname()
|
virtualhost := ctx.framework.Servers.Main().VirtualHostname()
|
||||||
|
|
||||||
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
|
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
|
||||||
hostname = hostname[0:portIdx]
|
hostname = hostname[0:portIdx]
|
||||||
|
@ -284,13 +286,13 @@ func (ctx *Context) RequestHeader(k string) string {
|
||||||
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
|
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostFormValue returns a single value from post request's data
|
// FormValueString returns a single value, as string, from post request's data
|
||||||
func (ctx *Context) PostFormValue(name string) string {
|
func (ctx *Context) FormValueString(name string) string {
|
||||||
return string(ctx.FormValue(name))
|
return string(ctx.FormValue(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostFormMulti returns a slice of string from post request's data
|
// FormValues returns a slice of string from post request's data
|
||||||
func (ctx *Context) PostFormMulti(name string) []string {
|
func (ctx *Context) FormValues(name string) []string {
|
||||||
arrBytes := ctx.PostArgs().PeekMulti(name)
|
arrBytes := ctx.PostArgs().PeekMulti(name)
|
||||||
arrStr := make([]string, len(arrBytes))
|
arrStr := make([]string, len(arrBytes))
|
||||||
for i, v := range arrBytes {
|
for i, v := range arrBytes {
|
||||||
|
@ -314,7 +316,7 @@ func (ctx *Context) Subdomain() (subdomain string) {
|
||||||
// use it only for special cases, when the default behavior doesn't suits you.
|
// use it only for special cases, when the default behavior doesn't suits you.
|
||||||
//
|
//
|
||||||
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
||||||
/* Credits to Manish Singh @kryptodev for URLEncode */
|
/* Credits to Manish Singh @kryptodev for URLEncode by post issue share code */
|
||||||
func URLEncode(path string) string {
|
func URLEncode(path string) string {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return ""
|
return ""
|
||||||
|
@ -419,7 +421,7 @@ func (ctx *Context) SetHeader(k string, v string) {
|
||||||
// first parameter is the url to redirect
|
// first parameter is the url to redirect
|
||||||
// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
|
// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
|
||||||
func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
|
func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
|
||||||
httpStatus := StatusFound // temporary redirect
|
httpStatus := StatusFound // a 'temporary-redirect-like' wich works better than for our purpose
|
||||||
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
|
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
|
||||||
httpStatus = statusHeader[0]
|
httpStatus = statusHeader[0]
|
||||||
}
|
}
|
||||||
|
@ -480,7 +482,11 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{
|
||||||
|
|
||||||
// Render same as .RenderWithStatus but with status to iris.StatusOK (200)
|
// Render same as .RenderWithStatus but with status to iris.StatusOK (200)
|
||||||
func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
|
func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
|
||||||
return ctx.RenderWithStatus(StatusOK, name, binding, layout...)
|
errCode := ctx.RequestCtx.Response.StatusCode()
|
||||||
|
if errCode <= 0 {
|
||||||
|
errCode = StatusOK
|
||||||
|
}
|
||||||
|
return ctx.RenderWithStatus(errCode, name, binding, layout...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
|
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
|
||||||
|
@ -687,6 +693,28 @@ func (ctx *Context) Set(key string, value interface{}) {
|
||||||
ctx.RequestCtx.SetUserValue(key, value)
|
ctx.RequestCtx.SetUserValue(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
|
||||||
|
//
|
||||||
|
// Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment.
|
||||||
|
// This is the reason which this function exists and should be used instead of fasthttp's built'n.
|
||||||
|
func (ctx *Context) VisitAllCookies(visitor func(key string, value string)) {
|
||||||
|
// strange bug, this doesnt works also: cookieHeaderContent := ctx.Request.Header.Peek("Cookie")/User-Agent tested also
|
||||||
|
headerbody := string(ctx.Request.Header.Header())
|
||||||
|
headerlines := strings.Split(headerbody, "\n")
|
||||||
|
for _, s := range headerlines {
|
||||||
|
if len(s) > cookieHeaderIDLen {
|
||||||
|
if s[0:cookieHeaderIDLen] == cookieHeaderID {
|
||||||
|
contents := s[cookieHeaderIDLen:]
|
||||||
|
values := strings.Split(contents, "; ")
|
||||||
|
for _, s := range values {
|
||||||
|
keyvalue := strings.SplitN(s, "=", 2)
|
||||||
|
visitor(keyvalue[0], keyvalue[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetCookie returns cookie's value by it's name
|
// GetCookie returns cookie's value by it's name
|
||||||
// returns empty string if nothing was found
|
// returns empty string if nothing was found
|
||||||
func (ctx *Context) GetCookie(name string) (val string) {
|
func (ctx *Context) GetCookie(name string) (val string) {
|
||||||
|
@ -719,19 +747,64 @@ func (ctx *Context) RemoveCookie(name string) {
|
||||||
ctx.RequestCtx.Response.Header.DelClientCookie(name)
|
ctx.RequestCtx.Response.Header.DelClientCookie(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFlashes returns all the flash messages for available for this request
|
||||||
|
func (ctx *Context) GetFlashes() map[string]string {
|
||||||
|
// if already taken at least one time, this will be filled
|
||||||
|
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
|
||||||
|
if m, isMap := messages.(map[string]string); isMap {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flashMessageFound := false
|
||||||
|
// else first time, get all flash cookie keys(the prefix will tell us which is a flash message), and after get all one-by-one using the GetFlash.
|
||||||
|
flashMessageCookiePrefixLen := len(flashMessageCookiePrefix)
|
||||||
|
ctx.VisitAllCookies(func(key string, value string) {
|
||||||
|
if len(key) > flashMessageCookiePrefixLen {
|
||||||
|
if key[0:flashMessageCookiePrefixLen] == flashMessageCookiePrefix {
|
||||||
|
unprefixedKey := key[flashMessageCookiePrefixLen:]
|
||||||
|
_, err := ctx.GetFlash(unprefixedKey) // this func will add to the list (flashMessagesStoreContextKey) also
|
||||||
|
if err == nil {
|
||||||
|
flashMessageFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// if we found at least one flash message then re-execute this function to return the list
|
||||||
|
if flashMessageFound {
|
||||||
|
return ctx.GetFlashes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) decodeFlashCookie(key string) (string, string) {
|
||||||
|
cookieKey := flashMessageCookiePrefix + key
|
||||||
|
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(cookieKey))
|
||||||
|
|
||||||
|
if cookieValue != "" {
|
||||||
|
v, e := base64.URLEncoding.DecodeString(cookieValue)
|
||||||
|
if e == nil {
|
||||||
|
return cookieKey, string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
// GetFlash get a flash message by it's key
|
// GetFlash get a flash message by it's key
|
||||||
// returns the value as string and an error
|
// returns the value as string and an error
|
||||||
//
|
//
|
||||||
// if the cookie doesn't exists the string is empty and the error is filled
|
// if the cookie doesn't exists the string is empty and the error is filled
|
||||||
// after the request's life the value is removed
|
// after the request's life the value is removed
|
||||||
func (ctx *Context) GetFlash(key string) (value string, err error) {
|
func (ctx *Context) GetFlash(key string) (string, error) {
|
||||||
|
|
||||||
// first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie
|
// first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie
|
||||||
storeExists := false
|
storeExists := false
|
||||||
|
|
||||||
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
|
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
|
||||||
m, isMap := messages.(map[string]string)
|
m, isMap := messages.(map[string]string)
|
||||||
if !isMap {
|
if !isMap {
|
||||||
return "", fmt.Errorf("Messages request's store is not a map[string]string. This suppose will never happen, please report this bug.")
|
return "", fmt.Errorf("Flash store is not a map[string]string. This suppose will never happen, please report this bug.")
|
||||||
}
|
}
|
||||||
storeExists = true // in order to skip the check later
|
storeExists = true // in order to skip the check later
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
|
@ -741,38 +814,32 @@ func (ctx *Context) GetFlash(key string) (value string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key))
|
cookieKey, cookieValue := ctx.decodeFlashCookie(key)
|
||||||
|
|
||||||
if cookieValue == "" {
|
if cookieValue == "" {
|
||||||
err = errFlashNotFound.Return()
|
return "", errFlashNotFound.Return()
|
||||||
} else {
|
|
||||||
v, e := base64.URLEncoding.DecodeString(cookieValue)
|
|
||||||
if e != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
value = string(v)
|
|
||||||
// store this flash message to the lifetime request's local storage,
|
|
||||||
// I choose this method because no need to store it if not used at all
|
|
||||||
if storeExists {
|
|
||||||
ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = value
|
|
||||||
} else {
|
|
||||||
flashStoreMap := make(map[string]string)
|
|
||||||
flashStoreMap[key] = value
|
|
||||||
ctx.Set(flashMessagesStoreContextKey, flashStoreMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
//remove the real cookie, no need to have that, we stored it on lifetime request
|
|
||||||
ctx.RemoveCookie(key)
|
|
||||||
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
|
|
||||||
}
|
}
|
||||||
return
|
// store this flash message to the lifetime request's local storage,
|
||||||
|
// I choose this method because no need to store it if not used at all
|
||||||
|
if storeExists {
|
||||||
|
ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = cookieValue
|
||||||
|
} else {
|
||||||
|
flashStoreMap := make(map[string]string)
|
||||||
|
flashStoreMap[key] = cookieValue
|
||||||
|
ctx.Set(flashMessagesStoreContextKey, flashStoreMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove the real cookie, no need to have that, we stored it on lifetime request
|
||||||
|
ctx.RemoveCookie(cookieKey)
|
||||||
|
return cookieValue, nil
|
||||||
|
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
|
// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
|
||||||
// the value will be available on the NEXT request
|
// the value will be available on the NEXT request
|
||||||
func (ctx *Context) SetFlash(key string, value string) {
|
func (ctx *Context) SetFlash(key string, value string) {
|
||||||
c := fasthttp.AcquireCookie()
|
c := fasthttp.AcquireCookie()
|
||||||
c.SetKey(key)
|
c.SetKey(flashMessageCookiePrefix + key)
|
||||||
c.SetValue(base64.URLEncoding.EncodeToString([]byte(value)))
|
c.SetValue(base64.URLEncoding.EncodeToString([]byte(value)))
|
||||||
c.SetPath("/")
|
c.SetPath("/")
|
||||||
c.SetHTTPOnly(true)
|
c.SetHTTPOnly(true)
|
||||||
|
|
|
@ -14,6 +14,10 @@ type (
|
||||||
// IContext the interface for the iris/context
|
// IContext the interface for the iris/context
|
||||||
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
|
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
|
||||||
IContext interface {
|
IContext interface {
|
||||||
|
// deprecated Start
|
||||||
|
PostFormValue(string) string
|
||||||
|
PostFormMulti(string) []string
|
||||||
|
// deprecated End
|
||||||
Param(string) string
|
Param(string) string
|
||||||
ParamInt(string) (int, error)
|
ParamInt(string) (int, error)
|
||||||
ParamInt64(string) (int64, error)
|
ParamInt64(string) (int64, error)
|
||||||
|
@ -29,8 +33,8 @@ type (
|
||||||
RequestIP() string
|
RequestIP() string
|
||||||
RemoteAddr() string
|
RemoteAddr() string
|
||||||
RequestHeader(k string) string
|
RequestHeader(k string) string
|
||||||
PostFormValue(string) string
|
FormValueString(string) string
|
||||||
PostFormMulti(string) []string
|
FormValues(string) []string
|
||||||
SetStatusCode(int)
|
SetStatusCode(int)
|
||||||
SetContentType(string)
|
SetContentType(string)
|
||||||
SetHeader(string, string)
|
SetHeader(string, string)
|
||||||
|
@ -66,9 +70,11 @@ type (
|
||||||
GetString(string) string
|
GetString(string) string
|
||||||
GetInt(string) int
|
GetInt(string) int
|
||||||
Set(string, interface{})
|
Set(string, interface{})
|
||||||
|
VisitAllCookies(func(string, string))
|
||||||
SetCookie(*fasthttp.Cookie)
|
SetCookie(*fasthttp.Cookie)
|
||||||
SetCookieKV(string, string)
|
SetCookieKV(string, string)
|
||||||
RemoveCookie(string)
|
RemoveCookie(string)
|
||||||
|
GetFlashes() map[string]string
|
||||||
GetFlash(string) (string, error)
|
GetFlash(string) (string, error)
|
||||||
SetFlash(string, string)
|
SetFlash(string, string)
|
||||||
Session() store.IStore
|
Session() store.IStore
|
||||||
|
|
613
context_test.go
Normal file
613
context_test.go
Normal file
|
@ -0,0 +1,613 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
/*
|
||||||
|
The most part of the context covered,
|
||||||
|
the other part contains serving static methods,
|
||||||
|
find remote ip, GetInt and the view engine rendering(templates)
|
||||||
|
I am not waiting unexpected behaviors from the rest of the funcs,
|
||||||
|
so that's all with context's tests.
|
||||||
|
|
||||||
|
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextReset(t *testing.T) {
|
||||||
|
var context Context
|
||||||
|
context.Params = PathParameters{PathParameter{Key: "testkey", Value: "testvalue"}}
|
||||||
|
context.Reset(nil)
|
||||||
|
if len(context.Params) > 0 {
|
||||||
|
t.Fatalf("Expecting to have %d params but got: %d", 0, len(context.Params))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextClone(t *testing.T) {
|
||||||
|
var context Context
|
||||||
|
context.Params = PathParameters{
|
||||||
|
PathParameter{Key: "testkey", Value: "testvalue"},
|
||||||
|
PathParameter{Key: "testkey2", Value: "testvalue2"},
|
||||||
|
}
|
||||||
|
c := context.Clone()
|
||||||
|
if v := c.Param("testkey"); v != context.Param("testkey") {
|
||||||
|
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey"), v)
|
||||||
|
}
|
||||||
|
if v := c.Param("testkey2"); v != context.Param("testkey2") {
|
||||||
|
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey2"), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextDoNextStop(t *testing.T) {
|
||||||
|
var context Context
|
||||||
|
ok := false
|
||||||
|
afterStop := false
|
||||||
|
context.middleware = Middleware{HandlerFunc(func(*Context) {
|
||||||
|
ok = true
|
||||||
|
}), HandlerFunc(func(*Context) {
|
||||||
|
ok = true
|
||||||
|
}), HandlerFunc(func(*Context) {
|
||||||
|
// this will never execute
|
||||||
|
afterStop = true
|
||||||
|
})}
|
||||||
|
context.Do()
|
||||||
|
if context.pos != 0 {
|
||||||
|
t.Fatalf("Expecting position 0 for context's middleware but we got: %d", context.pos)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected behavior, first context's middleware didn't executed")
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
|
||||||
|
context.Next()
|
||||||
|
|
||||||
|
if int(context.pos) != 1 {
|
||||||
|
t.Fatalf("Expecting to have position %d but we got: %d", 1, context.pos)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Next context's middleware didn't executed")
|
||||||
|
}
|
||||||
|
|
||||||
|
context.StopExecution()
|
||||||
|
if context.pos != stopExecutionPosition {
|
||||||
|
t.Fatalf("Context's StopExecution didn't worked, we expected to have position %d but we got %d", stopExecutionPosition, context.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !context.IsStopped() {
|
||||||
|
t.Fatalf("Should be stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Next()
|
||||||
|
|
||||||
|
if afterStop {
|
||||||
|
t.Fatalf("We stopped the execution but the next handler was executed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextParams(t *testing.T) {
|
||||||
|
var context Context
|
||||||
|
params := PathParameters{
|
||||||
|
PathParameter{Key: "testkey", Value: "testvalue"},
|
||||||
|
PathParameter{Key: "testkey2", Value: "testvalue2"},
|
||||||
|
PathParameter{Key: "id", Value: "3"},
|
||||||
|
PathParameter{Key: "bigint", Value: "548921854390354"},
|
||||||
|
}
|
||||||
|
context.Params = params
|
||||||
|
|
||||||
|
if v := context.Param(params[0].Key); v != params[0].Value {
|
||||||
|
t.Fatalf("Expecting parameter value to be %s but we got %s", params[0].Value, context.Param("testkey"))
|
||||||
|
}
|
||||||
|
if v := context.Param(params[1].Key); v != params[1].Value {
|
||||||
|
t.Fatalf("Expecting parameter value to be %s but we got %s", params[1].Value, context.Param("testkey2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(context.Params) != len(params) {
|
||||||
|
t.Fatalf("Expecting to have %d parameters but we got %d", len(params), len(context.Params))
|
||||||
|
}
|
||||||
|
|
||||||
|
if vi, err := context.ParamInt(params[2].Key); err != nil {
|
||||||
|
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
|
||||||
|
} else if vi != 3 {
|
||||||
|
t.Fatalf("Expecting to receive %d but we got %d", 3, vi)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vi, err := context.ParamInt64(params[3].Key); err != nil {
|
||||||
|
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
|
||||||
|
} else if vi != 548921854390354 {
|
||||||
|
t.Fatalf("Expecting to receive %d but we got %d", 548921854390354, vi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// end-to-end test now, note that we will not test the whole mux here, this happens on http_test.go
|
||||||
|
|
||||||
|
initDefault()
|
||||||
|
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
|
||||||
|
Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *Context) {
|
||||||
|
paramsStr := ctx.Params.String()
|
||||||
|
ctx.Write(paramsStr)
|
||||||
|
})
|
||||||
|
|
||||||
|
Tester(t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(StatusOK).Body().Equal(expectedParamsStr)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextURLParams(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
passedParams := map[string]string{"param1": "value1", "param2": "value2"}
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
params := ctx.URLParams()
|
||||||
|
ctx.JSON(StatusOK, params)
|
||||||
|
})
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.GET("/").WithQueryObject(passedParams).Expect().Status(StatusOK).JSON().Equal(passedParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hoststring returns the full host, will return the HOST:IP
|
||||||
|
func TestContextHostString(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
Config.Tester.ListeningAddr = "localhost:8080"
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
ctx.Write(ctx.HostString())
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/wrong", func(ctx *Context) {
|
||||||
|
ctx.Write(ctx.HostString() + "w")
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
e.GET("/").Expect().Status(StatusOK).Body().Equal(Config.Tester.ListeningAddr)
|
||||||
|
e.GET("/wrong").Expect().Body().NotEqual(Config.Tester.ListeningAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualHostname returns the hostname only,
|
||||||
|
// if the host starts with 127.0.0.1 or localhost it gives the registered hostname part of the listening addr
|
||||||
|
func TestContextVirtualHostName(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
vhost := "mycustomvirtualname.com"
|
||||||
|
Config.Tester.ListeningAddr = vhost + ":8080"
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
ctx.Write(ctx.VirtualHostname())
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/wrong", func(ctx *Context) {
|
||||||
|
ctx.Write(ctx.VirtualHostname() + "w")
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
e.GET("/").Expect().Status(StatusOK).Body().Equal(vhost)
|
||||||
|
e.GET("/wrong").Expect().Body().NotEqual(vhost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextFormValueString(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
var k, v string
|
||||||
|
k = "postkey"
|
||||||
|
v = "postvalue"
|
||||||
|
Post("/", func(ctx *Context) {
|
||||||
|
ctx.Write(k + "=" + ctx.FormValueString(k))
|
||||||
|
})
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.POST("/").WithFormField(k, v).Expect().Status(StatusOK).Body().Equal(k + "=" + v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextSubdomain(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
Config.Tester.ListeningAddr = "mydomain.com:9999"
|
||||||
|
//Config.Tester.ExplicitURL = true
|
||||||
|
Party("mysubdomain.").Get("/mypath", func(ctx *Context) {
|
||||||
|
ctx.Write(ctx.Subdomain())
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
e.GET("/").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound)
|
||||||
|
e.GET("/mypath").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusOK).Body().Equal("mysubdomain")
|
||||||
|
|
||||||
|
//e.GET("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound)
|
||||||
|
//e.GET("http://mysubdomain.mydomain.com:9999/mypath").Expect().Status(StatusOK).Body().Equal("mysubdomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
type testBinderData struct {
|
||||||
|
Username string
|
||||||
|
Mail string
|
||||||
|
Data []string `form:"mydata" json:"mydata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testBinderXMLData struct {
|
||||||
|
XMLName xml.Name `xml:"info"`
|
||||||
|
FirstAttr string `xml:"first,attr"`
|
||||||
|
SecondAttr string `xml:"second,attr"`
|
||||||
|
Name string `xml:"name",json:"name"`
|
||||||
|
Birth string `xml:"birth",json:"birth"`
|
||||||
|
Stars int `xml:"stars",json:"stars"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextReadForm(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
Post("/form", func(ctx *Context) {
|
||||||
|
obj := testBinderData{}
|
||||||
|
err := ctx.ReadForm(&obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error when parsing the FORM: %s", err.Error())
|
||||||
|
}
|
||||||
|
ctx.JSON(StatusOK, obj)
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"},
|
||||||
|
"[1]": []string{"mydata2"}}}
|
||||||
|
|
||||||
|
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
|
||||||
|
|
||||||
|
e.POST("/form").WithForm(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextReadJSON(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
Post("/json", func(ctx *Context) {
|
||||||
|
obj := testBinderData{}
|
||||||
|
err := ctx.ReadJSON(&obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error when parsing the JSON body: %s", err.Error())
|
||||||
|
}
|
||||||
|
ctx.JSON(StatusOK, obj)
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}}
|
||||||
|
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
|
||||||
|
|
||||||
|
e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextReadXML(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
Post("/xml", func(ctx *Context) {
|
||||||
|
obj := testBinderXMLData{}
|
||||||
|
err := ctx.ReadXML(&obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error when parsing the XML body: %s", err.Error())
|
||||||
|
}
|
||||||
|
ctx.XML(StatusOK, obj)
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
expectedObj := testBinderXMLData{
|
||||||
|
XMLName: xml.Name{Local: "info", Space: "info"},
|
||||||
|
FirstAttr: "this is the first attr",
|
||||||
|
SecondAttr: "this is the second attr",
|
||||||
|
Name: "Iris web framework",
|
||||||
|
Birth: "13 March 2016",
|
||||||
|
Stars: 4064,
|
||||||
|
}
|
||||||
|
// so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting
|
||||||
|
expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `"><name>` + expectedObj.Name + `</name><birth>` + expectedObj.Birth + `</birth><stars>` + strconv.Itoa(expectedObj.Stars) + `</stars></info>`
|
||||||
|
e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestContextRedirectTo tests the named route redirect action
|
||||||
|
func TestContextRedirectTo(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
h := func(ctx *Context) { ctx.Write(ctx.PathString()) }
|
||||||
|
Get("/mypath", h)("my-path")
|
||||||
|
Get("/mypostpath", h)("my-post-path")
|
||||||
|
Get("mypath/with/params/:param1/:param2", func(ctx *Context) {
|
||||||
|
if len(ctx.Params) != 2 {
|
||||||
|
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", len(ctx.Params))
|
||||||
|
}
|
||||||
|
ctx.Write(ctx.PathString())
|
||||||
|
})("my-path-with-params")
|
||||||
|
|
||||||
|
Get("/redirect/to/:routeName/*anyparams", func(ctx *Context) {
|
||||||
|
routeName := ctx.Param("routeName")
|
||||||
|
var args []interface{}
|
||||||
|
anyparams := ctx.Param("anyparams")
|
||||||
|
if anyparams != "" && anyparams != "/" {
|
||||||
|
params := strings.Split(anyparams[1:], "/") // firstparam/secondparam
|
||||||
|
for _, s := range params {
|
||||||
|
args = append(args, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//println("Redirecting to: " + routeName + " with path: " + Path(routeName, args...))
|
||||||
|
ctx.RedirectTo(routeName, args...)
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.GET("/redirect/to/my-path/").Expect().Status(StatusOK).Body().Equal("/mypath")
|
||||||
|
e.GET("/redirect/to/my-post-path/").Expect().Status(StatusOK).Body().Equal("/mypostpath")
|
||||||
|
e.GET("/redirect/to/my-path-with-params/firstparam/secondparam").Expect().Status(StatusOK).Body().Equal("/mypath/with/params/firstparam/secondparam")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextUserValues(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
testCustomObjUserValue := struct{ Name string }{Name: "a name"}
|
||||||
|
values := map[string]interface{}{"key1": "value1", "key2": "value2", "key3": 3, "key4": testCustomObjUserValue, "key5": map[string]string{"key": "value"}}
|
||||||
|
|
||||||
|
Get("/test", func(ctx *Context) {
|
||||||
|
|
||||||
|
for k, v := range values {
|
||||||
|
ctx.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, func(ctx *Context) {
|
||||||
|
for k, v := range values {
|
||||||
|
userValue := ctx.Get(k)
|
||||||
|
if userValue != v {
|
||||||
|
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, isMap := userValue.(map[string]string); isMap {
|
||||||
|
if m["key"] != v.(map[string]string)["key"] {
|
||||||
|
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v.(map[string]string)["key"], m["key"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if userValue != v {
|
||||||
|
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.GET("/test").Expect().Status(StatusOK)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextFlashMessages(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
firstKey := "name"
|
||||||
|
lastKey := "package"
|
||||||
|
|
||||||
|
values := PathParameters{PathParameter{Key: firstKey, Value: "kataras"}, PathParameter{Key: lastKey, Value: "iris"}}
|
||||||
|
jsonExpected := map[string]string{firstKey: "kataras", lastKey: "iris"}
|
||||||
|
// set the flashes, the cookies are filled
|
||||||
|
Put("/set", func(ctx *Context) {
|
||||||
|
for _, v := range values {
|
||||||
|
ctx.SetFlash(v.Key, v.Value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// get the first flash, the next should be avaiable to the next requess
|
||||||
|
Get("/get_first_flash", func(ctx *Context) {
|
||||||
|
for _, v := range values {
|
||||||
|
val, _ := ctx.GetFlash(v.Key)
|
||||||
|
ctx.JSON(StatusOK, map[string]string{v.Key: val})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// just an empty handler to test if the flashes should remeain to the next if GetFlash/GetFlashes used
|
||||||
|
Get("/get_no_getflash", func(ctx *Context) {
|
||||||
|
})
|
||||||
|
|
||||||
|
// get the last flash, the next should be avaiable to the next requess
|
||||||
|
Get("/get_last_flash", func(ctx *Context) {
|
||||||
|
for i, v := range values {
|
||||||
|
if i == len(values)-1 {
|
||||||
|
val, _ := ctx.GetFlash(v.Key)
|
||||||
|
ctx.JSON(StatusOK, map[string]string{v.Key: val})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/get_zero_flashes", func(ctx *Context) {
|
||||||
|
ctx.JSON(StatusOK, ctx.GetFlashes()) // should return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// we use the GetFlash to get the flash messages, the messages and the cookies should be empty after that
|
||||||
|
Get("/get_flash", func(ctx *Context) {
|
||||||
|
kv := make(map[string]string)
|
||||||
|
for _, v := range values {
|
||||||
|
val, err := ctx.GetFlash(v.Key)
|
||||||
|
if err == nil {
|
||||||
|
kv[v.Key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.JSON(StatusOK, kv)
|
||||||
|
}, func(ctx *Context) {
|
||||||
|
// at the same request, flashes should be available
|
||||||
|
if len(ctx.GetFlashes()) == 0 {
|
||||||
|
t.Fatalf("Flashes should be remeain to the whole request lifetime")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/get_flashes", func(ctx *Context) {
|
||||||
|
// one time one handler, using GetFlashes
|
||||||
|
kv := make(map[string]string)
|
||||||
|
flashes := ctx.GetFlashes()
|
||||||
|
//second time on the same handler, using the GetFlash
|
||||||
|
for k := range flashes {
|
||||||
|
kv[k], _ = ctx.GetFlash(k)
|
||||||
|
}
|
||||||
|
if len(flashes) != len(kv) {
|
||||||
|
ctx.SetStatusCode(StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Next()
|
||||||
|
|
||||||
|
}, func(ctx *Context) {
|
||||||
|
// third time on a next handler
|
||||||
|
// test the if next handler has access to them(must) because flash are request lifetime now.
|
||||||
|
// print them to the client for test the response also
|
||||||
|
ctx.JSON(StatusOK, ctx.GetFlashes())
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
|
||||||
|
e.GET("/get_first_flash").Expect().Status(StatusOK).JSON().Object().ContainsKey(firstKey).NotContainsKey(lastKey)
|
||||||
|
// just a request which does not use the flash message, so flash messages should be available on the next request
|
||||||
|
e.GET("/get_no_getflash").Expect().Status(StatusOK)
|
||||||
|
e.GET("/get_last_flash").Expect().Status(StatusOK).JSON().Object().ContainsKey(lastKey).NotContainsKey(firstKey)
|
||||||
|
g := e.GET("/get_zero_flashes").Expect().Status(StatusOK)
|
||||||
|
g.JSON().Null()
|
||||||
|
g.Cookies().Empty()
|
||||||
|
// set the magain
|
||||||
|
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
|
||||||
|
// get them again using GetFlash
|
||||||
|
e.GET("/get_flash").Expect().Status(StatusOK).JSON().Object().Equal(jsonExpected)
|
||||||
|
// this should be empty again
|
||||||
|
g = e.GET("/get_zero_flashes").Expect().Status(StatusOK)
|
||||||
|
g.JSON().Null()
|
||||||
|
g.Cookies().Empty()
|
||||||
|
//set them again
|
||||||
|
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
|
||||||
|
// get them again using GetFlashes
|
||||||
|
e.GET("/get_flashes").Expect().Status(StatusOK).JSON().Object().Equal(jsonExpected)
|
||||||
|
// this should be empty again
|
||||||
|
g = e.GET("/get_zero_flashes").Expect().Status(StatusOK)
|
||||||
|
g.JSON().Null()
|
||||||
|
g.Cookies().Empty()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextSessions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
values := map[string]interface{}{
|
||||||
|
"Name": "iris",
|
||||||
|
"Months": "4",
|
||||||
|
"Secret": "dsads£2132215£%%Ssdsa",
|
||||||
|
}
|
||||||
|
|
||||||
|
initDefault()
|
||||||
|
Config.Sessions.Cookie = "mycustomsessionid"
|
||||||
|
|
||||||
|
writeValues := func(ctx *Context) {
|
||||||
|
sessValues := ctx.Session().GetAll()
|
||||||
|
ctx.JSON(StatusOK, sessValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testEnableSubdomain {
|
||||||
|
Party(testSubdomain+".").Get("/get", func(ctx *Context) {
|
||||||
|
writeValues(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Post("set", func(ctx *Context) {
|
||||||
|
vals := make(map[string]interface{}, 0)
|
||||||
|
if err := ctx.ReadJSON(&vals); err != nil {
|
||||||
|
t.Fatalf("Cannot readjson. Trace %s", err.Error())
|
||||||
|
}
|
||||||
|
for k, v := range vals {
|
||||||
|
ctx.Session().Set(k, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/get", func(ctx *Context) {
|
||||||
|
writeValues(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/clear", func(ctx *Context) {
|
||||||
|
ctx.Session().Clear()
|
||||||
|
writeValues(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/destroy", func(ctx *Context) {
|
||||||
|
ctx.SessionDestroy()
|
||||||
|
writeValues(ctx)
|
||||||
|
// the cookie and all values should be empty
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty()
|
||||||
|
e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values)
|
||||||
|
if testEnableSubdomain {
|
||||||
|
es := subdomainTester(e)
|
||||||
|
es.Request("GET", "/get").Expect().Status(StatusOK).JSON().Object().Equal(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test destory which also clears first
|
||||||
|
d := e.GET("/destroy").Expect().Status(StatusOK)
|
||||||
|
d.JSON().Object().Empty()
|
||||||
|
d.Cookies().ContainsOnly(Config.Sessions.Cookie)
|
||||||
|
// set and clear again
|
||||||
|
e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty()
|
||||||
|
e.GET("/clear").Expect().Status(StatusOK).JSON().Object().Empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
type renderTestInformationType struct {
|
||||||
|
XMLName xml.Name `xml:"info"`
|
||||||
|
FirstAttr string `xml:"first,attr"`
|
||||||
|
SecondAttr string `xml:"second,attr"`
|
||||||
|
Name string `xml:"name",json:"name"`
|
||||||
|
Birth string `xml:"birth",json:"birth"`
|
||||||
|
Stars int `xml:"stars",json:"stars"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextRenderRest(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
dataContents := []byte("Some binary data here.")
|
||||||
|
textContents := "Plain text here"
|
||||||
|
JSONPContents := map[string]string{"hello": "jsonp"}
|
||||||
|
JSONPCallback := "callbackName"
|
||||||
|
JSONXMLContents := renderTestInformationType{
|
||||||
|
XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later
|
||||||
|
FirstAttr: "this is the first attr",
|
||||||
|
SecondAttr: "this is the second attr",
|
||||||
|
Name: "Iris web framework",
|
||||||
|
Birth: "13 March 2016",
|
||||||
|
Stars: 4064,
|
||||||
|
}
|
||||||
|
markdownContents := "# Hello dynamic markdown from Iris"
|
||||||
|
|
||||||
|
Get("/data", func(ctx *Context) {
|
||||||
|
ctx.Data(StatusOK, dataContents)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/text", func(ctx *Context) {
|
||||||
|
ctx.Text(StatusOK, textContents)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/jsonp", func(ctx *Context) {
|
||||||
|
ctx.JSONP(StatusOK, JSONPCallback, JSONPContents)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/json", func(ctx *Context) {
|
||||||
|
ctx.JSON(StatusOK, JSONXMLContents)
|
||||||
|
})
|
||||||
|
Get("/xml", func(ctx *Context) {
|
||||||
|
ctx.XML(StatusOK, JSONXMLContents)
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/markdown", func(ctx *Context) {
|
||||||
|
ctx.Markdown(StatusOK, markdownContents)
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
dataT := e.GET("/data").Expect().Status(StatusOK)
|
||||||
|
dataT.Header("Content-Type").Equal("application/octet-stream")
|
||||||
|
dataT.Body().Equal(string(dataContents))
|
||||||
|
|
||||||
|
textT := e.GET("/text").Expect().Status(StatusOK)
|
||||||
|
textT.Header("Content-Type").Equal("text/plain; charset=UTF-8")
|
||||||
|
textT.Body().Equal(textContents)
|
||||||
|
|
||||||
|
JSONPT := e.GET("/jsonp").Expect().Status(StatusOK)
|
||||||
|
JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8")
|
||||||
|
JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`)
|
||||||
|
|
||||||
|
JSONT := e.GET("/json").Expect().Status(StatusOK)
|
||||||
|
JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8")
|
||||||
|
JSONT.JSON().Object().Equal(JSONXMLContents)
|
||||||
|
|
||||||
|
XMLT := e.GET("/xml").Expect().Status(StatusOK)
|
||||||
|
XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8")
|
||||||
|
XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `"><name>` + JSONXMLContents.Name + `</name><birth>` + JSONXMLContents.Birth + `</birth><stars>` + strconv.Itoa(JSONXMLContents.Stars) + `</stars></info>`)
|
||||||
|
|
||||||
|
markdownT := e.GET("/markdown").Expect().Status(StatusOK)
|
||||||
|
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
|
||||||
|
markdownT.Body().Equal("<h1>" + markdownContents[2:] + "</h1>\n")
|
||||||
|
|
||||||
|
}
|
82
deprecated.go
Normal file
82
deprecated.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
import "github.com/kataras/iris/config"
|
||||||
|
|
||||||
|
/* Contains some different functions of context.go & iris.go which will be removed on the next revision */
|
||||||
|
|
||||||
|
// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead
|
||||||
|
// DEPRECATED: use .AddServer instead
|
||||||
|
// AddServers starts a server which listens to this station
|
||||||
|
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
||||||
|
//
|
||||||
|
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
|
||||||
|
//
|
||||||
|
// receives one parameter which is the config.Server for the new server
|
||||||
|
// returns the new standalone server( you can close this server by the returning reference)
|
||||||
|
//
|
||||||
|
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
//
|
||||||
|
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
func SecondaryListen(cfg config.Server) *Server {
|
||||||
|
return Default.SecondaryListen(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead
|
||||||
|
// DEPRECATED: use .AddServer instead
|
||||||
|
// AddServers starts a server which listens to this station
|
||||||
|
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
||||||
|
//
|
||||||
|
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
|
||||||
|
//
|
||||||
|
// receives one parameter which is the config.Server for the new server
|
||||||
|
// returns the new standalone server( you can close this server by the returning reference)
|
||||||
|
//
|
||||||
|
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
//
|
||||||
|
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
func (s *Framework) SecondaryListen(cfg config.Server) *Server {
|
||||||
|
return s.Servers.Add(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
||||||
|
// DEPRECATED: use ListenVirtual instead
|
||||||
|
// initializes the whole framework but server doesn't listens to a specific net.Listener
|
||||||
|
// it is not blocking the app
|
||||||
|
func NoListen(optionalAddr ...string) *Server {
|
||||||
|
return Default.NoListen(optionalAddr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
||||||
|
// DEPRECATED: use ListenVirtual instead
|
||||||
|
// initializes the whole framework but server doesn't listens to a specific net.Listener
|
||||||
|
// it is not blocking the app
|
||||||
|
func (s *Framework) NoListen(optionalAddr ...string) *Server {
|
||||||
|
return s.ListenVirtual(optionalAddr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithErr terminates all the registered servers and returns an error if any
|
||||||
|
// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close())
|
||||||
|
// if you want to panic on this error use the iris.Must(iris.Close())
|
||||||
|
func CloseWithErr() error {
|
||||||
|
return Default.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithErr terminates all the registered servers and returns an error if any
|
||||||
|
// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close())
|
||||||
|
// if you want to panic on this error use the iris.Must(iris.Close())
|
||||||
|
func (s *Framework) CloseWithErr() error {
|
||||||
|
return s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostFormMulti returns a slice of string from post request's data
|
||||||
|
// DEPRECATED: Plase use FormValues instead
|
||||||
|
func (ctx *Context) PostFormMulti(name string) []string {
|
||||||
|
return ctx.FormValues(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostFormValue This will be deprecated
|
||||||
|
///DEPRECATED: please use FormValueString instead
|
||||||
|
// PostFormValue returns a single value from post request's data
|
||||||
|
func (ctx *Context) PostFormValue(name string) string {
|
||||||
|
return ctx.FormValueString(name)
|
||||||
|
}
|
186
http.go
186
http.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/iris-contrib/errors"
|
"github.com/iris-contrib/errors"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
|
@ -236,35 +237,47 @@ var (
|
||||||
errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
|
errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server the http server
|
type (
|
||||||
type Server struct {
|
// Server the http server
|
||||||
*fasthttp.Server
|
Server struct {
|
||||||
listener net.Listener
|
*fasthttp.Server
|
||||||
Config *config.Server
|
listener net.Listener
|
||||||
tls bool
|
Config config.Server
|
||||||
mu sync.Mutex
|
tls bool
|
||||||
}
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
// ServerList contains the servers connected to the Iris station
|
||||||
|
ServerList struct {
|
||||||
|
mux *serveMux
|
||||||
|
servers []*Server
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// newServer returns a pointer to a Server object, and set it's options if any, nothing more
|
// newServer returns a pointer to a Server object, and set it's options if any, nothing more
|
||||||
func newServer(c *config.Server) *Server {
|
func newServer(cfg config.Server) *Server {
|
||||||
s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
|
s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: cfg}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHandler sets the handler in order to listen on client requests
|
|
||||||
func (s *Server) SetHandler(mux *serveMux) {
|
|
||||||
if s.Server != nil {
|
|
||||||
s.Server.Handler = mux.ServeRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsListening returns true if server is listening/started, otherwise false
|
// IsListening returns true if server is listening/started, otherwise false
|
||||||
func (s *Server) IsListening() bool {
|
func (s *Server) IsListening() bool {
|
||||||
|
if s == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
return s.listener != nil && s.listener.Addr().String() != ""
|
return s.listener != nil && s.listener.Addr().String() != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsOpened checks if handler is not nil and returns true if not, otherwise false
|
||||||
|
// this is used to see if a server has opened, use IsListening if you want to see if the server is actually ready to serve connections
|
||||||
|
func (s *Server) IsOpened() bool {
|
||||||
|
if s == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Server != nil && s.Server.Handler != nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsSecure returns true if server uses TLS, otherwise false
|
// IsSecure returns true if server uses TLS, otherwise false
|
||||||
func (s *Server) IsSecure() bool {
|
func (s *Server) IsSecure() bool {
|
||||||
return s.tls
|
return s.tls
|
||||||
|
@ -398,7 +411,11 @@ func (s *Server) serve(l net.Listener) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
|
// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
|
||||||
func (s *Server) Open() error {
|
func (s *Server) Open(h fasthttp.RequestHandler) error {
|
||||||
|
if h == nil {
|
||||||
|
return errServerHandlerMissing.Return()
|
||||||
|
}
|
||||||
|
|
||||||
if s.IsListening() {
|
if s.IsListening() {
|
||||||
return errServerAlreadyStarted.Return()
|
return errServerAlreadyStarted.Return()
|
||||||
}
|
}
|
||||||
|
@ -407,10 +424,6 @@ func (s *Server) Open() error {
|
||||||
return errServerConfigMissing.Return()
|
return errServerConfigMissing.Return()
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Handler == nil {
|
|
||||||
return errServerHandlerMissing.Return()
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
|
// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
|
||||||
a := s.Config.ListeningAddr
|
a := s.Config.ListeningAddr
|
||||||
//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
|
//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
|
||||||
|
@ -419,9 +432,13 @@ func (s *Server) Open() error {
|
||||||
s.Config.ListeningAddr = config.DefaultServerHostname + a
|
s.Config.ListeningAddr = config.DefaultServerHostname + a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize {
|
||||||
|
s.Server.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
|
||||||
|
}
|
||||||
|
|
||||||
if s.Config.RedirectTo != "" {
|
if s.Config.RedirectTo != "" {
|
||||||
// override the handler and redirect all requests to this addr
|
// override the handler and redirect all requests to this addr
|
||||||
s.Handler = func(reqCtx *fasthttp.RequestCtx) {
|
s.Server.Handler = func(reqCtx *fasthttp.RequestCtx) {
|
||||||
path := string(reqCtx.Path())
|
path := string(reqCtx.Path())
|
||||||
redirectTo := s.Config.RedirectTo
|
redirectTo := s.Config.RedirectTo
|
||||||
if path != "/" {
|
if path != "/" {
|
||||||
|
@ -429,12 +446,19 @@ func (s *Server) Open() error {
|
||||||
}
|
}
|
||||||
reqCtx.Redirect(redirectTo, StatusMovedPermanently)
|
reqCtx.Redirect(redirectTo, StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
s.Server.Handler = h
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Config.Virtual {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Config.Mode > 0 {
|
if s.Config.Mode > 0 {
|
||||||
return s.listenUNIX()
|
return s.listenUNIX()
|
||||||
}
|
}
|
||||||
return s.listen()
|
return s.listen()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close terminates the server
|
// Close terminates the server
|
||||||
|
@ -447,6 +471,122 @@ func (s *Server) Close() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// --------------------------------ServerList implementation-----------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Add adds a server to the list by its config
|
||||||
|
// returns the new server
|
||||||
|
func (s *ServerList) Add(cfg config.Server) *Server {
|
||||||
|
srv := newServer(cfg)
|
||||||
|
s.servers = append(s.servers, srv)
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the size of the server list
|
||||||
|
func (s *ServerList) Len() int {
|
||||||
|
return len(s.servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main returns the main server,
|
||||||
|
// the last added server is the main server, even if's Virtual
|
||||||
|
func (s *ServerList) Main() (srv *Server) {
|
||||||
|
l := len(s.servers) - 1
|
||||||
|
for i := range s.servers {
|
||||||
|
if i == l {
|
||||||
|
return s.servers[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the server by it's registered Address
|
||||||
|
func (s *ServerList) Get(addr string) (srv *Server) {
|
||||||
|
for i := range s.servers {
|
||||||
|
srv = s.servers[i]
|
||||||
|
if srv.Config.ListeningAddr == addr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all registered servers
|
||||||
|
func (s *ServerList) GetAll() []*Server {
|
||||||
|
return s.servers
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByIndex returns a server from the list by it's index
|
||||||
|
func (s *ServerList) GetByIndex(i int) *Server {
|
||||||
|
if len(s.servers) >= i+1 {
|
||||||
|
return s.servers[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes a server by it's registered Address
|
||||||
|
// returns true if something was removed, otherwise returns false
|
||||||
|
func (s *ServerList) Remove(addr string) bool {
|
||||||
|
servers := s.servers
|
||||||
|
for i := range servers {
|
||||||
|
srv := servers[i]
|
||||||
|
if srv.Config.ListeningAddr == addr {
|
||||||
|
copy(servers[i:], servers[i+1:])
|
||||||
|
servers[len(servers)-1] = nil
|
||||||
|
s.servers = servers[:len(servers)-1]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseAll terminates all listening servers
|
||||||
|
// returns the first error, if erro happens it continues to closes the rest of the servers
|
||||||
|
func (s *ServerList) CloseAll() (err error) {
|
||||||
|
for i := range s.servers {
|
||||||
|
if err == nil {
|
||||||
|
err = s.servers[i].Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAll starts all servers
|
||||||
|
// returns the first error happens to one of these servers
|
||||||
|
// if one server gets error it closes the previous servers and exits from this process
|
||||||
|
func (s *ServerList) OpenAll() error {
|
||||||
|
l := len(s.servers) - 1
|
||||||
|
h := s.mux.ServeRequest()
|
||||||
|
for i := range s.servers {
|
||||||
|
|
||||||
|
if err := s.servers[i].Open(h); err != nil {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
// for any case,
|
||||||
|
// we don't care about performance on initialization,
|
||||||
|
// we must make sure that the previous servers are running before closing them
|
||||||
|
s.CloseAll()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == l {
|
||||||
|
s.mux.setHostname(s.servers[i].VirtualHostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOpened returns all opened/started servers
|
||||||
|
func (s *ServerList) GetAllOpened() (servers []*Server) {
|
||||||
|
for i := range s.servers {
|
||||||
|
if s.servers[i].IsOpened() {
|
||||||
|
servers = append(servers, s.servers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
|
// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
|
||||||
// It seems to be a +type Points to: +pointer.'
|
// It seems to be a +type Points to: +pointer.'
|
||||||
var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
|
var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
|
||||||
|
|
529
http_test.go
Normal file
529
http_test.go
Normal file
|
@ -0,0 +1,529 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the part we only care, these are end-to-end tests for the mux(router) and the server, the whole http file is made for these reasons only, so these tests are enough I think.
|
||||||
|
|
||||||
|
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gavv/httpexpect"
|
||||||
|
"github.com/kataras/iris/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testTLSCert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDAzCCAeugAwIBAgIJAPDsxtKV4v3uMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
|
||||||
|
BAMMDTEyNy4wLjAuMTo0NDMwHhcNMTYwNjI5MTMxMjU4WhcNMjYwNjI3MTMxMjU4
|
||||||
|
WjAYMRYwFAYDVQQDDA0xMjcuMC4wLjE6NDQzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH
|
||||||
|
XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns
|
||||||
|
8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB
|
||||||
|
dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv
|
||||||
|
ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs
|
||||||
|
pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||||
|
zr1df/c9+NyTpmyiQO8g3a8NswYwHwYDVR0jBBgwFoAUzr1df/c9+NyTpmyiQO8g
|
||||||
|
3a8NswYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEACG5shtMSDgCd
|
||||||
|
MNjOF+YmD+PX3Wy9J9zehgaDJ1K1oDvBbQFl7EOJl8lRMWITSws22Wxwh8UXVibL
|
||||||
|
sscKBp14dR3e7DdbwCVIX/JyrJyOaCfy2nNBdf1B06jYFsIHvP3vtBAb9bPNOTBQ
|
||||||
|
QE0Ztu9kCqgsmu0//sHuBEeA3d3E7wvDhlqRSxTLcLtgC1NSgkFvBw0JvwgpkX6s
|
||||||
|
M5WpSBZwZv8qpplxhFfqNy8Uf+xrpSW0pGfkHumehkQGC6/Ry7raganS0aHhDPK9
|
||||||
|
Z1bEJ2com1bFFAQsm9yIXrRVMGGCtihB2Au0Q4jpEjUbzWYM+ItZyvRAGRM6Qex6
|
||||||
|
s/jogMeRsw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH
|
||||||
|
XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns
|
||||||
|
8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB
|
||||||
|
dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv
|
||||||
|
ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs
|
||||||
|
pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABAoIBAQDCd+bo9I0s8Fun
|
||||||
|
4z3Y5oYSDTZ5O/CY0O5GyXPrSzCSM4Cj7EWEj1mTdb9Ohv9tam7WNHHLrcd+4NfK
|
||||||
|
4ok5hLVs1vqM6h6IksB7taKATz+Jo0PzkzrsXvMqzERhEBo4aoGMIv2rXIkrEdas
|
||||||
|
S+pCsp8+nAWtAeBMCn0Slu65d16vQxwgfod6YZfvMKbvfhOIOShl9ejQ+JxVZcMw
|
||||||
|
Ti8sgvYmFUrdrEH3nCgptARwbx4QwlHGaw/cLGHdepfFsVaNQsEzc7m61fSO70m4
|
||||||
|
NYJv48ZgjOooF5AccbEcQW9IxxikwNc+wpFYy5vDGzrBwS5zLZQFpoyMWFhtWdjx
|
||||||
|
hbmNn1jlAoGBAPs0ZjqsfDrH5ja4dQIdu5ErOccsmoHfMMBftMRqNG5neQXEmoLc
|
||||||
|
Uz8WeQ/QDf302aTua6E9iSjd7gglbFukVwMndQ1Q8Rwxz10jkXfiE32lFnqK0csx
|
||||||
|
ltruU6hOeSGSJhtGWBuNrT93G2lmy23fSG6BqOzdU4rn/2GPXy5zaxM/AoGBANSm
|
||||||
|
/E96RcBUiI6rDVqKhY+7M1yjLB41JrErL9a0Qfa6kYnaXMr84pOqVN11IjhNNTgl
|
||||||
|
g1lwxlpXZcZh7rYu9b7EEMdiWrJDQV7OxLDHopqUWkQ+3MHwqs6CxchyCq7kv9Df
|
||||||
|
IKqat7Me6Cyeo0MqcW+UMxlCRBxKQ9jqC7hDfZuXAoGBAJmyS8ImerP0TtS4M08i
|
||||||
|
JfsCOY21qqs/hbKOXCm42W+be56d1fOvHngBJf0YzRbO0sNo5Q14ew04DEWLsCq5
|
||||||
|
+EsDv0hwd7VKfJd+BakV99ruQTyk5wutwaEeJK1bph12MD6L4aiqHJAyLeFldZ45
|
||||||
|
+TUzu8mA+XaJz+U/NXtUPvU9AoGBALtl9M+tdy6I0Fa50ujJTe5eEGNAwK5WNKTI
|
||||||
|
5D2XWNIvk/Yh4shXlux+nI8UnHV1RMMX++qkAYi3oE71GsKeG55jdk3fFQInVsJQ
|
||||||
|
APGw3FDRD8M4ip62ki+u+tEr/tIlcAyHtWfjNKO7RuubWVDlZFXqCiXmSdOMdsH/
|
||||||
|
bxiREW49AoGACWev/eOzBoQJCRN6EvU2OV0s3b6f1QsPvcaH0zc6bgbBFOGmJU8v
|
||||||
|
pXhD88tsu9exptLkGVoYZjR0n0QT/2Kkyu93jVDW/80P7VCz8DKYyAJDa4CVwZxO
|
||||||
|
MlobQSunSDKx/CCJhWkbytCyh1bngAtwSAYLXavYIlJbAzx6FvtAIw4=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contains the server test for multi running servers
|
||||||
|
func TestMultiRunningServers_v1(t *testing.T) {
|
||||||
|
host := "mydomain.com:443" // you have to add it to your hosts file( for windows, as 127.0.0.1 mydomain.com)
|
||||||
|
initDefault()
|
||||||
|
Config.DisableBanner = true
|
||||||
|
// create the key and cert files on the fly, and delete them when this test finished
|
||||||
|
certFile, ferr := ioutil.TempFile("", "cert")
|
||||||
|
|
||||||
|
if ferr != nil {
|
||||||
|
t.Fatal(ferr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFile, ferr := ioutil.TempFile("", "key")
|
||||||
|
if ferr != nil {
|
||||||
|
t.Fatal(ferr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile.WriteString(testTLSCert)
|
||||||
|
keyFile.WriteString(testTLSKey)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
certFile.Close()
|
||||||
|
time.Sleep(350 * time.Millisecond)
|
||||||
|
os.Remove(certFile.Name())
|
||||||
|
|
||||||
|
keyFile.Close()
|
||||||
|
time.Sleep(350 * time.Millisecond)
|
||||||
|
os.Remove(keyFile.Name())
|
||||||
|
}()
|
||||||
|
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
ctx.Write("Hello from %s", ctx.HostString())
|
||||||
|
})
|
||||||
|
|
||||||
|
// start the secondary server
|
||||||
|
SecondaryListen(config.Server{ListeningAddr: "mydomain.com:80", RedirectTo: "https://" + host, Virtual: true})
|
||||||
|
// start the main server
|
||||||
|
go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
|
||||||
|
// prepare test framework
|
||||||
|
if ok := <-Available; !ok {
|
||||||
|
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.Request("GET", "http://mydomain.com:80").Expect().Status(StatusOK).Body().Equal("Hello from " + host)
|
||||||
|
e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains the server test for multi running servers
|
||||||
|
func TestMultiRunningServers_v2(t *testing.T) {
|
||||||
|
domain := "mydomain.com"
|
||||||
|
host := domain + ":443"
|
||||||
|
initDefault()
|
||||||
|
Config.DisableBanner = true
|
||||||
|
Config.Tester.ListeningAddr = host
|
||||||
|
// create the key and cert files on the fly, and delete them when this test finished
|
||||||
|
certFile, ferr := ioutil.TempFile("", "cert")
|
||||||
|
|
||||||
|
if ferr != nil {
|
||||||
|
t.Fatal(ferr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFile, ferr := ioutil.TempFile("", "key")
|
||||||
|
if ferr != nil {
|
||||||
|
t.Fatal(ferr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile.WriteString(testTLSCert)
|
||||||
|
keyFile.WriteString(testTLSKey)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
certFile.Close()
|
||||||
|
time.Sleep(350 * time.Millisecond)
|
||||||
|
os.Remove(certFile.Name())
|
||||||
|
|
||||||
|
keyFile.Close()
|
||||||
|
time.Sleep(350 * time.Millisecond)
|
||||||
|
os.Remove(keyFile.Name())
|
||||||
|
}()
|
||||||
|
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
ctx.Write("Hello from %s", ctx.HostString())
|
||||||
|
})
|
||||||
|
|
||||||
|
// add a secondary server
|
||||||
|
Servers.Add(config.Server{ListeningAddr: domain + ":80", RedirectTo: "https://" + host, Virtual: true})
|
||||||
|
// add our primary/main server
|
||||||
|
Servers.Add(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
|
||||||
|
|
||||||
|
go Go()
|
||||||
|
|
||||||
|
// prepare test framework
|
||||||
|
if ok := <-Available; !ok {
|
||||||
|
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.Request("GET", "http://"+domain+":80").Expect().Status(StatusOK).Body().Equal("Hello from " + host)
|
||||||
|
e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testEnableSubdomain = false
|
||||||
|
testSubdomain = "mysubdomain.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSubdomainHost() string {
|
||||||
|
return testSubdomain + strconv.Itoa(Servers.Main().Port())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSubdomainURL() (subdomainURL string) {
|
||||||
|
subdomainHost := testSubdomainHost()
|
||||||
|
if Servers.Main().IsSecure() {
|
||||||
|
subdomainURL = "https://" + subdomainHost
|
||||||
|
} else {
|
||||||
|
subdomainURL = "http://" + subdomainHost
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subdomainTester(e *httpexpect.Expect) *httpexpect.Expect {
|
||||||
|
es := e.Builder(func(req *httpexpect.Request) {
|
||||||
|
req.WithURL(testSubdomainURL())
|
||||||
|
})
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
type param struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type testRoute struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
RequestPath string
|
||||||
|
RequestQuery string
|
||||||
|
Body string
|
||||||
|
Status int
|
||||||
|
Register bool
|
||||||
|
Params []param
|
||||||
|
URLParams []param
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxSimple(t *testing.T) {
|
||||||
|
testRoutes := []testRoute{
|
||||||
|
// FOUND - registed
|
||||||
|
{"GET", "/test_get", "/test_get", "", "hello, get!", 200, true, nil, nil},
|
||||||
|
{"POST", "/test_post", "/test_post", "", "hello, post!", 200, true, nil, nil},
|
||||||
|
{"PUT", "/test_put", "/test_put", "", "hello, put!", 200, true, nil, nil},
|
||||||
|
{"DELETE", "/test_delete", "/test_delete", "", "hello, delete!", 200, true, nil, nil},
|
||||||
|
{"HEAD", "/test_head", "/test_head", "", "hello, head!", 200, true, nil, nil},
|
||||||
|
{"OPTIONS", "/test_options", "/test_options", "", "hello, options!", 200, true, nil, nil},
|
||||||
|
{"CONNECT", "/test_connect", "/test_connect", "", "hello, connect!", 200, true, nil, nil},
|
||||||
|
{"PATCH", "/test_patch", "/test_patch", "", "hello, patch!", 200, true, nil, nil},
|
||||||
|
{"TRACE", "/test_trace", "/test_trace", "", "hello, trace!", 200, true, nil, nil},
|
||||||
|
// NOT FOUND - not registed
|
||||||
|
{"GET", "/test_get_nofound", "/test_get_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"POST", "/test_post_nofound", "/test_post_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"PUT", "/test_put_nofound", "/test_put_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"DELETE", "/test_delete_nofound", "/test_delete_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"HEAD", "/test_head_nofound", "/test_head_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"OPTIONS", "/test_options_nofound", "/test_options_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"CONNECT", "/test_connect_nofound", "/test_connect_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"PATCH", "/test_patch_nofound", "/test_patch_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
{"TRACE", "/test_trace_nofound", "/test_trace_nofound", "", "Not Found", 404, false, nil, nil},
|
||||||
|
// Parameters
|
||||||
|
{"GET", "/test_get_parameter1/:name", "/test_get_parameter1/iris", "", "name=iris", 200, true, []param{{"name", "iris"}}, nil},
|
||||||
|
{"GET", "/test_get_parameter2/:name/details/:something", "/test_get_parameter2/iris/details/anything", "", "name=iris,something=anything", 200, true, []param{{"name", "iris"}, {"something", "anything"}}, nil},
|
||||||
|
{"GET", "/test_get_parameter2/:name/details/:something/*else", "/test_get_parameter2/iris/details/anything/elsehere", "", "name=iris,something=anything,else=/elsehere", 200, true, []param{{"name", "iris"}, {"something", "anything"}, {"else", "elsehere"}}, nil},
|
||||||
|
// URL Parameters
|
||||||
|
{"GET", "/test_get_urlparameter1/first", "/test_get_urlparameter1/first", "name=irisurl", "name=irisurl", 200, true, nil, []param{{"name", "irisurl"}}},
|
||||||
|
{"GET", "/test_get_urlparameter2/second", "/test_get_urlparameter2/second", "name=irisurl&something=anything", "name=irisurl,something=anything", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}}},
|
||||||
|
{"GET", "/test_get_urlparameter2/first/second/third", "/test_get_urlparameter2/first/second/third", "name=irisurl&something=anything&else=elsehere", "name=irisurl,something=anything,else=elsehere", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}, {"else", "elsehere"}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
for idx := range testRoutes {
|
||||||
|
r := testRoutes[idx]
|
||||||
|
if r.Register {
|
||||||
|
HandleFunc(r.Method, r.Path, func(ctx *Context) {
|
||||||
|
ctx.SetStatusCode(r.Status)
|
||||||
|
if r.Params != nil && len(r.Params) > 0 {
|
||||||
|
ctx.SetBodyString(ctx.Params.String())
|
||||||
|
} else if r.URLParams != nil && len(r.URLParams) > 0 {
|
||||||
|
if len(r.URLParams) != len(ctx.URLParams()) {
|
||||||
|
t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams()))
|
||||||
|
}
|
||||||
|
paramsKeyVal := ""
|
||||||
|
for idxp, p := range r.URLParams {
|
||||||
|
val := ctx.URLParam(p.Key)
|
||||||
|
paramsKeyVal += p.Key + "=" + val + ","
|
||||||
|
if idxp == len(r.URLParams)-1 {
|
||||||
|
paramsKeyVal = paramsKeyVal[0 : len(paramsKeyVal)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.SetBodyString(paramsKeyVal)
|
||||||
|
} else {
|
||||||
|
ctx.SetBodyString(r.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
// run the tests (1)
|
||||||
|
for idx := range testRoutes {
|
||||||
|
r := testRoutes[idx]
|
||||||
|
e.Request(r.Method, r.RequestPath).WithQueryString(r.RequestQuery).
|
||||||
|
Expect().
|
||||||
|
Status(r.Status).Body().Equal(r.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxSimpleParty(t *testing.T) {
|
||||||
|
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
h := func(c *Context) { c.WriteString(c.HostString() + c.PathString()) }
|
||||||
|
|
||||||
|
if testEnableSubdomain {
|
||||||
|
subdomainParty := Party(testSubdomain + ".")
|
||||||
|
{
|
||||||
|
subdomainParty.Get("/", h)
|
||||||
|
subdomainParty.Get("/path1", h)
|
||||||
|
subdomainParty.Get("/path2", h)
|
||||||
|
subdomainParty.Get("/namedpath/:param1/something/:param2", h)
|
||||||
|
subdomainParty.Get("/namedpath/:param1/something/:param2/else", h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple
|
||||||
|
p := Party("/party1")
|
||||||
|
{
|
||||||
|
p.Get("/", h)
|
||||||
|
p.Get("/path1", h)
|
||||||
|
p.Get("/path2", h)
|
||||||
|
p.Get("/namedpath/:param1/something/:param2", h)
|
||||||
|
p.Get("/namedpath/:param1/something/:param2/else", h)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
request := func(reqPath string) {
|
||||||
|
e.Request("GET", reqPath).
|
||||||
|
Expect().
|
||||||
|
Status(StatusOK).Body().Equal(Servers.Main().Host() + reqPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the tests
|
||||||
|
request("/party1/")
|
||||||
|
request("/party1/path1")
|
||||||
|
request("/party1/path2")
|
||||||
|
request("/party1/namedpath/theparam1/something/theparam2")
|
||||||
|
request("/party1/namedpath/theparam1/something/theparam2/else")
|
||||||
|
|
||||||
|
if testEnableSubdomain {
|
||||||
|
es := subdomainTester(e)
|
||||||
|
subdomainRequest := func(reqPath string) {
|
||||||
|
es.Request("GET", reqPath).
|
||||||
|
Expect().
|
||||||
|
Status(StatusOK).Body().Equal(testSubdomainHost() + reqPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
subdomainRequest("/")
|
||||||
|
subdomainRequest("/path1")
|
||||||
|
subdomainRequest("/path2")
|
||||||
|
subdomainRequest("/namedpath/theparam1/something/theparam2")
|
||||||
|
subdomainRequest("/namedpath/theparam1/something/theparam2/else")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxPathEscape(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
Get("/details/:name", func(ctx *Context) {
|
||||||
|
name := ctx.Param("name")
|
||||||
|
highlight := ctx.URLParam("highlight")
|
||||||
|
ctx.Text(StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
e.GET("/details/Sakamoto desu ga").
|
||||||
|
WithQuery("highlight", "text").
|
||||||
|
Expect().Status(StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxCustomErrors(t *testing.T) {
|
||||||
|
var (
|
||||||
|
notFoundMessage = "Iris custom message for 404 not found"
|
||||||
|
internalServerMessage = "Iris custom message for 500 internal server error"
|
||||||
|
testRoutesCustomErrors = []testRoute{
|
||||||
|
// NOT FOUND CUSTOM ERRORS - not registed
|
||||||
|
{"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
{"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
|
||||||
|
// SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registed
|
||||||
|
{"GET", "/test_get_panic_custom", "/test_get_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"POST", "/test_post_panic_custom", "/test_post_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"PUT", "/test_put_panic_custom", "/test_put_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
{"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", "", internalServerMessage, 500, true, nil, nil},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
initDefault()
|
||||||
|
// first register the testRoutes needed
|
||||||
|
for _, r := range testRoutesCustomErrors {
|
||||||
|
if r.Register {
|
||||||
|
HandleFunc(r.Method, r.Path, func(ctx *Context) {
|
||||||
|
ctx.EmitError(r.Status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the custom errors
|
||||||
|
OnError(404, func(ctx *Context) {
|
||||||
|
ctx.Write("%s", notFoundMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
OnError(500, func(ctx *Context) {
|
||||||
|
ctx.Write("%s", internalServerMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
// create httpexpect instance that will call fasthtpp.RequestHandler directly
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
// run the tests
|
||||||
|
for _, r := range testRoutesCustomErrors {
|
||||||
|
e.Request(r.Method, r.RequestPath).
|
||||||
|
Expect().
|
||||||
|
Status(r.Status).Body().Equal(r.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testUserAPI struct {
|
||||||
|
*Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /users
|
||||||
|
func (u testUserAPI) Get() {
|
||||||
|
u.Write("Get Users\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /users/:param1 which its value passed to the id argument
|
||||||
|
func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1")
|
||||||
|
u.Write("Get By %s\n", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT /users
|
||||||
|
func (u testUserAPI) Put() {
|
||||||
|
u.Write("Put, name: %s\n", u.FormValue("name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /users/:param1
|
||||||
|
func (u testUserAPI) PostBy(id string) {
|
||||||
|
u.Write("Post By %s, name: %s\n", id, u.FormValue("name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE /users/:param1
|
||||||
|
func (u testUserAPI) DeleteBy(id string) {
|
||||||
|
u.Write("Delete By %s\n", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxAPI(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
|
||||||
|
middlewareResponseText := "I assume that you are authenticated\n"
|
||||||
|
API("/users", testUserAPI{}, func(ctx *Context) { // optional middleware for .API
|
||||||
|
// do your work here, or render a login window if not logged in, get the user and send it to the next middleware, or do all here
|
||||||
|
ctx.Set("user", "username")
|
||||||
|
ctx.Next()
|
||||||
|
}, func(ctx *Context) {
|
||||||
|
if ctx.Get("user") == "username" {
|
||||||
|
ctx.Write(middlewareResponseText)
|
||||||
|
ctx.Next()
|
||||||
|
} else {
|
||||||
|
ctx.SetStatusCode(StatusUnauthorized)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
|
||||||
|
userID := "4077"
|
||||||
|
formname := "kataras"
|
||||||
|
|
||||||
|
e.GET("/users").Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get Users\n")
|
||||||
|
e.GET("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n")
|
||||||
|
e.PUT("/users").WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n")
|
||||||
|
e.POST("/users/"+userID).WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n")
|
||||||
|
e.DELETE("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
type myTestHandlerData struct {
|
||||||
|
Sysname string // this will be the same for all requests
|
||||||
|
Version int // this will be the same for all requests
|
||||||
|
DynamicPathParameter string // this will be different for each request
|
||||||
|
}
|
||||||
|
|
||||||
|
type myTestCustomHandler struct {
|
||||||
|
data myTestHandlerData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *myTestCustomHandler) Serve(ctx *Context) {
|
||||||
|
data := &m.data
|
||||||
|
data.DynamicPathParameter = ctx.Param("myparam")
|
||||||
|
ctx.JSON(StatusOK, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxCustomHandler(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
myData := myTestHandlerData{
|
||||||
|
Sysname: "Redhat",
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
Handle("GET", "/custom_handler_1/:myparam", &myTestCustomHandler{myData})
|
||||||
|
Handle("GET", "/custom_handler_2/:myparam", &myTestCustomHandler{myData})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
// two times per testRoute
|
||||||
|
param1 := "thisimyparam1"
|
||||||
|
expectedData1 := myData
|
||||||
|
expectedData1.DynamicPathParameter = param1
|
||||||
|
e.GET("/custom_handler_1/" + param1).Expect().Status(StatusOK).JSON().Equal(expectedData1)
|
||||||
|
|
||||||
|
param2 := "thisimyparam2"
|
||||||
|
expectedData2 := myData
|
||||||
|
expectedData2.DynamicPathParameter = param2
|
||||||
|
e.GET("/custom_handler_1/" + param2).Expect().Status(StatusOK).JSON().Equal(expectedData2)
|
||||||
|
|
||||||
|
param3 := "thisimyparam3"
|
||||||
|
expectedData3 := myData
|
||||||
|
expectedData3.DynamicPathParameter = param3
|
||||||
|
e.GET("/custom_handler_2/" + param3).Expect().Status(StatusOK).JSON().Equal(expectedData3)
|
||||||
|
|
||||||
|
param4 := "thisimyparam4"
|
||||||
|
expectedData4 := myData
|
||||||
|
expectedData4.DynamicPathParameter = param4
|
||||||
|
e.GET("/custom_handler_2/" + param4).Expect().Status(StatusOK).JSON().Equal(expectedData4)
|
||||||
|
}
|
225
initiatory.go
225
initiatory.go
|
@ -1,225 +0,0 @@
|
||||||
package iris
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gavv/httpexpect"
|
|
||||||
"github.com/kataras/iris/config"
|
|
||||||
"github.com/kataras/iris/logger"
|
|
||||||
"github.com/kataras/iris/render/rest"
|
|
||||||
"github.com/kataras/iris/render/template"
|
|
||||||
"github.com/kataras/iris/sessions"
|
|
||||||
"github.com/kataras/iris/websocket"
|
|
||||||
///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled.
|
|
||||||
_ "github.com/kataras/iris/sessions/providers/memory"
|
|
||||||
_ "github.com/kataras/iris/sessions/providers/redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default entry, use it with iris.$anyPublicFunc
|
|
||||||
var (
|
|
||||||
Default *Framework
|
|
||||||
Config *config.Iris
|
|
||||||
Logger *logger.Logger
|
|
||||||
Plugins PluginContainer
|
|
||||||
Websocket websocket.Server
|
|
||||||
HTTPServer *Server
|
|
||||||
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
|
||||||
// never fires false, if the .Close called then the channel is re-allocating.
|
|
||||||
// the channel is always oepen until you close it when you don't need this.
|
|
||||||
//
|
|
||||||
// Note: it is a simple channel and decided to put it here and no inside HTTPServer, doesn't have statuses just true and false, simple as possible
|
|
||||||
// Where to use that?
|
|
||||||
// this is used on extreme cases when you don't know which .Listen/.NoListen will be called
|
|
||||||
// and you want to run/declare something external-not-Iris (all Iris functionality declared before .Listen/.NoListen) AFTER the server is started and plugins finished.
|
|
||||||
// see the server_test.go for an example
|
|
||||||
Available chan bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Default = New()
|
|
||||||
Config = Default.Config
|
|
||||||
Logger = Default.Logger
|
|
||||||
Plugins = Default.Plugins
|
|
||||||
Websocket = Default.Websocket
|
|
||||||
HTTPServer = Default.HTTPServer
|
|
||||||
Available = Default.Available
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
/* conversional */
|
|
||||||
|
|
||||||
// HTMLEngine conversion for config.HTMLEngine
|
|
||||||
HTMLEngine = config.HTMLEngine
|
|
||||||
// PongoEngine conversion for config.PongoEngine
|
|
||||||
PongoEngine = config.PongoEngine
|
|
||||||
// MarkdownEngine conversion for config.MarkdownEngine
|
|
||||||
MarkdownEngine = config.MarkdownEngine
|
|
||||||
// JadeEngine conversion for config.JadeEngine
|
|
||||||
JadeEngine = config.JadeEngine
|
|
||||||
// AmberEngine conversion for config.AmberEngine
|
|
||||||
AmberEngine = config.AmberEngine
|
|
||||||
// HandlebarsEngine conversion for config.HandlebarsEngine
|
|
||||||
HandlebarsEngine = config.HandlebarsEngine
|
|
||||||
// DefaultEngine conversion for config.DefaultEngine
|
|
||||||
DefaultEngine = config.DefaultEngine
|
|
||||||
// NoEngine conversion for config.NoEngine
|
|
||||||
NoEngine = config.NoEngine
|
|
||||||
// NoLayout to disable layout for a particular template file
|
|
||||||
// conversion for config.NoLayout
|
|
||||||
NoLayout = config.NoLayout
|
|
||||||
/* end conversional */
|
|
||||||
)
|
|
||||||
|
|
||||||
// Framework is our God |\| Google.Search('Greek mythology Iris')
|
|
||||||
//
|
|
||||||
// Implements the FrameworkAPI
|
|
||||||
type Framework struct {
|
|
||||||
*muxAPI
|
|
||||||
rest *rest.Render
|
|
||||||
templates *template.Template
|
|
||||||
sessions *sessions.Manager
|
|
||||||
// fields which are useful to the user/dev
|
|
||||||
HTTPServer *Server
|
|
||||||
Config *config.Iris
|
|
||||||
Logger *logger.Logger
|
|
||||||
Plugins PluginContainer
|
|
||||||
Websocket websocket.Server
|
|
||||||
Available chan bool
|
|
||||||
// this is setted once when .Tester(t) is called
|
|
||||||
testFramework *httpexpect.Expect
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates and returns a new Iris station aka Framework.
|
|
||||||
//
|
|
||||||
// Receives an optional config.Iris as parameter
|
|
||||||
// If empty then config.Default() is used instead
|
|
||||||
func New(cfg ...config.Iris) *Framework {
|
|
||||||
c := config.Default().Merge(cfg)
|
|
||||||
|
|
||||||
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
|
|
||||||
// some things never change :)
|
|
||||||
s := &Framework{Config: &c, Available: make(chan bool)}
|
|
||||||
{
|
|
||||||
///NOTE: set all with s.Config pointer
|
|
||||||
// set the Logger
|
|
||||||
s.Logger = logger.New(s.Config.Logger)
|
|
||||||
// set the plugin container
|
|
||||||
s.Plugins = &pluginContainer{logger: s.Logger}
|
|
||||||
// set the websocket server
|
|
||||||
s.Websocket = websocket.NewServer(s.Config.Websocket)
|
|
||||||
// set the servemux, which will provide us the public API also, with its context pool
|
|
||||||
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
|
|
||||||
// set the public router API (and party)
|
|
||||||
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
|
|
||||||
// set the server
|
|
||||||
s.HTTPServer = newServer(&s.Config.Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Framework) initialize() {
|
|
||||||
// set sessions
|
|
||||||
if s.Config.Sessions.Provider != "" {
|
|
||||||
s.sessions = sessions.New(s.Config.Sessions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the rest
|
|
||||||
s.rest = rest.New(s.Config.Render.Rest)
|
|
||||||
|
|
||||||
// set templates if not already setted
|
|
||||||
s.prepareTemplates()
|
|
||||||
|
|
||||||
// listen to websocket connections
|
|
||||||
websocket.RegisterServer(s, s.Websocket, s.Logger)
|
|
||||||
|
|
||||||
// prepare the mux & the server
|
|
||||||
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
|
||||||
s.mux.setEscapePath(!s.Config.DisablePathEscape)
|
|
||||||
s.mux.setHostname(s.HTTPServer.VirtualHostname())
|
|
||||||
// set the debug profiling handlers if ProfilePath is setted
|
|
||||||
if debugPath := s.Config.ProfilePath; debugPath != "" {
|
|
||||||
s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize {
|
|
||||||
s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen
|
|
||||||
func (s *Framework) prepareTemplates() {
|
|
||||||
// prepare the templates
|
|
||||||
if s.templates == nil {
|
|
||||||
// These functions are directly contact with Iris' functionality.
|
|
||||||
funcs := map[string]interface{}{
|
|
||||||
"url": s.URL,
|
|
||||||
"urlpath": s.Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
template.RegisterSharedFuncs(funcs)
|
|
||||||
|
|
||||||
s.templates = template.New(s.Config.Render.Template)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// openServer is internal method, open the server with specific options passed by the Listen and ListenTLS
|
|
||||||
// it's a blocking func
|
|
||||||
func (s *Framework) openServer() (err error) {
|
|
||||||
s.initialize()
|
|
||||||
s.Plugins.DoPreListen(s)
|
|
||||||
// set the server's handler now, in order to give the chance to the plugins to add their own middlewares and routes to this station
|
|
||||||
s.HTTPServer.SetHandler(s.mux)
|
|
||||||
if err = s.HTTPServer.Open(); err == nil {
|
|
||||||
// print the banner
|
|
||||||
if !s.Config.DisableBanner {
|
|
||||||
s.Logger.PrintBanner(banner,
|
|
||||||
fmt.Sprintf("%s: Running at %s\n", time.Now().Format(config.TimeFormat),
|
|
||||||
s.HTTPServer.Host()))
|
|
||||||
}
|
|
||||||
s.Plugins.DoPostListen(s)
|
|
||||||
s.Available <- true
|
|
||||||
ch := make(chan os.Signal)
|
|
||||||
<-ch
|
|
||||||
s.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeServer is used to close the tcp listener from the server, returns an error
|
|
||||||
func (s *Framework) closeServer() error {
|
|
||||||
s.Plugins.DoPreClose(s)
|
|
||||||
s.Available = make(chan bool)
|
|
||||||
return s.HTTPServer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// justServe initializes the whole framework but server doesn't listens to a specific net.Listener
|
|
||||||
func (s *Framework) justServe(optionalAddr ...string) *Server {
|
|
||||||
s.HTTPServer.Config = &s.Config.Server
|
|
||||||
|
|
||||||
if len(optionalAddr) > 0 {
|
|
||||||
s.HTTPServer.Config.ListeningAddr = optionalAddr[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
s.initialize()
|
|
||||||
s.Plugins.DoPreListen(s)
|
|
||||||
s.HTTPServer.SetHandler(s.mux)
|
|
||||||
s.Plugins.DoPostListen(s)
|
|
||||||
go func() {
|
|
||||||
s.Available <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
return s.HTTPServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// tester returns the test framework
|
|
||||||
func (s *Framework) tester(t *testing.T) *httpexpect.Expect {
|
|
||||||
if s.testFramework == nil {
|
|
||||||
s.testFramework = NewTester(s, t)
|
|
||||||
}
|
|
||||||
return s.testFramework
|
|
||||||
}
|
|
610
iris.go
610
iris.go
|
@ -61,40 +61,109 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gavv/httpexpect"
|
"github.com/gavv/httpexpect"
|
||||||
"github.com/iris-contrib/errors"
|
"github.com/iris-contrib/errors"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
|
"github.com/kataras/iris/logger"
|
||||||
|
"github.com/kataras/iris/render/rest"
|
||||||
|
"github.com/kataras/iris/render/template"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
"github.com/kataras/iris/utils"
|
"github.com/kataras/iris/utils"
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled.
|
||||||
|
_ "github.com/kataras/iris/sessions/providers/memory"
|
||||||
|
_ "github.com/kataras/iris/sessions/providers/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version of the iris
|
// Version of the iris
|
||||||
Version = "3.0.0-rc.4"
|
Version = "3.0.0-pre.release"
|
||||||
banner = ` _____ _
|
|
||||||
|
// HTMLEngine conversion for config.HTMLEngine
|
||||||
|
HTMLEngine = config.HTMLEngine
|
||||||
|
// PongoEngine conversion for config.PongoEngine
|
||||||
|
PongoEngine = config.PongoEngine
|
||||||
|
// MarkdownEngine conversion for config.MarkdownEngine
|
||||||
|
MarkdownEngine = config.MarkdownEngine
|
||||||
|
// JadeEngine conversion for config.JadeEngine
|
||||||
|
JadeEngine = config.JadeEngine
|
||||||
|
// AmberEngine conversion for config.AmberEngine
|
||||||
|
AmberEngine = config.AmberEngine
|
||||||
|
// HandlebarsEngine conversion for config.HandlebarsEngine
|
||||||
|
HandlebarsEngine = config.HandlebarsEngine
|
||||||
|
// DefaultEngine conversion for config.DefaultEngine
|
||||||
|
DefaultEngine = config.DefaultEngine
|
||||||
|
// NoEngine conversion for config.NoEngine
|
||||||
|
NoEngine = config.NoEngine
|
||||||
|
// NoLayout to disable layout for a particular template file
|
||||||
|
// conversion for config.NoLayout
|
||||||
|
NoLayout = config.NoLayout
|
||||||
|
|
||||||
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
| | ____ _ ___
|
| | ____ _ ___
|
||||||
| | | __|| |/ __|
|
| | | __|| |/ __|
|
||||||
_| |_| | | |\__ \
|
_| |_| | | |\__ \
|
||||||
|_____|_| |_||___/ ` + Version + `
|
|_____|_| |_||___/ ` + Version + ` `
|
||||||
`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default entry, use it with iris.$anyPublicFunc
|
||||||
|
var (
|
||||||
|
Default *Framework
|
||||||
|
Config *config.Iris
|
||||||
|
Logger *logger.Logger
|
||||||
|
Plugins PluginContainer
|
||||||
|
Websocket websocket.Server
|
||||||
|
Servers *ServerList
|
||||||
|
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
||||||
|
// never fires false, if the .Close called then the channel is re-allocating.
|
||||||
|
// the channel is closed only when .ListenVirtual is used, otherwise it remains open until you close it.
|
||||||
|
//
|
||||||
|
// Note: it is a simple channel and decided to put it here and no inside HTTPServer, doesn't have statuses just true and false, simple as possible
|
||||||
|
// Where to use that?
|
||||||
|
// this is used on extreme cases when you don't know which .Listen/.NoListen will be called
|
||||||
|
// and you want to run/declare something external-not-Iris (all Iris functionality declared before .Listen/.NoListen) AFTER the server is started and plugins finished.
|
||||||
|
// see the server_test.go for an example
|
||||||
|
Available chan bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDefault() {
|
||||||
|
Default = New()
|
||||||
|
Config = Default.Config
|
||||||
|
Logger = Default.Logger
|
||||||
|
Plugins = Default.Plugins
|
||||||
|
Websocket = Default.Websocket
|
||||||
|
Servers = Default.Servers
|
||||||
|
Available = Default.Available
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// --------------------------------Framework implementation-----------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// FrameworkAPI contains the main Iris Public API
|
// FrameworkAPI contains the main Iris Public API
|
||||||
FrameworkAPI interface {
|
FrameworkAPI interface {
|
||||||
MuxAPI
|
MuxAPI
|
||||||
Must(error)
|
Must(error)
|
||||||
ListenWithErr(string) error
|
AddServer(config.Server) *Server
|
||||||
|
ListenTo(config.Server) error
|
||||||
Listen(string)
|
Listen(string)
|
||||||
ListenTLSWithErr(string, string, string) error
|
|
||||||
ListenTLS(string, string, string)
|
ListenTLS(string, string, string)
|
||||||
ListenUNIXWithErr(string, os.FileMode) error
|
|
||||||
ListenUNIX(string, os.FileMode)
|
ListenUNIX(string, os.FileMode)
|
||||||
SecondaryListen(config.Server) *Server
|
ListenVirtual(...string) *Server
|
||||||
NoListen(...string) *Server
|
Go() error
|
||||||
Close()
|
Close() error
|
||||||
// global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also
|
// global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also
|
||||||
MustUse(...Handler)
|
MustUse(...Handler)
|
||||||
MustUseFunc(...HandlerFunc)
|
MustUseFunc(...HandlerFunc)
|
||||||
|
@ -108,56 +177,138 @@ type (
|
||||||
Tester(t *testing.T) *httpexpect.Expect
|
Tester(t *testing.T) *httpexpect.Expect
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
|
// Framework is our God |\| Google.Search('Greek mythology Iris')
|
||||||
RouteNameFunc func(string)
|
//
|
||||||
// MuxAPI the visible api for the serveMux
|
// Implements the FrameworkAPI
|
||||||
MuxAPI interface {
|
Framework struct {
|
||||||
Party(string, ...HandlerFunc) MuxAPI
|
*muxAPI
|
||||||
// middleware serial, appending
|
rest *rest.Render
|
||||||
Use(...Handler)
|
templates *template.Template
|
||||||
UseFunc(...HandlerFunc)
|
sessions *sessions.Manager
|
||||||
|
// fields which are useful to the user/dev
|
||||||
// main handlers
|
// the last added server is the main server
|
||||||
Handle(string, string, ...Handler) RouteNameFunc
|
Servers *ServerList
|
||||||
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
|
Config *config.Iris
|
||||||
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
|
Logger *logger.Logger
|
||||||
H_(string, string, func(context.IContext)) func(string)
|
Plugins PluginContainer
|
||||||
API(string, HandlerAPI, ...HandlerFunc)
|
Websocket websocket.Server
|
||||||
|
Available chan bool
|
||||||
// http methods
|
// this is setted once when .Tester(t) is called
|
||||||
Get(string, ...HandlerFunc) RouteNameFunc
|
testFramework *httpexpect.Expect
|
||||||
Post(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Put(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Delete(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Connect(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Head(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Options(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Patch(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Trace(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Any(string, ...HandlerFunc)
|
|
||||||
|
|
||||||
// static content
|
|
||||||
StaticHandler(string, int, bool, bool, []string) HandlerFunc
|
|
||||||
Static(string, string, int) RouteNameFunc
|
|
||||||
StaticFS(string, string, int) RouteNameFunc
|
|
||||||
StaticWeb(string, string, int) RouteNameFunc
|
|
||||||
StaticServe(string, ...string) RouteNameFunc
|
|
||||||
StaticContent(string, string, []byte) func(string)
|
|
||||||
Favicon(string, ...string) RouteNameFunc
|
|
||||||
|
|
||||||
// templates
|
|
||||||
Layout(string) MuxAPI // returns itself
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// --------------------------------Framework implementation-----------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
var _ FrameworkAPI = &Framework{}
|
var _ FrameworkAPI = &Framework{}
|
||||||
|
|
||||||
|
// New creates and returns a new Iris station aka Framework.
|
||||||
|
//
|
||||||
|
// Receives an optional config.Iris as parameter
|
||||||
|
// If empty then config.Default() is used instead
|
||||||
|
func New(cfg ...config.Iris) *Framework {
|
||||||
|
c := config.Default().Merge(cfg)
|
||||||
|
|
||||||
|
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
|
||||||
|
// some things never change :)
|
||||||
|
s := &Framework{Config: &c, Available: make(chan bool)}
|
||||||
|
{
|
||||||
|
///NOTE: set all with s.Config pointer
|
||||||
|
// set the Logger
|
||||||
|
s.Logger = logger.New(s.Config.Logger)
|
||||||
|
// set the plugin container
|
||||||
|
s.Plugins = &pluginContainer{logger: s.Logger}
|
||||||
|
// set the websocket server
|
||||||
|
s.Websocket = websocket.NewServer(s.Config.Websocket)
|
||||||
|
// set the servemux, which will provide us the public API also, with its context pool
|
||||||
|
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
|
||||||
|
// set the public router API (and party)
|
||||||
|
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
|
||||||
|
|
||||||
|
s.Servers = &ServerList{mux: mux, servers: make([]*Server, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Framework) initialize() {
|
||||||
|
// set sessions
|
||||||
|
if s.Config.Sessions.Provider != "" {
|
||||||
|
s.sessions = sessions.New(s.Config.Sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the rest
|
||||||
|
s.rest = rest.New(s.Config.Render.Rest)
|
||||||
|
|
||||||
|
// set templates if not already setted
|
||||||
|
s.prepareTemplates()
|
||||||
|
|
||||||
|
// listen to websocket connections
|
||||||
|
websocket.RegisterServer(s, s.Websocket, s.Logger)
|
||||||
|
|
||||||
|
// prepare the mux & the server
|
||||||
|
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
||||||
|
s.mux.setEscapePath(!s.Config.DisablePathEscape)
|
||||||
|
|
||||||
|
// set the debug profiling handlers if ProfilePath is setted
|
||||||
|
if debugPath := s.Config.ProfilePath; debugPath != "" {
|
||||||
|
s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen
|
||||||
|
func (s *Framework) prepareTemplates() {
|
||||||
|
// prepare the templates
|
||||||
|
if s.templates == nil {
|
||||||
|
// These functions are directly contact with Iris' functionality.
|
||||||
|
funcs := map[string]interface{}{
|
||||||
|
"url": s.URL,
|
||||||
|
"urlpath": s.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
template.RegisterSharedFuncs(funcs)
|
||||||
|
|
||||||
|
s.templates = template.New(s.Config.Render.Template)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go starts the iris station, listens to all registered servers, and prepare only if Virtual
|
||||||
|
func Go() error {
|
||||||
|
return Default.Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go starts the iris station, listens to all registered servers, and prepare only if Virtual
|
||||||
|
func (s *Framework) Go() error {
|
||||||
|
s.initialize()
|
||||||
|
s.Plugins.DoPreListen(s)
|
||||||
|
|
||||||
|
if firstErr := s.Servers.OpenAll(); firstErr != nil {
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the banner
|
||||||
|
if !s.Config.DisableBanner {
|
||||||
|
|
||||||
|
openedServers := s.Servers.GetAllOpened()
|
||||||
|
l := len(openedServers)
|
||||||
|
hosts := make([]string, l, l)
|
||||||
|
for i, srv := range openedServers {
|
||||||
|
hosts[i] = srv.Host()
|
||||||
|
}
|
||||||
|
|
||||||
|
bannerMessage := time.Now().Format(config.TimeFormat) + ": Running at " + strings.Join(hosts, ", ")
|
||||||
|
s.Logger.PrintBanner(banner, bannerMessage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Plugins.DoPostListen(s)
|
||||||
|
|
||||||
|
go func() { s.Available <- true }()
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
<-ch
|
||||||
|
s.Close() // btw, don't panic here
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Must panics on error, it panics on registed iris' logger
|
// Must panics on error, it panics on registed iris' logger
|
||||||
func Must(err error) {
|
func Must(err error) {
|
||||||
Default.Must(err)
|
Default.Must(err)
|
||||||
|
@ -170,60 +321,74 @@ func (s *Framework) Must(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenWithErr starts the standalone http server
|
// AddServer same as .Servers.Add(config.Server) instead
|
||||||
// which listens to the addr parameter which as the form of
|
|
||||||
// host:port
|
|
||||||
//
|
//
|
||||||
// It returns an error you are responsible how to handle this
|
// AddServers starts a server which listens to this station
|
||||||
// if you need a func to panic on error use the Listen
|
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
||||||
// ex: log.Fatal(iris.ListenWithErr(":8080"))
|
//
|
||||||
func ListenWithErr(addr string) error {
|
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
|
||||||
return Default.ListenWithErr(addr)
|
//
|
||||||
|
// receives one parameter which is the config.Server for the new server
|
||||||
|
// returns the new standalone server( you can close this server by the returning reference)
|
||||||
|
//
|
||||||
|
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
//
|
||||||
|
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
func AddServer(cfg config.Server) *Server {
|
||||||
|
return Default.AddServer(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddServer same as .Servers.Add(config.Server) instead
|
||||||
|
//
|
||||||
|
// AddServers starts a server which listens to this station
|
||||||
|
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
||||||
|
//
|
||||||
|
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
|
||||||
|
//
|
||||||
|
// receives one parameter which is the config.Server for the new server
|
||||||
|
// returns the new standalone server( you can close this server by the returning reference)
|
||||||
|
//
|
||||||
|
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
//
|
||||||
|
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
|
||||||
|
func (s *Framework) AddServer(cfg config.Server) *Server {
|
||||||
|
return s.Servers.Add(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenTo listens to a server but receives the full server's configuration
|
||||||
|
// returns an error, you're responsible to handle that
|
||||||
|
// or use the iris.Must(iris.ListenTo(config.Server{}))
|
||||||
|
//
|
||||||
|
// it's a blocking func
|
||||||
|
func ListenTo(cfg config.Server) error {
|
||||||
|
return Default.ListenTo(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenTo listens to a server but receives the full server's configuration
|
||||||
|
// it's a blocking func
|
||||||
|
func (s *Framework) ListenTo(cfg config.Server) (err error) {
|
||||||
|
s.Servers.Add(cfg)
|
||||||
|
return s.Go()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen starts the standalone http server
|
// Listen starts the standalone http server
|
||||||
// which listens to the addr parameter which as the form of
|
// which listens to the addr parameter which as the form of
|
||||||
// host:port
|
// host:port
|
||||||
//
|
//
|
||||||
// It panics on error if you need a func to return an error use the ListenWithErr
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
// ex: iris.Listen(":8080")
|
// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
|
||||||
func Listen(addr string) {
|
func Listen(addr string) {
|
||||||
Default.Listen(addr)
|
Default.Listen(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenWithErr starts the standalone http server
|
|
||||||
// which listens to the addr parameter which as the form of
|
|
||||||
// host:port
|
|
||||||
//
|
|
||||||
// It returns an error you are responsible how to handle this
|
|
||||||
// if you need a func to panic on error use the Listen
|
|
||||||
// ex: log.Fatal(iris.ListenWithErr(":8080"))
|
|
||||||
func (s *Framework) ListenWithErr(addr string) error {
|
|
||||||
s.Config.Server.ListeningAddr = addr
|
|
||||||
return s.openServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen starts the standalone http server
|
// Listen starts the standalone http server
|
||||||
// which listens to the addr parameter which as the form of
|
// which listens to the addr parameter which as the form of
|
||||||
// host:port
|
// host:port
|
||||||
//
|
//
|
||||||
// It panics on error if you need a func to return an error use the ListenWithErr
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
// ex: iris.Listen(":8080")
|
// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
|
||||||
func (s *Framework) Listen(addr string) {
|
func (s *Framework) Listen(addr string) {
|
||||||
s.Must(s.ListenWithErr(addr))
|
s.Must(s.ListenTo(config.Server{ListeningAddr: addr}))
|
||||||
}
|
|
||||||
|
|
||||||
// ListenTLSWithErr Starts a https server with certificates,
|
|
||||||
// if you use this method the requests of the form of 'http://' will fail
|
|
||||||
// only https:// connections are allowed
|
|
||||||
// which listens to the addr parameter which as the form of
|
|
||||||
// host:port
|
|
||||||
//
|
|
||||||
// It returns an error you are responsible how to handle this
|
|
||||||
// if you need a func to panic on error use the ListenTLS
|
|
||||||
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
|
|
||||||
func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
|
|
||||||
return Default.ListenTLSWithErr(addr, certFile, keyFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenTLS Starts a https server with certificates,
|
// ListenTLS Starts a https server with certificates,
|
||||||
|
@ -232,142 +397,86 @@ func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
|
||||||
// which listens to the addr parameter which as the form of
|
// which listens to the addr parameter which as the form of
|
||||||
// host:port
|
// host:port
|
||||||
//
|
//
|
||||||
// It panics on error if you need a func to return an error use the ListenTLSWithErr
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
|
// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
|
||||||
func ListenTLS(addr string, certFile string, keyFile string) {
|
func ListenTLS(addr string, certFile string, keyFile string) {
|
||||||
Default.ListenTLS(addr, certFile, keyFile)
|
Default.ListenTLS(addr, certFile, keyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenTLSWithErr Starts a https server with certificates,
|
|
||||||
// if you use this method the requests of the form of 'http://' will fail
|
|
||||||
// only https:// connections are allowed
|
|
||||||
// which listens to the addr parameter which as the form of
|
|
||||||
// host:port
|
|
||||||
//
|
|
||||||
// It returns an error you are responsible how to handle this
|
|
||||||
// if you need a func to panic on error use the ListenTLS
|
|
||||||
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
|
|
||||||
func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error {
|
|
||||||
if certFile == "" || keyFile == "" {
|
|
||||||
return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL")
|
|
||||||
}
|
|
||||||
s.Config.Server.ListeningAddr = addr
|
|
||||||
s.Config.Server.CertFile = certFile
|
|
||||||
s.Config.Server.KeyFile = keyFile
|
|
||||||
|
|
||||||
return s.openServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenTLS Starts a https server with certificates,
|
// ListenTLS Starts a https server with certificates,
|
||||||
// if you use this method the requests of the form of 'http://' will fail
|
// if you use this method the requests of the form of 'http://' will fail
|
||||||
// only https:// connections are allowed
|
// only https:// connections are allowed
|
||||||
// which listens to the addr parameter which as the form of
|
// which listens to the addr parameter which as the form of
|
||||||
// host:port
|
// host:port
|
||||||
//
|
//
|
||||||
// It panics on error if you need a func to return an error use the ListenTLSWithErr
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
|
// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
|
||||||
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
|
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
|
||||||
s.Must(s.ListenTLSWithErr(addr, certFile, keyFile))
|
if certFile == "" || keyFile == "" {
|
||||||
}
|
s.Logger.Panic("You should provide certFile and keyFile for TLS/SSL")
|
||||||
|
}
|
||||||
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
|
s.Must(s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}))
|
||||||
// returns an error if something bad happens when trying to listen to
|
|
||||||
func ListenUNIXWithErr(addr string, mode os.FileMode) error {
|
|
||||||
return Default.ListenUNIXWithErr(addr, mode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
||||||
// panics on error
|
//
|
||||||
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
|
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
|
||||||
func ListenUNIX(addr string, mode os.FileMode) {
|
func ListenUNIX(addr string, mode os.FileMode) {
|
||||||
Default.ListenUNIX(addr, mode)
|
Default.ListenUNIX(addr, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
|
|
||||||
// returns an error if something bad happens when trying to listen to
|
|
||||||
func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error {
|
|
||||||
s.Config.Server.ListeningAddr = addr
|
|
||||||
s.Config.Server.Mode = mode
|
|
||||||
return s.openServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
||||||
// panics on error
|
//
|
||||||
|
// It panics on error if you need a func to return an error, use the ListenTo
|
||||||
|
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
|
||||||
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
|
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
|
||||||
s.Must(s.ListenUNIXWithErr(addr, mode))
|
s.Must(ListenTo(config.Server{ListeningAddr: addr, Mode: mode}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecondaryListen starts a server which listens to this station
|
// ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
||||||
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
|
||||||
//
|
|
||||||
// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station
|
|
||||||
//
|
|
||||||
// receives one parameter which is the config.Server for the new server
|
|
||||||
// returns the new standalone server( you can close this server by the returning reference)
|
|
||||||
//
|
|
||||||
// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions.
|
|
||||||
//
|
|
||||||
// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen.
|
|
||||||
func SecondaryListen(cfg config.Server) *Server {
|
|
||||||
return Default.SecondaryListen(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondaryListen starts a server which listens to this station
|
|
||||||
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
|
|
||||||
//
|
|
||||||
// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station
|
|
||||||
//
|
|
||||||
// receives one parameter which is the config.Server for the new server
|
|
||||||
// returns the new standalone server( you can close this server by the returning reference)
|
|
||||||
//
|
|
||||||
// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions.
|
|
||||||
//
|
|
||||||
// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen.
|
|
||||||
func (s *Framework) SecondaryListen(cfg config.Server) *Server {
|
|
||||||
srv := newServer(&cfg)
|
|
||||||
// add a post listen event to start this server after the previous started
|
|
||||||
s.Plugins.Add(PostListenFunc(func(*Framework) {
|
|
||||||
go func() { // goroutine in order to not block any runtime post listeners
|
|
||||||
srv.Handler = s.HTTPServer.Handler
|
|
||||||
if err := srv.Open(); err == nil {
|
|
||||||
ch := make(chan os.Signal)
|
|
||||||
<-ch
|
|
||||||
srv.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}))
|
|
||||||
|
|
||||||
return srv
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
|
||||||
func NoListen(optionalAddr ...string) *Server {
|
|
||||||
return Default.NoListen(optionalAddr...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
|
||||||
// initializes the whole framework but server doesn't listens to a specific net.Listener
|
// initializes the whole framework but server doesn't listens to a specific net.Listener
|
||||||
func (s *Framework) NoListen(optionalAddr ...string) *Server {
|
// it is not blocking the app
|
||||||
return s.justServe(optionalAddr...)
|
func ListenVirtual(optionalAddr ...string) *Server {
|
||||||
|
return Default.ListenVirtual(optionalAddr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseWithErr terminates the server and returns an error if any
|
// ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
|
||||||
func CloseWithErr() error {
|
// initializes the whole framework but server doesn't listens to a specific net.Listener
|
||||||
return Default.CloseWithErr()
|
// it is not blocking the app
|
||||||
|
func (s *Framework) ListenVirtual(optionalAddr ...string) *Server {
|
||||||
|
s.Config.DisableBanner = true
|
||||||
|
cfg := config.DefaultServer()
|
||||||
|
|
||||||
|
if len(optionalAddr) > 0 && optionalAddr[0] != "" {
|
||||||
|
cfg.ListeningAddr = optionalAddr[0]
|
||||||
|
}
|
||||||
|
cfg.Virtual = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.Must(s.ListenTo(cfg))
|
||||||
|
}()
|
||||||
|
|
||||||
|
if ok := <-s.Available; !ok {
|
||||||
|
s.Logger.Panic("Unexpected error:Virtual server cannot start, please report this as bug!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
close(s.Available)
|
||||||
|
return s.Servers.Main()
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close terminates the server and panic if error occurs
|
// Close terminates all the registered servers and returns an error if any
|
||||||
func Close() {
|
// if you want to panic on this error use the iris.Must(iris.Close())
|
||||||
Default.Close()
|
func Close() error {
|
||||||
|
return Default.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseWithErr terminates the server and returns an error if any
|
// Close terminates all the registered servers and returns an error if any
|
||||||
func (s *Framework) CloseWithErr() error {
|
// if you want to panic on this error use the iris.Must(iris.Close())
|
||||||
return s.closeServer()
|
func (s *Framework) Close() error {
|
||||||
}
|
s.Plugins.DoPreClose(s)
|
||||||
|
s.Available = make(chan bool)
|
||||||
//Close terminates the server and panic if error occurs
|
return s.Servers.CloseAll()
|
||||||
func (s *Framework) Close() {
|
|
||||||
s.Must(s.CloseWithErr())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustUse registers Handler middleware to the beginning, prepends them instead of append
|
// MustUse registers Handler middleware to the beginning, prepends them instead of append
|
||||||
|
@ -541,13 +650,13 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
srv := s.Servers.Main()
|
||||||
scheme := "http://"
|
scheme := "http://"
|
||||||
if s.HTTPServer.IsSecure() {
|
if srv.IsSecure() {
|
||||||
scheme = "https://"
|
scheme = "https://"
|
||||||
}
|
}
|
||||||
|
|
||||||
host := s.HTTPServer.VirtualHost()
|
host := srv.VirtualHost()
|
||||||
arguments := args[0:]
|
arguments := args[0:]
|
||||||
|
|
||||||
// join arrays as arguments
|
// join arrays as arguments
|
||||||
|
@ -608,21 +717,35 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{},
|
||||||
// NewTester Prepares and returns a new test framework based on the api
|
// NewTester Prepares and returns a new test framework based on the api
|
||||||
// is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T)
|
// is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T)
|
||||||
func NewTester(api *Framework, t *testing.T) *httpexpect.Expect {
|
func NewTester(api *Framework, t *testing.T) *httpexpect.Expect {
|
||||||
api.Config.DisableBanner = true
|
srv := api.Servers.Main()
|
||||||
if !api.HTTPServer.IsListening() { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app
|
if srv == nil { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app
|
||||||
api.NoListen()
|
srv = api.ListenVirtual(api.Config.Tester.ListeningAddr)
|
||||||
if ok := <-api.Available; !ok {
|
|
||||||
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
|
|
||||||
}
|
|
||||||
close(api.Available)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := api.HTTPServer.Handler
|
opened := api.Servers.GetAllOpened()
|
||||||
|
h := srv.Handler
|
||||||
|
baseURL := srv.FullHost()
|
||||||
|
if len(opened) > 1 {
|
||||||
|
baseURL = ""
|
||||||
|
//we have more than one server, so we will create a handler here and redirect by registered listening addresses
|
||||||
|
h = func(reqCtx *fasthttp.RequestCtx) {
|
||||||
|
for _, s := range opened {
|
||||||
|
if strings.HasPrefix(reqCtx.URI().String(), s.FullHost()) { // yes on :80 should be passed :80 also, this is inneed for multiserver testing
|
||||||
|
s.Handler(reqCtx)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.Config.Tester.ExplicitURL {
|
||||||
|
baseURL = ""
|
||||||
|
}
|
||||||
|
|
||||||
testConfiguration := httpexpect.Config{
|
testConfiguration := httpexpect.Config{
|
||||||
BaseURL: api.HTTPServer.FullHost(),
|
BaseURL: baseURL,
|
||||||
Client: &http.Client{
|
Client: &http.Client{
|
||||||
Transport: httpexpect.NewFastBinder(handler),
|
Transport: httpexpect.NewFastBinder(h),
|
||||||
Jar: httpexpect.NewJar(),
|
Jar: httpexpect.NewJar(),
|
||||||
},
|
},
|
||||||
Reporter: httpexpect.NewAssertReporter(t),
|
Reporter: httpexpect.NewAssertReporter(t),
|
||||||
|
@ -644,7 +767,10 @@ func Tester(t *testing.T) *httpexpect.Expect {
|
||||||
|
|
||||||
// Tester returns the test framework for this iris insance
|
// Tester returns the test framework for this iris insance
|
||||||
func (s *Framework) Tester(t *testing.T) *httpexpect.Expect {
|
func (s *Framework) Tester(t *testing.T) *httpexpect.Expect {
|
||||||
return s.tester(t)
|
if s.testFramework == nil {
|
||||||
|
s.testFramework = NewTester(s, t)
|
||||||
|
}
|
||||||
|
return s.testFramework
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
|
@ -652,12 +778,56 @@ func (s *Framework) Tester(t *testing.T) *httpexpect.Expect {
|
||||||
// ----------------------------------MuxAPI implementation------------------------------
|
// ----------------------------------MuxAPI implementation------------------------------
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
|
type (
|
||||||
|
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
|
||||||
|
RouteNameFunc func(string)
|
||||||
|
// MuxAPI the visible api for the serveMux
|
||||||
|
MuxAPI interface {
|
||||||
|
Party(string, ...HandlerFunc) MuxAPI
|
||||||
|
// middleware serial, appending
|
||||||
|
Use(...Handler)
|
||||||
|
UseFunc(...HandlerFunc)
|
||||||
|
|
||||||
type muxAPI struct {
|
// main handlers
|
||||||
mux *serveMux
|
Handle(string, string, ...Handler) RouteNameFunc
|
||||||
relativePath string
|
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
|
||||||
middleware Middleware
|
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
|
||||||
}
|
H_(string, string, func(context.IContext)) func(string)
|
||||||
|
API(string, HandlerAPI, ...HandlerFunc)
|
||||||
|
|
||||||
|
// http methods
|
||||||
|
Get(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Post(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Put(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Delete(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Connect(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Head(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Options(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Patch(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Trace(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Any(string, ...HandlerFunc)
|
||||||
|
|
||||||
|
// static content
|
||||||
|
StaticHandler(string, int, bool, bool, []string) HandlerFunc
|
||||||
|
Static(string, string, int) RouteNameFunc
|
||||||
|
StaticFS(string, string, int) RouteNameFunc
|
||||||
|
StaticWeb(string, string, int) RouteNameFunc
|
||||||
|
StaticServe(string, ...string) RouteNameFunc
|
||||||
|
StaticContent(string, string, []byte) func(string)
|
||||||
|
Favicon(string, ...string) RouteNameFunc
|
||||||
|
|
||||||
|
// templates
|
||||||
|
Layout(string) MuxAPI // returns itself
|
||||||
|
}
|
||||||
|
|
||||||
|
muxAPI struct {
|
||||||
|
mux *serveMux
|
||||||
|
relativePath string
|
||||||
|
middleware Middleware
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ MuxAPI = &muxAPI{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..'
|
// errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..'
|
||||||
|
@ -666,8 +836,6 @@ var (
|
||||||
errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
|
errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ MuxAPI = &muxAPI{}
|
|
||||||
|
|
||||||
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
|
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
|
||||||
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
|
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
|
||||||
func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
|
func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
|
||||||
|
|
14
iris_test.go
Normal file
14
iris_test.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
/*
|
||||||
|
The most iris.go file implementation tested at other files like context_test, http_test, the untested are the Static methods, the favicon and some interfaces, which I already
|
||||||
|
tested them on production and I don't expect unexpected behavior but if you think we need more:
|
||||||
|
|
||||||
|
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Notes:
|
||||||
|
//
|
||||||
|
// We use Default := New() via initDefault() and not api := New() neither just Default. because we want to cover as much code as possible
|
||||||
|
// The tests are mostly end-to-end, except some features like plugins.
|
||||||
|
//
|
10
plugin.go
10
plugin.go
|
@ -214,11 +214,13 @@ type pluginContainer struct {
|
||||||
customEvents map[string][]func()
|
customEvents map[string][]func()
|
||||||
downloader *pluginDownloadManager
|
downloader *pluginDownloadManager
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add activates the plugins and if succeed then adds it to the activated plugins list
|
// Add activates the plugins and if succeed then adds it to the activated plugins list
|
||||||
func (p *pluginContainer) Add(plugins ...Plugin) error {
|
func (p *pluginContainer) Add(plugins ...Plugin) error {
|
||||||
for _, plugin := range plugins {
|
for _, plugin := range plugins {
|
||||||
|
|
||||||
if p.activatedPlugins == nil {
|
if p.activatedPlugins == nil {
|
||||||
p.activatedPlugins = make([]Plugin, 0)
|
p.activatedPlugins = make([]Plugin, 0)
|
||||||
}
|
}
|
||||||
|
@ -232,10 +234,16 @@ func (p *pluginContainer) Add(plugins ...Plugin) error {
|
||||||
}
|
}
|
||||||
// Activate the plugin, if no error then add it to the plugins
|
// Activate the plugin, if no error then add it to the plugins
|
||||||
if pluginObj, ok := plugin.(pluginActivate); ok {
|
if pluginObj, ok := plugin.(pluginActivate); ok {
|
||||||
err := pluginObj.Activate(p)
|
tempPluginContainer := *p
|
||||||
|
err := pluginObj.Activate(&tempPluginContainer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errPluginActivate.Format(pName, err.Error())
|
return errPluginActivate.Format(pName, err.Error())
|
||||||
}
|
}
|
||||||
|
tempActivatedPluginsLen := len(tempPluginContainer.activatedPlugins)
|
||||||
|
if tempActivatedPluginsLen != len(p.activatedPlugins)+tempActivatedPluginsLen+1 { // see test: plugin_test.go TestPluginActivate && TestPluginActivationError
|
||||||
|
p.activatedPlugins = tempPluginContainer.activatedPlugins
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All ok, add it to the plugins list
|
// All ok, add it to the plugins list
|
||||||
|
|
200
plugin_test.go
Normal file
200
plugin_test.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Contains tests for plugin, no end-to-end, just local-object tests, these are enoguh for now.
|
||||||
|
|
||||||
|
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testPluginExDescription = "Description for My test plugin"
|
||||||
|
testPluginExName = "My test plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testPluginEx struct {
|
||||||
|
named, activated, descriptioned bool
|
||||||
|
prelistenran, postlistenran, precloseran bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) GetName() string {
|
||||||
|
fmt.Println("GetName Struct")
|
||||||
|
t.named = true
|
||||||
|
return testPluginExName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) GetDescription() string {
|
||||||
|
fmt.Println("GetDescription Struct")
|
||||||
|
t.descriptioned = true
|
||||||
|
return testPluginExDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) Activate(p PluginContainer) error {
|
||||||
|
fmt.Println("Activate Struct")
|
||||||
|
t.activated = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) PreListen(*Framework) {
|
||||||
|
fmt.Println("PreListen Struct")
|
||||||
|
t.prelistenran = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) PostListen(*Framework) {
|
||||||
|
fmt.Println("PostListen Struct")
|
||||||
|
t.postlistenran = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testPluginEx) PreClose(*Framework) {
|
||||||
|
fmt.Println("PreClose Struct")
|
||||||
|
t.precloseran = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExamplePlugins_Add() {
|
||||||
|
initDefault()
|
||||||
|
Plugins.Add(PreListenFunc(func(*Framework) {
|
||||||
|
fmt.Println("PreListen Func")
|
||||||
|
}))
|
||||||
|
|
||||||
|
Plugins.Add(PostListenFunc(func(*Framework) {
|
||||||
|
fmt.Println("PostListen Func")
|
||||||
|
}))
|
||||||
|
|
||||||
|
Plugins.Add(PreCloseFunc(func(*Framework) {
|
||||||
|
fmt.Println("PreClose Func")
|
||||||
|
}))
|
||||||
|
|
||||||
|
myplugin := &testPluginEx{}
|
||||||
|
Plugins.Add(myplugin)
|
||||||
|
desc := Plugins.GetDescription(myplugin)
|
||||||
|
fmt.Println(desc)
|
||||||
|
|
||||||
|
ListenVirtual()
|
||||||
|
Close()
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// GetName Struct
|
||||||
|
// Activate Struct
|
||||||
|
// GetDescription Struct
|
||||||
|
// Description for My test plugin
|
||||||
|
// PreListen Func
|
||||||
|
// PreListen Struct
|
||||||
|
// PostListen Func
|
||||||
|
// PostListen Struct
|
||||||
|
// PreClose Func
|
||||||
|
// PreClose Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a plugin has GetName, then it should be registered only one time, the name exists for that reason, it's like unique ID
|
||||||
|
func TestPluginDublicateName(t *testing.T) {
|
||||||
|
var plugins pluginContainer
|
||||||
|
firstNamedPlugin := &testPluginEx{}
|
||||||
|
sameNamedPlugin := &testPluginEx{}
|
||||||
|
// err := plugins.Add(firstNamedPlugin, sameNamedPlugin) or
|
||||||
|
err := plugins.Add(firstNamedPlugin)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error when adding a plugin with name: %s", testPluginExName)
|
||||||
|
}
|
||||||
|
err = plugins.Add(sameNamedPlugin)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected an error because of dublicate named plugin!")
|
||||||
|
}
|
||||||
|
if len(plugins.activatedPlugins) != 1 {
|
||||||
|
t.Fatalf("Expected: %d activated plugin but we got: %d", 1, len(plugins.activatedPlugins))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testPluginActivationType struct {
|
||||||
|
shouldError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testPluginActivationType) Activate(p PluginContainer) error {
|
||||||
|
p.Add(&testPluginEx{})
|
||||||
|
if t.shouldError {
|
||||||
|
return fmt.Errorf("An error happens, this plugin and the added plugins by this plugin should not be registered")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginActivate(t *testing.T) {
|
||||||
|
var plugins pluginContainer
|
||||||
|
myplugin := testPluginActivationType{shouldError: false}
|
||||||
|
plugins.Add(myplugin)
|
||||||
|
|
||||||
|
if len(plugins.activatedPlugins) != 2 { // 2 because it registeres a second plugin also
|
||||||
|
t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any error returned from the Activate plugin's method, then this plugin and the plugins it registers should not be registered at all
|
||||||
|
func TestPluginActivationError(t *testing.T) {
|
||||||
|
var plugins pluginContainer
|
||||||
|
myplugin := testPluginActivationType{shouldError: true}
|
||||||
|
plugins.Add(myplugin)
|
||||||
|
|
||||||
|
if len(plugins.activatedPlugins) > 0 {
|
||||||
|
t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginEvents(t *testing.T) {
|
||||||
|
var plugins pluginContainer
|
||||||
|
var prelistenran, postlistenran, precloseran bool
|
||||||
|
|
||||||
|
plugins.Add(PreListenFunc(func(*Framework) {
|
||||||
|
prelistenran = true
|
||||||
|
}))
|
||||||
|
|
||||||
|
plugins.Add(PostListenFunc(func(*Framework) {
|
||||||
|
postlistenran = true
|
||||||
|
}))
|
||||||
|
|
||||||
|
plugins.Add(PreCloseFunc(func(*Framework) {
|
||||||
|
precloseran = true
|
||||||
|
}))
|
||||||
|
|
||||||
|
myplugin := &testPluginEx{}
|
||||||
|
plugins.Add(myplugin)
|
||||||
|
if len(plugins.activatedPlugins) != 4 {
|
||||||
|
t.Fatalf("Expected: %d plugins to be registed but we got: %d", 4, len(plugins.activatedPlugins))
|
||||||
|
}
|
||||||
|
desc := plugins.GetDescription(myplugin)
|
||||||
|
if desc != testPluginExDescription {
|
||||||
|
t.Fatalf("Expected: %s as Description of the plugin but got: %s", testPluginExDescription, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.DoPreListen(nil)
|
||||||
|
plugins.DoPostListen(nil)
|
||||||
|
plugins.DoPreClose(nil)
|
||||||
|
|
||||||
|
if !prelistenran {
|
||||||
|
t.Fatalf("Expected to run PreListen Func but it doesnt!")
|
||||||
|
}
|
||||||
|
if !postlistenran {
|
||||||
|
t.Fatalf("Expected to run PostListen Func but it doesnt!")
|
||||||
|
}
|
||||||
|
if !precloseran {
|
||||||
|
t.Fatalf("Expected to run PostListen Func but it doesnt!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !myplugin.named {
|
||||||
|
t.Fatalf("Plugin should be named with: %s!", testPluginExName)
|
||||||
|
}
|
||||||
|
if !myplugin.activated {
|
||||||
|
t.Fatalf("Plugin should be activated but it's not!")
|
||||||
|
}
|
||||||
|
if !myplugin.prelistenran {
|
||||||
|
t.Fatalf("Expected to run PreListen Struct but it doesnt!")
|
||||||
|
}
|
||||||
|
if !myplugin.postlistenran {
|
||||||
|
t.Fatalf("Expected to run PostListen Struct but it doesnt!")
|
||||||
|
}
|
||||||
|
if !myplugin.precloseran {
|
||||||
|
t.Fatalf("Expected to run PostListen Struct but it doesnt!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,15 +19,11 @@ var _ store.IStore = &Store{}
|
||||||
|
|
||||||
// GetAll returns all values
|
// GetAll returns all values
|
||||||
func (s *Store) GetAll() map[string]interface{} {
|
func (s *Store) GetAll() map[string]interface{} {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.values
|
return s.values
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||||
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
for key := range s.values {
|
for key := range s.values {
|
||||||
cb(key, s.values[key])
|
cb(key, s.values[key])
|
||||||
}
|
}
|
||||||
|
@ -36,9 +32,7 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||||
// Get returns the value of an entry by its key
|
// Get returns the value of an entry by its key
|
||||||
func (s *Store) Get(key string) interface{} {
|
func (s *Store) Get(key string) interface{} {
|
||||||
Provider.Update(s.sid)
|
Provider.Update(s.sid)
|
||||||
s.mu.Lock()
|
|
||||||
if value, found := s.values[key]; found {
|
if value, found := s.values[key]; found {
|
||||||
s.mu.Unlock()
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
|
@ -81,15 +81,11 @@ func (s *Store) update() {
|
||||||
|
|
||||||
// GetAll returns all values
|
// GetAll returns all values
|
||||||
func (s *Store) GetAll() map[string]interface{} {
|
func (s *Store) GetAll() map[string]interface{} {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.values
|
return s.values
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||||
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
for key := range s.values {
|
for key := range s.values {
|
||||||
cb(key, s.values[key])
|
cb(key, s.values[key])
|
||||||
}
|
}
|
||||||
|
@ -98,12 +94,10 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||||
// Get returns the value of an entry by its key
|
// Get returns the value of an entry by its key
|
||||||
func (s *Store) Get(key string) interface{} {
|
func (s *Store) Get(key string) interface{} {
|
||||||
Provider.Update(s.sid)
|
Provider.Update(s.sid)
|
||||||
s.mu.Lock()
|
|
||||||
if value, found := s.values[key]; found {
|
if value, found := s.values[key]; found {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user