diff --git a/HISTORY.md b/HISTORY.md
index 608ef6e9..699bd9cb 100644
--- a/HISTORY.md
+++ b/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`.
+## 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
**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):
diff --git a/README.md b/README.md
index cff7b320..01ebcd90 100644
--- a/README.md
+++ b/README.md
@@ -139,7 +139,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v3.0.0-rc.4**
+Current: **v3.0.0-pre.release**
> Iris is an active project
@@ -185,7 +185,7 @@ License can be found [here](LICENSE).
[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]: 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
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris
diff --git a/config/iris.go b/config/iris.go
index c7d7310b..247356a2 100644
--- a/config/iris.go
+++ b/config/iris.go
@@ -1,15 +1,11 @@
package config
-import (
- "github.com/imdario/mergo"
- "github.com/valyala/fasthttp"
-)
+import "github.com/imdario/mergo"
// Default values for base Iris conf
const (
DefaultDisablePathCorrection = false
DefaultDisablePathEscape = false
- DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
)
type (
@@ -54,13 +50,6 @@ type (
// Default is false
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
// Default is empty, if you set it to a $path, these routes will handled:
// $path/cmdline
@@ -103,13 +92,6 @@ type (
// Websocket contains the configs for Websocket's server integration
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 Tester
}
@@ -142,14 +124,12 @@ func Default() Iris {
DisablePathCorrection: DefaultDisablePathCorrection,
DisablePathEscape: DefaultDisablePathEscape,
DisableBanner: false,
- MaxRequestBodySize: DefaultMaxRequestBodySize,
ProfilePath: "",
Logger: DefaultLogger(),
Sessions: DefaultSessions(),
Render: DefaultRender(),
Websocket: DefaultWebsocket(),
- Server: DefaultServer(),
- Tester: Tester{Debug: false},
+ Tester: DefaultTester(),
}
}
diff --git a/config/server.go b/config/server.go
index b580fce5..29be03bc 100644
--- a/config/server.go
+++ b/config/server.go
@@ -5,13 +5,17 @@ import (
"strconv"
"github.com/imdario/mergo"
+ "github.com/kataras/fasthttp"
)
+// Default values for base Server conf
const (
// DefaultServerHostname returns the default hostname which is 127.0.0.1
DefaultServerHostname = "127.0.0.1"
// DefaultServerPort returns the default port which is 8080
DefaultServerPort = 8080
+ // DefaultMaxRequestBodySize is 4MB
+ DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
)
var (
@@ -30,6 +34,12 @@ type Server struct {
KeyFile string
// Mode this is for unix only
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)
//
// 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
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
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
@@ -57,3 +70,12 @@ func (c Server) Merge(cfg []Server) (config Server) {
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
+}
diff --git a/config/sessions.go b/config/sessions.go
index 85f56f9f..c062bacf 100644
--- a/config/sessions.go
+++ b/config/sessions.go
@@ -34,7 +34,7 @@ type (
Redis struct {
// Network "tcp"
Network string
- // Addr "127.0.01:6379"
+ // Addr "127.0.0.1:6379"
Addr string
// Password string .If no password then no 'AUTH'. Default ""
Password string
diff --git a/config/tester.go b/config/tester.go
index 11488068..c933a366 100644
--- a/config/tester.go
+++ b/config/tester.go
@@ -2,5 +2,13 @@ package config
// Tester configuration
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}
}
diff --git a/context.go b/context.go
index 201491c5..1136c1c6 100644
--- a/context.go
+++ b/context.go
@@ -1,6 +1,5 @@
/*
-Context.go Implements: ./context/context.go ,
-files: context_renderer.go, context_storage.go, context_request.go, context_response.go
+Context.go Implements: ./context/context.go
*/
package iris
@@ -56,6 +55,9 @@ const (
stopExecutionPosition = 255
// used inside GetFlash to store the lifetime request 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
@@ -218,7 +220,7 @@ func (ctx *Context) HostString() string {
func (ctx *Context) VirtualHostname() string {
realhost := ctx.HostString()
hostname := realhost
- virtualhost := ctx.framework.HTTPServer.VirtualHostname()
+ virtualhost := ctx.framework.Servers.Main().VirtualHostname()
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
hostname = hostname[0:portIdx]
@@ -284,13 +286,13 @@ func (ctx *Context) RequestHeader(k string) string {
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
}
-// PostFormValue returns a single value from post request's data
-func (ctx *Context) PostFormValue(name string) string {
+// FormValueString returns a single value, as string, from post request's data
+func (ctx *Context) FormValueString(name string) string {
return string(ctx.FormValue(name))
}
-// PostFormMulti returns a slice of string from post request's data
-func (ctx *Context) PostFormMulti(name string) []string {
+// FormValues returns a slice of string from post request's data
+func (ctx *Context) FormValues(name string) []string {
arrBytes := ctx.PostArgs().PeekMulti(name)
arrStr := make([]string, len(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.
//
// 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 {
if path == "" {
return ""
@@ -419,7 +421,7 @@ func (ctx *Context) SetHeader(k string, v string) {
// 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
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 {
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)
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
@@ -687,6 +693,28 @@ func (ctx *Context) Set(key string, value interface{}) {
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
// returns empty string if nothing was found
func (ctx *Context) GetCookie(name string) (val string) {
@@ -719,19 +747,64 @@ func (ctx *Context) RemoveCookie(name string) {
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
// returns the value as string and an error
//
// if the cookie doesn't exists the string is empty and the error is filled
// 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
storeExists := false
+
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
m, isMap := messages.(map[string]string)
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
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 == "" {
- err = 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 "", errFlashNotFound.Return()
}
- 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)
// the value will be available on the NEXT request
func (ctx *Context) SetFlash(key string, value string) {
c := fasthttp.AcquireCookie()
- c.SetKey(key)
+ c.SetKey(flashMessageCookiePrefix + key)
c.SetValue(base64.URLEncoding.EncodeToString([]byte(value)))
c.SetPath("/")
c.SetHTTPOnly(true)
diff --git a/context/context.go b/context/context.go
index 2116a83e..166455dc 100644
--- a/context/context.go
+++ b/context/context.go
@@ -14,6 +14,10 @@ type (
// IContext the interface for the iris/context
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
IContext interface {
+ // deprecated Start
+ PostFormValue(string) string
+ PostFormMulti(string) []string
+ // deprecated End
Param(string) string
ParamInt(string) (int, error)
ParamInt64(string) (int64, error)
@@ -29,8 +33,8 @@ type (
RequestIP() string
RemoteAddr() string
RequestHeader(k string) string
- PostFormValue(string) string
- PostFormMulti(string) []string
+ FormValueString(string) string
+ FormValues(string) []string
SetStatusCode(int)
SetContentType(string)
SetHeader(string, string)
@@ -66,9 +70,11 @@ type (
GetString(string) string
GetInt(string) int
Set(string, interface{})
+ VisitAllCookies(func(string, string))
SetCookie(*fasthttp.Cookie)
SetCookieKV(string, string)
RemoveCookie(string)
+ GetFlashes() map[string]string
GetFlash(string) (string, error)
SetFlash(string, string)
Session() store.IStore
diff --git a/context_test.go b/context_test.go
new file mode 100644
index 00000000..7045131a
--- /dev/null
+++ b/context_test.go
@@ -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 + `">` + expectedObj.Name + `` + expectedObj.Birth + `` + strconv.Itoa(expectedObj.Stars) + ``
+ 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 + `">` + JSONXMLContents.Name + `` + JSONXMLContents.Birth + `` + strconv.Itoa(JSONXMLContents.Stars) + ``)
+
+ markdownT := e.GET("/markdown").Expect().Status(StatusOK)
+ markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
+ markdownT.Body().Equal("
" + markdownContents[2:] + "
\n")
+
+}
diff --git a/deprecated.go b/deprecated.go
new file mode 100644
index 00000000..4d24938e
--- /dev/null
+++ b/deprecated.go
@@ -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)
+}
diff --git a/http.go b/http.go
index 6d72aaa5..66011974 100644
--- a/http.go
+++ b/http.go
@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"sync"
+ "time"
"github.com/iris-contrib/errors"
"github.com/kataras/iris/config"
@@ -236,35 +237,47 @@ var (
errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
)
-// Server the http server
-type Server struct {
- *fasthttp.Server
- listener net.Listener
- Config *config.Server
- tls bool
- mu sync.Mutex
-}
+type (
+ // Server the http server
+ Server struct {
+ *fasthttp.Server
+ listener net.Listener
+ Config config.Server
+ 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
-func newServer(c *config.Server) *Server {
- s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
+func newServer(cfg config.Server) *Server {
+ s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: cfg}
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
func (s *Server) IsListening() bool {
+ if s == nil {
+ return false
+ }
s.mu.Lock()
defer s.mu.Unlock()
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
func (s *Server) IsSecure() bool {
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
-func (s *Server) Open() error {
+func (s *Server) Open(h fasthttp.RequestHandler) error {
+ if h == nil {
+ return errServerHandlerMissing.Return()
+ }
+
if s.IsListening() {
return errServerAlreadyStarted.Return()
}
@@ -407,10 +424,6 @@ func (s *Server) Open() error {
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
a := s.Config.ListeningAddr
//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
}
+ if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize {
+ s.Server.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
+ }
+
if s.Config.RedirectTo != "" {
// 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())
redirectTo := s.Config.RedirectTo
if path != "/" {
@@ -429,12 +446,19 @@ func (s *Server) Open() error {
}
reqCtx.Redirect(redirectTo, StatusMovedPermanently)
}
+ } else {
+ s.Server.Handler = h
+ }
+
+ if s.Config.Virtual {
+ return nil
}
if s.Config.Mode > 0 {
return s.listenUNIX()
}
return s.listen()
+
}
// Close terminates the server
@@ -447,6 +471,122 @@ func (s *Server) Close() (err error) {
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)
// 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.")
diff --git a/http_test.go b/http_test.go
new file mode 100644
index 00000000..e99f45cf
--- /dev/null
+++ b/http_test.go
@@ -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)
+}
diff --git a/initiatory.go b/initiatory.go
deleted file mode 100644
index 6d238e18..00000000
--- a/initiatory.go
+++ /dev/null
@@ -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
-}
diff --git a/iris.go b/iris.go
index 7eae811d..578fa141 100644
--- a/iris.go
+++ b/iris.go
@@ -61,40 +61,109 @@ import (
"testing"
"time"
+ "sync"
+
"github.com/gavv/httpexpect"
"github.com/iris-contrib/errors"
"github.com/kataras/iris/config"
"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/websocket"
"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 (
// Version of the iris
- Version = "3.0.0-rc.4"
- banner = ` _____ _
+ Version = "3.0.0-pre.release"
+
+ // 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 (
// FrameworkAPI contains the main Iris Public API
FrameworkAPI interface {
MuxAPI
Must(error)
- ListenWithErr(string) error
+ AddServer(config.Server) *Server
+ ListenTo(config.Server) error
Listen(string)
- ListenTLSWithErr(string, string, string) error
ListenTLS(string, string, string)
- ListenUNIXWithErr(string, os.FileMode) error
ListenUNIX(string, os.FileMode)
- SecondaryListen(config.Server) *Server
- NoListen(...string) *Server
- Close()
+ ListenVirtual(...string) *Server
+ Go() error
+ Close() error
// global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also
MustUse(...Handler)
MustUseFunc(...HandlerFunc)
@@ -108,56 +177,138 @@ type (
Tester(t *testing.T) *httpexpect.Expect
}
- // 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)
-
- // main handlers
- Handle(string, string, ...Handler) RouteNameFunc
- HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
- // 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
+ // Framework is our God |\| Google.Search('Greek mythology Iris')
+ //
+ // Implements the FrameworkAPI
+ Framework struct {
+ *muxAPI
+ rest *rest.Render
+ templates *template.Template
+ sessions *sessions.Manager
+ // fields which are useful to the user/dev
+ // the last added server is the main server
+ Servers *ServerList
+ 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
}
)
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// --------------------------------Framework implementation-----------------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
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
func Must(err error) {
Default.Must(err)
@@ -170,60 +321,74 @@ func (s *Framework) Must(err error) {
}
}
-// ListenWithErr starts the standalone http server
-// which listens to the addr parameter which as the form of
-// host:port
+// AddServer same as .Servers.Add(config.Server) instead
//
-// 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 ListenWithErr(addr string) error {
- return Default.ListenWithErr(addr)
+// 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 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
// which listens to the addr parameter which as the form of
// host:port
//
-// It panics on error if you need a func to return an error use the ListenWithErr
-// ex: iris.Listen(":8080")
+// It panics on error if you need a func to return an error, use the ListenTo
+// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func Listen(addr string) {
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
// which listens to the addr parameter which as the form of
// host:port
//
-// It panics on error if you need a func to return an error use the ListenWithErr
-// ex: iris.Listen(":8080")
+// It panics on error if you need a func to return an error, use the ListenTo
+// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func (s *Framework) Listen(addr string) {
- s.Must(s.ListenWithErr(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)
+ s.Must(s.ListenTo(config.Server{ListeningAddr: addr}))
}
// 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
// host:port
//
-// It panics on error if you need a func to return an error use the ListenTLSWithErr
-// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
+// It panics on error if you need a func to return an error, use the ListenTo
+// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
func ListenTLS(addr string, certFile string, keyFile string) {
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,
// 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 panics on error if you need a func to return an error use the ListenTLSWithErr
-// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
+// It panics on error if you need a func to return an error, use the ListenTo
+// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
- s.Must(s.ListenTLSWithErr(addr, certFile, keyFile))
-}
-
-// 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 ListenUNIXWithErr(addr string, mode os.FileMode) error {
- return Default.ListenUNIXWithErr(addr, mode)
+ if certFile == "" || keyFile == "" {
+ s.Logger.Panic("You should provide certFile and keyFile for TLS/SSL")
+ }
+ s.Must(s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}))
}
// 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) {
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
-// 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) {
- s.Must(s.ListenUNIXWithErr(addr, mode))
+ s.Must(ListenTo(config.Server{ListeningAddr: addr, Mode: mode}))
}
-// 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 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
+// ListenVirtual 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
-func (s *Framework) NoListen(optionalAddr ...string) *Server {
- return s.justServe(optionalAddr...)
+// it is not blocking the app
+func ListenVirtual(optionalAddr ...string) *Server {
+ return Default.ListenVirtual(optionalAddr...)
}
-// CloseWithErr terminates the server and returns an error if any
-func CloseWithErr() error {
- return Default.CloseWithErr()
+// ListenVirtual 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
+// 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
-func Close() {
- Default.Close()
+// Close terminates all the registered servers and returns an error if any
+// if you want to panic on this error use the iris.Must(iris.Close())
+func Close() error {
+ return Default.Close()
}
-// CloseWithErr terminates the server and returns an error if any
-func (s *Framework) CloseWithErr() error {
- return s.closeServer()
-}
-
-//Close terminates the server and panic if error occurs
-func (s *Framework) Close() {
- s.Must(s.CloseWithErr())
+// Close terminates all the registered servers and returns an error if any
+// if you want to panic on this error use the iris.Must(iris.Close())
+func (s *Framework) Close() error {
+ s.Plugins.DoPreClose(s)
+ s.Available = make(chan bool)
+ return s.Servers.CloseAll()
}
// 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 {
return
}
-
+ srv := s.Servers.Main()
scheme := "http://"
- if s.HTTPServer.IsSecure() {
+ if srv.IsSecure() {
scheme = "https://"
}
- host := s.HTTPServer.VirtualHost()
+ host := srv.VirtualHost()
arguments := args[0:]
// 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
// 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 {
- api.Config.DisableBanner = true
- 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
- api.NoListen()
- if ok := <-api.Available; !ok {
- t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
- }
- close(api.Available)
+ srv := api.Servers.Main()
+ 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
+ srv = api.ListenVirtual(api.Config.Tester.ListeningAddr)
}
- 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{
- BaseURL: api.HTTPServer.FullHost(),
+ BaseURL: baseURL,
Client: &http.Client{
- Transport: httpexpect.NewFastBinder(handler),
+ Transport: httpexpect.NewFastBinder(h),
Jar: httpexpect.NewJar(),
},
Reporter: httpexpect.NewAssertReporter(t),
@@ -644,7 +767,10 @@ func Tester(t *testing.T) *httpexpect.Expect {
// Tester returns the test framework for this iris insance
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------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
+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 {
- mux *serveMux
- relativePath string
- middleware Middleware
-}
+ // main handlers
+ Handle(string, string, ...Handler) RouteNameFunc
+ HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
+ // 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 (
// 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")
)
-var _ MuxAPI = &muxAPI{}
-
// 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
func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
diff --git a/iris_test.go b/iris_test.go
new file mode 100644
index 00000000..69504f1c
--- /dev/null
+++ b/iris_test.go
@@ -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.
+//
diff --git a/plugin.go b/plugin.go
index d0a49e72..4f24abec 100644
--- a/plugin.go
+++ b/plugin.go
@@ -214,11 +214,13 @@ type pluginContainer struct {
customEvents map[string][]func()
downloader *pluginDownloadManager
logger *logger.Logger
+ mu sync.Mutex
}
// Add activates the plugins and if succeed then adds it to the activated plugins list
func (p *pluginContainer) Add(plugins ...Plugin) error {
for _, plugin := range plugins {
+
if p.activatedPlugins == nil {
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
if pluginObj, ok := plugin.(pluginActivate); ok {
- err := pluginObj.Activate(p)
+ tempPluginContainer := *p
+ err := pluginObj.Activate(&tempPluginContainer)
if err != nil {
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
diff --git a/plugin_test.go b/plugin_test.go
new file mode 100644
index 00000000..67906e3e
--- /dev/null
+++ b/plugin_test.go
@@ -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!")
+ }
+
+}
diff --git a/sessions/providers/memory/store.go b/sessions/providers/memory/store.go
index b96835f4..40c8d91d 100644
--- a/sessions/providers/memory/store.go
+++ b/sessions/providers/memory/store.go
@@ -19,15 +19,11 @@ var _ store.IStore = &Store{}
// GetAll returns all values
func (s *Store) GetAll() map[string]interface{} {
- s.mu.Lock()
- defer s.mu.Unlock()
return s.values
}
// VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k string, v interface{})) {
- s.mu.Lock()
- defer s.mu.Unlock()
for key := range s.values {
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
func (s *Store) Get(key string) interface{} {
Provider.Update(s.sid)
- s.mu.Lock()
if value, found := s.values[key]; found {
- s.mu.Unlock()
return value
}
s.mu.Unlock()
diff --git a/sessions/providers/redis/redisstore.go b/sessions/providers/redis/redisstore.go
index fa43213c..58a62834 100644
--- a/sessions/providers/redis/redisstore.go
+++ b/sessions/providers/redis/redisstore.go
@@ -81,15 +81,11 @@ func (s *Store) update() {
// GetAll returns all values
func (s *Store) GetAll() map[string]interface{} {
- s.mu.Lock()
- defer s.mu.Unlock()
return s.values
}
// VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k string, v interface{})) {
- s.mu.Lock()
- defer s.mu.Unlock()
for key := range s.values {
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
func (s *Store) Get(key string) interface{} {
Provider.Update(s.sid)
- s.mu.Lock()
if value, found := s.values[key]; found {
s.mu.Unlock()
return value
}
- s.mu.Unlock()
return nil
}