Merge remote-tracking branch 'refs/remotes/origin/dev'

This commit is contained in:
Makis Maropoulos 2016-07-07 02:50:21 +02:00
commit ec5a71e0af
19 changed files with 2172 additions and 547 deletions

View File

@ -2,6 +2,31 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. **How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
## 3.0.0-rc.4 -> 3.0.0-pre.release
- `context.PostFormValue` -> `context.FormValueString`, old func stays until the next revision
- `context.PostFormMulti` -> `context.FormValues` , old func stays until the next revision
- Added `context.VisitAllCookies(func(key,value string))` to visit all your cookies (because `context.Request.Header.VisitAllCookie` has a bug(I can't fix/pr it because the author is away atm))
- Added `context.GetFlashes` to get all available flash messages for a particular request
- Fix flash message removed after the first `GetFlash` call in the same request
**NEW FEATURE**: Built'n support for multi listening servers per iris station, secondary and virtual servers with one-line using the `iris.AddServer` & `iris.Go` to start all servers.
- `iris.SecondaryListen` -> `iris.AddServer`, old func stays until the next revision
- Added `iris.Servers` with this field you can manage your servers very easy
- Added `iris.AddServer/iris.ListenTo/iris.Go`, but funcs like `Listen/ListenTLS/ListenUNIX` will stay forever
- Added `config.Server.Virtual(bool), config.Server.RedirectTo(string) and config.Server.MaxRequestBodySize(int64)`
- Added `iris.Available (channel bool)`
- `iris.HTTPServer` -> `iris.Servers.Main()` to get the main server, which is always the last registered server (if more than one used), old field removed
- `iris.Config.MaxRequestBodySize` -> `config.Server.MaxRequestBodySize`, old field removed
**NEW FEATURE**: Build'n support for your API's end-to-end tests
- Added `tester := iris.Tester(*testing.T)` , look inside: [http_test.go](https://github.com/kataras/iris/blob/master/http_test.go) & [./context_test.go](https://github.com/kataras/iris/blob/master/context_test.go) for `Tester` usage, you can also look inside the [httpexpect's repo](https://github.com/gavv/httpexpect/blob/master/example/iris_test.go) for extended examples with Iris.
## 3.0.0-rc.3 -> 3.0.0-rc.4 ## 3.0.0-rc.3 -> 3.0.0-rc.4
**NEW FEATURE**: **Handlebars** template engine support with all Iris' view engine's functions/helpers support, as requested [here](https://github.com/kataras/iris/issues/239): **NEW FEATURE**: **Handlebars** template engine support with all Iris' view engine's functions/helpers support, as requested [here](https://github.com/kataras/iris/issues/239):

View File

@ -139,7 +139,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning Versioning
------------ ------------
Current: **v3.0.0-rc.4** Current: **v3.0.0-pre.release**
> Iris is an active project > Iris is an active project
@ -185,7 +185,7 @@ License can be found [here](LICENSE).
[Travis]: http://travis-ci.org/kataras/iris [Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE [License]: https://github.com/kataras/iris/blob/master/LICENSE
[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.4-blue.svg?style=flat-square [Release Widget]: https://img.shields.io/badge/release-v3.0.0--pre.release-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases [Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square [Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris [Chat]: https://kataras.rocket.chat/channel/iris

View File

@ -1,15 +1,11 @@
package config package config
import ( import "github.com/imdario/mergo"
"github.com/imdario/mergo"
"github.com/valyala/fasthttp"
)
// Default values for base Iris conf // Default values for base Iris conf
const ( const (
DefaultDisablePathCorrection = false DefaultDisablePathCorrection = false
DefaultDisablePathEscape = false DefaultDisablePathEscape = false
DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
) )
type ( type (
@ -54,13 +50,6 @@ type (
// Default is false // Default is false
DisableBanner bool DisableBanner bool
// MaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 4MB.
MaxRequestBodySize int64
// ProfilePath a the route path, set it to enable http pprof tool // ProfilePath a the route path, set it to enable http pprof tool
// Default is empty, if you set it to a $path, these routes will handled: // Default is empty, if you set it to a $path, these routes will handled:
// $path/cmdline // $path/cmdline
@ -103,13 +92,6 @@ type (
// Websocket contains the configs for Websocket's server integration // Websocket contains the configs for Websocket's server integration
Websocket *Websocket Websocket *Websocket
// Server contains the configs for the http server
// Server configs are the only one which are setted inside base Iris package (from Listen, ListenTLS, ListenUNIX) NO from users
//
// this field is useful only when you need to READ which is the server's address, certfile & keyfile or unix's mode.
//
Server Server
// Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself // Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself
Tester Tester Tester Tester
} }
@ -142,14 +124,12 @@ func Default() Iris {
DisablePathCorrection: DefaultDisablePathCorrection, DisablePathCorrection: DefaultDisablePathCorrection,
DisablePathEscape: DefaultDisablePathEscape, DisablePathEscape: DefaultDisablePathEscape,
DisableBanner: false, DisableBanner: false,
MaxRequestBodySize: DefaultMaxRequestBodySize,
ProfilePath: "", ProfilePath: "",
Logger: DefaultLogger(), Logger: DefaultLogger(),
Sessions: DefaultSessions(), Sessions: DefaultSessions(),
Render: DefaultRender(), Render: DefaultRender(),
Websocket: DefaultWebsocket(), Websocket: DefaultWebsocket(),
Server: DefaultServer(), Tester: DefaultTester(),
Tester: Tester{Debug: false},
} }
} }

View File

@ -5,13 +5,17 @@ import (
"strconv" "strconv"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/kataras/fasthttp"
) )
// Default values for base Server conf
const ( const (
// DefaultServerHostname returns the default hostname which is 127.0.0.1 // DefaultServerHostname returns the default hostname which is 127.0.0.1
DefaultServerHostname = "127.0.0.1" DefaultServerHostname = "127.0.0.1"
// DefaultServerPort returns the default port which is 8080 // DefaultServerPort returns the default port which is 8080
DefaultServerPort = 8080 DefaultServerPort = 8080
// DefaultMaxRequestBodySize is 4MB
DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize
) )
var ( var (
@ -30,6 +34,12 @@ type Server struct {
KeyFile string KeyFile string
// Mode this is for unix only // Mode this is for unix only
Mode os.FileMode Mode os.FileMode
// MaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 4MB.
MaxRequestBodySize int64
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT) // RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
// //
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server // NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
@ -37,11 +47,14 @@ type Server struct {
// //
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2 // example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
RedirectTo string RedirectTo string
// Virtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
Virtual bool
} }
// DefaultServer returns the default configs for the server // DefaultServer returns the default configs for the server
func DefaultServer() Server { func DefaultServer() Server {
return Server{ListeningAddr: DefaultServerAddr} return Server{ListeningAddr: DefaultServerAddr,
MaxRequestBodySize: DefaultMaxRequestBodySize}
} }
// Merge merges the default with the given config and returns the result // Merge merges the default with the given config and returns the result
@ -57,3 +70,12 @@ func (c Server) Merge(cfg []Server) (config Server) {
return return
} }
// MergeSingle merges the default with the given config and returns the result
func (c Server) MergeSingle(cfg Server) (config Server) {
config = cfg
mergo.Merge(&config, c)
return
}

View File

@ -34,7 +34,7 @@ type (
Redis struct { Redis struct {
// Network "tcp" // Network "tcp"
Network string Network string
// Addr "127.0.01:6379" // Addr "127.0.0.1:6379"
Addr string Addr string
// Password string .If no password then no 'AUTH'. Default "" // Password string .If no password then no 'AUTH'. Default ""
Password string Password string

View File

@ -2,5 +2,13 @@ package config
// Tester configuration // Tester configuration
type Tester struct { type Tester struct {
Debug bool ListeningAddr string
ExplicitURL bool
Debug bool
}
// DefaultTester returns the default configuration for a tester
// the ListeningAddr is used as virtual only when no running server is founded
func DefaultTester() Tester {
return Tester{ListeningAddr: "iris-go.com:1993", ExplicitURL: false, Debug: false}
} }

View File

@ -1,6 +1,5 @@
/* /*
Context.go Implements: ./context/context.go , Context.go Implements: ./context/context.go
files: context_renderer.go, context_storage.go, context_request.go, context_response.go
*/ */
package iris package iris
@ -56,6 +55,9 @@ const (
stopExecutionPosition = 255 stopExecutionPosition = 255
// used inside GetFlash to store the lifetime request flash messages // used inside GetFlash to store the lifetime request flash messages
flashMessagesStoreContextKey = "_iris_flash_messages_" flashMessagesStoreContextKey = "_iris_flash_messages_"
flashMessageCookiePrefix = "_iris_flash_message_"
cookieHeaderID = "Cookie: "
cookieHeaderIDLen = len(cookieHeaderID)
) )
// this pool is used everywhere needed in the iris for example inside party-> Static // this pool is used everywhere needed in the iris for example inside party-> Static
@ -218,7 +220,7 @@ func (ctx *Context) HostString() string {
func (ctx *Context) VirtualHostname() string { func (ctx *Context) VirtualHostname() string {
realhost := ctx.HostString() realhost := ctx.HostString()
hostname := realhost hostname := realhost
virtualhost := ctx.framework.HTTPServer.VirtualHostname() virtualhost := ctx.framework.Servers.Main().VirtualHostname()
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 { if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
hostname = hostname[0:portIdx] hostname = hostname[0:portIdx]
@ -284,13 +286,13 @@ func (ctx *Context) RequestHeader(k string) string {
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k)) return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
} }
// PostFormValue returns a single value from post request's data // FormValueString returns a single value, as string, from post request's data
func (ctx *Context) PostFormValue(name string) string { func (ctx *Context) FormValueString(name string) string {
return string(ctx.FormValue(name)) return string(ctx.FormValue(name))
} }
// PostFormMulti returns a slice of string from post request's data // FormValues returns a slice of string from post request's data
func (ctx *Context) PostFormMulti(name string) []string { func (ctx *Context) FormValues(name string) []string {
arrBytes := ctx.PostArgs().PeekMulti(name) arrBytes := ctx.PostArgs().PeekMulti(name)
arrStr := make([]string, len(arrBytes)) arrStr := make([]string, len(arrBytes))
for i, v := range arrBytes { for i, v := range arrBytes {
@ -314,7 +316,7 @@ func (ctx *Context) Subdomain() (subdomain string) {
// use it only for special cases, when the default behavior doesn't suits you. // use it only for special cases, when the default behavior doesn't suits you.
// //
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
/* Credits to Manish Singh @kryptodev for URLEncode */ /* Credits to Manish Singh @kryptodev for URLEncode by post issue share code */
func URLEncode(path string) string { func URLEncode(path string) string {
if path == "" { if path == "" {
return "" return ""
@ -419,7 +421,7 @@ func (ctx *Context) SetHeader(k string, v string) {
// first parameter is the url to redirect // first parameter is the url to redirect
// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery // second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) { func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
httpStatus := StatusFound // temporary redirect httpStatus := StatusFound // a 'temporary-redirect-like' wich works better than for our purpose
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 { if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
httpStatus = statusHeader[0] httpStatus = statusHeader[0]
} }
@ -480,7 +482,11 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{
// Render same as .RenderWithStatus but with status to iris.StatusOK (200) // Render same as .RenderWithStatus but with status to iris.StatusOK (200)
func (ctx *Context) Render(name string, binding interface{}, layout ...string) error { func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
return ctx.RenderWithStatus(StatusOK, name, binding, layout...) errCode := ctx.RequestCtx.Response.StatusCode()
if errCode <= 0 {
errCode = StatusOK
}
return ctx.RenderWithStatus(errCode, name, binding, layout...)
} }
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail // MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
@ -687,6 +693,28 @@ func (ctx *Context) Set(key string, value interface{}) {
ctx.RequestCtx.SetUserValue(key, value) ctx.RequestCtx.SetUserValue(key, value)
} }
// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
//
// Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment.
// This is the reason which this function exists and should be used instead of fasthttp's built'n.
func (ctx *Context) VisitAllCookies(visitor func(key string, value string)) {
// strange bug, this doesnt works also: cookieHeaderContent := ctx.Request.Header.Peek("Cookie")/User-Agent tested also
headerbody := string(ctx.Request.Header.Header())
headerlines := strings.Split(headerbody, "\n")
for _, s := range headerlines {
if len(s) > cookieHeaderIDLen {
if s[0:cookieHeaderIDLen] == cookieHeaderID {
contents := s[cookieHeaderIDLen:]
values := strings.Split(contents, "; ")
for _, s := range values {
keyvalue := strings.SplitN(s, "=", 2)
visitor(keyvalue[0], keyvalue[1])
}
}
}
}
}
// GetCookie returns cookie's value by it's name // GetCookie returns cookie's value by it's name
// returns empty string if nothing was found // returns empty string if nothing was found
func (ctx *Context) GetCookie(name string) (val string) { func (ctx *Context) GetCookie(name string) (val string) {
@ -719,19 +747,64 @@ func (ctx *Context) RemoveCookie(name string) {
ctx.RequestCtx.Response.Header.DelClientCookie(name) ctx.RequestCtx.Response.Header.DelClientCookie(name)
} }
// GetFlashes returns all the flash messages for available for this request
func (ctx *Context) GetFlashes() map[string]string {
// if already taken at least one time, this will be filled
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
if m, isMap := messages.(map[string]string); isMap {
return m
}
} else {
flashMessageFound := false
// else first time, get all flash cookie keys(the prefix will tell us which is a flash message), and after get all one-by-one using the GetFlash.
flashMessageCookiePrefixLen := len(flashMessageCookiePrefix)
ctx.VisitAllCookies(func(key string, value string) {
if len(key) > flashMessageCookiePrefixLen {
if key[0:flashMessageCookiePrefixLen] == flashMessageCookiePrefix {
unprefixedKey := key[flashMessageCookiePrefixLen:]
_, err := ctx.GetFlash(unprefixedKey) // this func will add to the list (flashMessagesStoreContextKey) also
if err == nil {
flashMessageFound = true
}
}
}
})
// if we found at least one flash message then re-execute this function to return the list
if flashMessageFound {
return ctx.GetFlashes()
}
}
return nil
}
func (ctx *Context) decodeFlashCookie(key string) (string, string) {
cookieKey := flashMessageCookiePrefix + key
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(cookieKey))
if cookieValue != "" {
v, e := base64.URLEncoding.DecodeString(cookieValue)
if e == nil {
return cookieKey, string(v)
}
}
return "", ""
}
// GetFlash get a flash message by it's key // GetFlash get a flash message by it's key
// returns the value as string and an error // returns the value as string and an error
// //
// if the cookie doesn't exists the string is empty and the error is filled // if the cookie doesn't exists the string is empty and the error is filled
// after the request's life the value is removed // after the request's life the value is removed
func (ctx *Context) GetFlash(key string) (value string, err error) { func (ctx *Context) GetFlash(key string) (string, error) {
// first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie // first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie
storeExists := false storeExists := false
if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil { if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
m, isMap := messages.(map[string]string) m, isMap := messages.(map[string]string)
if !isMap { if !isMap {
return "", fmt.Errorf("Messages request's store is not a map[string]string. This suppose will never happen, please report this bug.") return "", fmt.Errorf("Flash store is not a map[string]string. This suppose will never happen, please report this bug.")
} }
storeExists = true // in order to skip the check later storeExists = true // in order to skip the check later
for k, v := range m { for k, v := range m {
@ -741,38 +814,32 @@ func (ctx *Context) GetFlash(key string) (value string, err error) {
} }
} }
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key)) cookieKey, cookieValue := ctx.decodeFlashCookie(key)
if cookieValue == "" { if cookieValue == "" {
err = errFlashNotFound.Return() return "", errFlashNotFound.Return()
} else {
v, e := base64.URLEncoding.DecodeString(cookieValue)
if e != nil {
return "", err
}
value = string(v)
// store this flash message to the lifetime request's local storage,
// I choose this method because no need to store it if not used at all
if storeExists {
ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = value
} else {
flashStoreMap := make(map[string]string)
flashStoreMap[key] = value
ctx.Set(flashMessagesStoreContextKey, flashStoreMap)
}
//remove the real cookie, no need to have that, we stored it on lifetime request
ctx.RemoveCookie(key)
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
} }
return // store this flash message to the lifetime request's local storage,
// I choose this method because no need to store it if not used at all
if storeExists {
ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = cookieValue
} else {
flashStoreMap := make(map[string]string)
flashStoreMap[key] = cookieValue
ctx.Set(flashMessagesStoreContextKey, flashStoreMap)
}
//remove the real cookie, no need to have that, we stored it on lifetime request
ctx.RemoveCookie(cookieKey)
return cookieValue, nil
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
} }
// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string) // SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
// the value will be available on the NEXT request // the value will be available on the NEXT request
func (ctx *Context) SetFlash(key string, value string) { func (ctx *Context) SetFlash(key string, value string) {
c := fasthttp.AcquireCookie() c := fasthttp.AcquireCookie()
c.SetKey(key) c.SetKey(flashMessageCookiePrefix + key)
c.SetValue(base64.URLEncoding.EncodeToString([]byte(value))) c.SetValue(base64.URLEncoding.EncodeToString([]byte(value)))
c.SetPath("/") c.SetPath("/")
c.SetHTTPOnly(true) c.SetHTTPOnly(true)

View File

@ -14,6 +14,10 @@ type (
// IContext the interface for the iris/context // IContext the interface for the iris/context
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris. // Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
IContext interface { IContext interface {
// deprecated Start
PostFormValue(string) string
PostFormMulti(string) []string
// deprecated End
Param(string) string Param(string) string
ParamInt(string) (int, error) ParamInt(string) (int, error)
ParamInt64(string) (int64, error) ParamInt64(string) (int64, error)
@ -29,8 +33,8 @@ type (
RequestIP() string RequestIP() string
RemoteAddr() string RemoteAddr() string
RequestHeader(k string) string RequestHeader(k string) string
PostFormValue(string) string FormValueString(string) string
PostFormMulti(string) []string FormValues(string) []string
SetStatusCode(int) SetStatusCode(int)
SetContentType(string) SetContentType(string)
SetHeader(string, string) SetHeader(string, string)
@ -66,9 +70,11 @@ type (
GetString(string) string GetString(string) string
GetInt(string) int GetInt(string) int
Set(string, interface{}) Set(string, interface{})
VisitAllCookies(func(string, string))
SetCookie(*fasthttp.Cookie) SetCookie(*fasthttp.Cookie)
SetCookieKV(string, string) SetCookieKV(string, string)
RemoveCookie(string) RemoveCookie(string)
GetFlashes() map[string]string
GetFlash(string) (string, error) GetFlash(string) (string, error)
SetFlash(string, string) SetFlash(string, string)
Session() store.IStore Session() store.IStore

613
context_test.go Normal file
View File

@ -0,0 +1,613 @@
package iris
/*
The most part of the context covered,
the other part contains serving static methods,
find remote ip, GetInt and the view engine rendering(templates)
I am not waiting unexpected behaviors from the rest of the funcs,
so that's all with context's tests.
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
*/
import (
"encoding/xml"
"net/url"
"strconv"
"strings"
"testing"
)
func TestContextReset(t *testing.T) {
var context Context
context.Params = PathParameters{PathParameter{Key: "testkey", Value: "testvalue"}}
context.Reset(nil)
if len(context.Params) > 0 {
t.Fatalf("Expecting to have %d params but got: %d", 0, len(context.Params))
}
}
func TestContextClone(t *testing.T) {
var context Context
context.Params = PathParameters{
PathParameter{Key: "testkey", Value: "testvalue"},
PathParameter{Key: "testkey2", Value: "testvalue2"},
}
c := context.Clone()
if v := c.Param("testkey"); v != context.Param("testkey") {
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey"), v)
}
if v := c.Param("testkey2"); v != context.Param("testkey2") {
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey2"), v)
}
}
func TestContextDoNextStop(t *testing.T) {
var context Context
ok := false
afterStop := false
context.middleware = Middleware{HandlerFunc(func(*Context) {
ok = true
}), HandlerFunc(func(*Context) {
ok = true
}), HandlerFunc(func(*Context) {
// this will never execute
afterStop = true
})}
context.Do()
if context.pos != 0 {
t.Fatalf("Expecting position 0 for context's middleware but we got: %d", context.pos)
}
if !ok {
t.Fatalf("Unexpected behavior, first context's middleware didn't executed")
}
ok = false
context.Next()
if int(context.pos) != 1 {
t.Fatalf("Expecting to have position %d but we got: %d", 1, context.pos)
}
if !ok {
t.Fatalf("Next context's middleware didn't executed")
}
context.StopExecution()
if context.pos != stopExecutionPosition {
t.Fatalf("Context's StopExecution didn't worked, we expected to have position %d but we got %d", stopExecutionPosition, context.pos)
}
if !context.IsStopped() {
t.Fatalf("Should be stopped")
}
context.Next()
if afterStop {
t.Fatalf("We stopped the execution but the next handler was executed")
}
}
func TestContextParams(t *testing.T) {
var context Context
params := PathParameters{
PathParameter{Key: "testkey", Value: "testvalue"},
PathParameter{Key: "testkey2", Value: "testvalue2"},
PathParameter{Key: "id", Value: "3"},
PathParameter{Key: "bigint", Value: "548921854390354"},
}
context.Params = params
if v := context.Param(params[0].Key); v != params[0].Value {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[0].Value, context.Param("testkey"))
}
if v := context.Param(params[1].Key); v != params[1].Value {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[1].Value, context.Param("testkey2"))
}
if len(context.Params) != len(params) {
t.Fatalf("Expecting to have %d parameters but we got %d", len(params), len(context.Params))
}
if vi, err := context.ParamInt(params[2].Key); err != nil {
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
} else if vi != 3 {
t.Fatalf("Expecting to receive %d but we got %d", 3, vi)
}
if vi, err := context.ParamInt64(params[3].Key); err != nil {
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
} else if vi != 548921854390354 {
t.Fatalf("Expecting to receive %d but we got %d", 548921854390354, vi)
}
// end-to-end test now, note that we will not test the whole mux here, this happens on http_test.go
initDefault()
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *Context) {
paramsStr := ctx.Params.String()
ctx.Write(paramsStr)
})
Tester(t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(StatusOK).Body().Equal(expectedParamsStr)
}
func TestContextURLParams(t *testing.T) {
initDefault()
passedParams := map[string]string{"param1": "value1", "param2": "value2"}
Get("/", func(ctx *Context) {
params := ctx.URLParams()
ctx.JSON(StatusOK, params)
})
e := Tester(t)
e.GET("/").WithQueryObject(passedParams).Expect().Status(StatusOK).JSON().Equal(passedParams)
}
// hoststring returns the full host, will return the HOST:IP
func TestContextHostString(t *testing.T) {
initDefault()
Config.Tester.ListeningAddr = "localhost:8080"
Get("/", func(ctx *Context) {
ctx.Write(ctx.HostString())
})
Get("/wrong", func(ctx *Context) {
ctx.Write(ctx.HostString() + "w")
})
e := Tester(t)
e.GET("/").Expect().Status(StatusOK).Body().Equal(Config.Tester.ListeningAddr)
e.GET("/wrong").Expect().Body().NotEqual(Config.Tester.ListeningAddr)
}
// VirtualHostname returns the hostname only,
// if the host starts with 127.0.0.1 or localhost it gives the registered hostname part of the listening addr
func TestContextVirtualHostName(t *testing.T) {
initDefault()
vhost := "mycustomvirtualname.com"
Config.Tester.ListeningAddr = vhost + ":8080"
Get("/", func(ctx *Context) {
ctx.Write(ctx.VirtualHostname())
})
Get("/wrong", func(ctx *Context) {
ctx.Write(ctx.VirtualHostname() + "w")
})
e := Tester(t)
e.GET("/").Expect().Status(StatusOK).Body().Equal(vhost)
e.GET("/wrong").Expect().Body().NotEqual(vhost)
}
func TestContextFormValueString(t *testing.T) {
initDefault()
var k, v string
k = "postkey"
v = "postvalue"
Post("/", func(ctx *Context) {
ctx.Write(k + "=" + ctx.FormValueString(k))
})
e := Tester(t)
e.POST("/").WithFormField(k, v).Expect().Status(StatusOK).Body().Equal(k + "=" + v)
}
func TestContextSubdomain(t *testing.T) {
initDefault()
Config.Tester.ListeningAddr = "mydomain.com:9999"
//Config.Tester.ExplicitURL = true
Party("mysubdomain.").Get("/mypath", func(ctx *Context) {
ctx.Write(ctx.Subdomain())
})
e := Tester(t)
e.GET("/").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound)
e.GET("/mypath").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusOK).Body().Equal("mysubdomain")
//e.GET("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound)
//e.GET("http://mysubdomain.mydomain.com:9999/mypath").Expect().Status(StatusOK).Body().Equal("mysubdomain")
}
type testBinderData struct {
Username string
Mail string
Data []string `form:"mydata" json:"mydata"`
}
type testBinderXMLData struct {
XMLName xml.Name `xml:"info"`
FirstAttr string `xml:"first,attr"`
SecondAttr string `xml:"second,attr"`
Name string `xml:"name",json:"name"`
Birth string `xml:"birth",json:"birth"`
Stars int `xml:"stars",json:"stars"`
}
func TestContextReadForm(t *testing.T) {
initDefault()
Post("/form", func(ctx *Context) {
obj := testBinderData{}
err := ctx.ReadForm(&obj)
if err != nil {
t.Fatalf("Error when parsing the FORM: %s", err.Error())
}
ctx.JSON(StatusOK, obj)
})
e := Tester(t)
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"},
"[1]": []string{"mydata2"}}}
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
e.POST("/form").WithForm(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
}
func TestContextReadJSON(t *testing.T) {
initDefault()
Post("/json", func(ctx *Context) {
obj := testBinderData{}
err := ctx.ReadJSON(&obj)
if err != nil {
t.Fatalf("Error when parsing the JSON body: %s", err.Error())
}
ctx.JSON(StatusOK, obj)
})
e := Tester(t)
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}}
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
}
func TestContextReadXML(t *testing.T) {
initDefault()
Post("/xml", func(ctx *Context) {
obj := testBinderXMLData{}
err := ctx.ReadXML(&obj)
if err != nil {
t.Fatalf("Error when parsing the XML body: %s", err.Error())
}
ctx.XML(StatusOK, obj)
})
e := Tester(t)
expectedObj := testBinderXMLData{
XMLName: xml.Name{Local: "info", Space: "info"},
FirstAttr: "this is the first attr",
SecondAttr: "this is the second attr",
Name: "Iris web framework",
Birth: "13 March 2016",
Stars: 4064,
}
// so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting
expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `"><name>` + expectedObj.Name + `</name><birth>` + expectedObj.Birth + `</birth><stars>` + strconv.Itoa(expectedObj.Stars) + `</stars></info>`
e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody)
}
// TestContextRedirectTo tests the named route redirect action
func TestContextRedirectTo(t *testing.T) {
initDefault()
h := func(ctx *Context) { ctx.Write(ctx.PathString()) }
Get("/mypath", h)("my-path")
Get("/mypostpath", h)("my-post-path")
Get("mypath/with/params/:param1/:param2", func(ctx *Context) {
if len(ctx.Params) != 2 {
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", len(ctx.Params))
}
ctx.Write(ctx.PathString())
})("my-path-with-params")
Get("/redirect/to/:routeName/*anyparams", func(ctx *Context) {
routeName := ctx.Param("routeName")
var args []interface{}
anyparams := ctx.Param("anyparams")
if anyparams != "" && anyparams != "/" {
params := strings.Split(anyparams[1:], "/") // firstparam/secondparam
for _, s := range params {
args = append(args, s)
}
}
//println("Redirecting to: " + routeName + " with path: " + Path(routeName, args...))
ctx.RedirectTo(routeName, args...)
})
e := Tester(t)
e.GET("/redirect/to/my-path/").Expect().Status(StatusOK).Body().Equal("/mypath")
e.GET("/redirect/to/my-post-path/").Expect().Status(StatusOK).Body().Equal("/mypostpath")
e.GET("/redirect/to/my-path-with-params/firstparam/secondparam").Expect().Status(StatusOK).Body().Equal("/mypath/with/params/firstparam/secondparam")
}
func TestContextUserValues(t *testing.T) {
initDefault()
testCustomObjUserValue := struct{ Name string }{Name: "a name"}
values := map[string]interface{}{"key1": "value1", "key2": "value2", "key3": 3, "key4": testCustomObjUserValue, "key5": map[string]string{"key": "value"}}
Get("/test", func(ctx *Context) {
for k, v := range values {
ctx.Set(k, v)
}
}, func(ctx *Context) {
for k, v := range values {
userValue := ctx.Get(k)
if userValue != v {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
}
if m, isMap := userValue.(map[string]string); isMap {
if m["key"] != v.(map[string]string)["key"] {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v.(map[string]string)["key"], m["key"])
}
} else {
if userValue != v {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
}
}
}
})
e := Tester(t)
e.GET("/test").Expect().Status(StatusOK)
}
func TestContextFlashMessages(t *testing.T) {
initDefault()
firstKey := "name"
lastKey := "package"
values := PathParameters{PathParameter{Key: firstKey, Value: "kataras"}, PathParameter{Key: lastKey, Value: "iris"}}
jsonExpected := map[string]string{firstKey: "kataras", lastKey: "iris"}
// set the flashes, the cookies are filled
Put("/set", func(ctx *Context) {
for _, v := range values {
ctx.SetFlash(v.Key, v.Value)
}
})
// get the first flash, the next should be avaiable to the next requess
Get("/get_first_flash", func(ctx *Context) {
for _, v := range values {
val, _ := ctx.GetFlash(v.Key)
ctx.JSON(StatusOK, map[string]string{v.Key: val})
break
}
})
// just an empty handler to test if the flashes should remeain to the next if GetFlash/GetFlashes used
Get("/get_no_getflash", func(ctx *Context) {
})
// get the last flash, the next should be avaiable to the next requess
Get("/get_last_flash", func(ctx *Context) {
for i, v := range values {
if i == len(values)-1 {
val, _ := ctx.GetFlash(v.Key)
ctx.JSON(StatusOK, map[string]string{v.Key: val})
}
}
})
Get("/get_zero_flashes", func(ctx *Context) {
ctx.JSON(StatusOK, ctx.GetFlashes()) // should return nil
})
// we use the GetFlash to get the flash messages, the messages and the cookies should be empty after that
Get("/get_flash", func(ctx *Context) {
kv := make(map[string]string)
for _, v := range values {
val, err := ctx.GetFlash(v.Key)
if err == nil {
kv[v.Key] = val
}
}
ctx.JSON(StatusOK, kv)
}, func(ctx *Context) {
// at the same request, flashes should be available
if len(ctx.GetFlashes()) == 0 {
t.Fatalf("Flashes should be remeain to the whole request lifetime")
}
})
Get("/get_flashes", func(ctx *Context) {
// one time one handler, using GetFlashes
kv := make(map[string]string)
flashes := ctx.GetFlashes()
//second time on the same handler, using the GetFlash
for k := range flashes {
kv[k], _ = ctx.GetFlash(k)
}
if len(flashes) != len(kv) {
ctx.SetStatusCode(StatusNoContent)
return
}
ctx.Next()
}, func(ctx *Context) {
// third time on a next handler
// test the if next handler has access to them(must) because flash are request lifetime now.
// print them to the client for test the response also
ctx.JSON(StatusOK, ctx.GetFlashes())
})
e := Tester(t)
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
e.GET("/get_first_flash").Expect().Status(StatusOK).JSON().Object().ContainsKey(firstKey).NotContainsKey(lastKey)
// just a request which does not use the flash message, so flash messages should be available on the next request
e.GET("/get_no_getflash").Expect().Status(StatusOK)
e.GET("/get_last_flash").Expect().Status(StatusOK).JSON().Object().ContainsKey(lastKey).NotContainsKey(firstKey)
g := e.GET("/get_zero_flashes").Expect().Status(StatusOK)
g.JSON().Null()
g.Cookies().Empty()
// set the magain
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
// get them again using GetFlash
e.GET("/get_flash").Expect().Status(StatusOK).JSON().Object().Equal(jsonExpected)
// this should be empty again
g = e.GET("/get_zero_flashes").Expect().Status(StatusOK)
g.JSON().Null()
g.Cookies().Empty()
//set them again
e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty()
// get them again using GetFlashes
e.GET("/get_flashes").Expect().Status(StatusOK).JSON().Object().Equal(jsonExpected)
// this should be empty again
g = e.GET("/get_zero_flashes").Expect().Status(StatusOK)
g.JSON().Null()
g.Cookies().Empty()
}
func TestContextSessions(t *testing.T) {
t.Parallel()
values := map[string]interface{}{
"Name": "iris",
"Months": "4",
"Secret": "dsads£2132215£%%Ssdsa",
}
initDefault()
Config.Sessions.Cookie = "mycustomsessionid"
writeValues := func(ctx *Context) {
sessValues := ctx.Session().GetAll()
ctx.JSON(StatusOK, sessValues)
}
if testEnableSubdomain {
Party(testSubdomain+".").Get("/get", func(ctx *Context) {
writeValues(ctx)
})
}
Post("set", func(ctx *Context) {
vals := make(map[string]interface{}, 0)
if err := ctx.ReadJSON(&vals); err != nil {
t.Fatalf("Cannot readjson. Trace %s", err.Error())
}
for k, v := range vals {
ctx.Session().Set(k, v)
}
})
Get("/get", func(ctx *Context) {
writeValues(ctx)
})
Get("/clear", func(ctx *Context) {
ctx.Session().Clear()
writeValues(ctx)
})
Get("/destroy", func(ctx *Context) {
ctx.SessionDestroy()
writeValues(ctx)
// the cookie and all values should be empty
})
e := Tester(t)
e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty()
e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values)
if testEnableSubdomain {
es := subdomainTester(e)
es.Request("GET", "/get").Expect().Status(StatusOK).JSON().Object().Equal(values)
}
// test destory which also clears first
d := e.GET("/destroy").Expect().Status(StatusOK)
d.JSON().Object().Empty()
d.Cookies().ContainsOnly(Config.Sessions.Cookie)
// set and clear again
e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty()
e.GET("/clear").Expect().Status(StatusOK).JSON().Object().Empty()
}
type renderTestInformationType struct {
XMLName xml.Name `xml:"info"`
FirstAttr string `xml:"first,attr"`
SecondAttr string `xml:"second,attr"`
Name string `xml:"name",json:"name"`
Birth string `xml:"birth",json:"birth"`
Stars int `xml:"stars",json:"stars"`
}
func TestContextRenderRest(t *testing.T) {
initDefault()
dataContents := []byte("Some binary data here.")
textContents := "Plain text here"
JSONPContents := map[string]string{"hello": "jsonp"}
JSONPCallback := "callbackName"
JSONXMLContents := renderTestInformationType{
XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later
FirstAttr: "this is the first attr",
SecondAttr: "this is the second attr",
Name: "Iris web framework",
Birth: "13 March 2016",
Stars: 4064,
}
markdownContents := "# Hello dynamic markdown from Iris"
Get("/data", func(ctx *Context) {
ctx.Data(StatusOK, dataContents)
})
Get("/text", func(ctx *Context) {
ctx.Text(StatusOK, textContents)
})
Get("/jsonp", func(ctx *Context) {
ctx.JSONP(StatusOK, JSONPCallback, JSONPContents)
})
Get("/json", func(ctx *Context) {
ctx.JSON(StatusOK, JSONXMLContents)
})
Get("/xml", func(ctx *Context) {
ctx.XML(StatusOK, JSONXMLContents)
})
Get("/markdown", func(ctx *Context) {
ctx.Markdown(StatusOK, markdownContents)
})
e := Tester(t)
dataT := e.GET("/data").Expect().Status(StatusOK)
dataT.Header("Content-Type").Equal("application/octet-stream")
dataT.Body().Equal(string(dataContents))
textT := e.GET("/text").Expect().Status(StatusOK)
textT.Header("Content-Type").Equal("text/plain; charset=UTF-8")
textT.Body().Equal(textContents)
JSONPT := e.GET("/jsonp").Expect().Status(StatusOK)
JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8")
JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`)
JSONT := e.GET("/json").Expect().Status(StatusOK)
JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8")
JSONT.JSON().Object().Equal(JSONXMLContents)
XMLT := e.GET("/xml").Expect().Status(StatusOK)
XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8")
XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `"><name>` + JSONXMLContents.Name + `</name><birth>` + JSONXMLContents.Birth + `</birth><stars>` + strconv.Itoa(JSONXMLContents.Stars) + `</stars></info>`)
markdownT := e.GET("/markdown").Expect().Status(StatusOK)
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
markdownT.Body().Equal("<h1>" + markdownContents[2:] + "</h1>\n")
}

82
deprecated.go Normal file
View File

@ -0,0 +1,82 @@
package iris
import "github.com/kataras/iris/config"
/* Contains some different functions of context.go & iris.go which will be removed on the next revision */
// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead
// DEPRECATED: use .AddServer instead
// AddServers starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func SecondaryListen(cfg config.Server) *Server {
return Default.SecondaryListen(cfg)
}
// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead
// DEPRECATED: use .AddServer instead
// AddServers starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func (s *Framework) SecondaryListen(cfg config.Server) *Server {
return s.Servers.Add(cfg)
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
// DEPRECATED: use ListenVirtual instead
// initializes the whole framework but server doesn't listens to a specific net.Listener
// it is not blocking the app
func NoListen(optionalAddr ...string) *Server {
return Default.NoListen(optionalAddr...)
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
// DEPRECATED: use ListenVirtual instead
// initializes the whole framework but server doesn't listens to a specific net.Listener
// it is not blocking the app
func (s *Framework) NoListen(optionalAddr ...string) *Server {
return s.ListenVirtual(optionalAddr...)
}
// CloseWithErr terminates all the registered servers and returns an error if any
// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close())
// if you want to panic on this error use the iris.Must(iris.Close())
func CloseWithErr() error {
return Default.Close()
}
// CloseWithErr terminates all the registered servers and returns an error if any
// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close())
// if you want to panic on this error use the iris.Must(iris.Close())
func (s *Framework) CloseWithErr() error {
return s.Close()
}
// PostFormMulti returns a slice of string from post request's data
// DEPRECATED: Plase use FormValues instead
func (ctx *Context) PostFormMulti(name string) []string {
return ctx.FormValues(name)
}
// PostFormValue This will be deprecated
///DEPRECATED: please use FormValueString instead
// PostFormValue returns a single value from post request's data
func (ctx *Context) PostFormValue(name string) string {
return ctx.FormValueString(name)
}

186
http.go
View File

@ -10,6 +10,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/iris-contrib/errors" "github.com/iris-contrib/errors"
"github.com/kataras/iris/config" "github.com/kataras/iris/config"
@ -236,35 +237,47 @@ var (
errServerChmod = errors.New("Cannot chmod %#o for %q: %s") errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
) )
// Server the http server type (
type Server struct { // Server the http server
*fasthttp.Server Server struct {
listener net.Listener *fasthttp.Server
Config *config.Server listener net.Listener
tls bool Config config.Server
mu sync.Mutex tls bool
} mu sync.Mutex
}
// ServerList contains the servers connected to the Iris station
ServerList struct {
mux *serveMux
servers []*Server
}
)
// newServer returns a pointer to a Server object, and set it's options if any, nothing more // newServer returns a pointer to a Server object, and set it's options if any, nothing more
func newServer(c *config.Server) *Server { func newServer(cfg config.Server) *Server {
s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c} s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: cfg}
return s return s
} }
// SetHandler sets the handler in order to listen on client requests
func (s *Server) SetHandler(mux *serveMux) {
if s.Server != nil {
s.Server.Handler = mux.ServeRequest()
}
}
// IsListening returns true if server is listening/started, otherwise false // IsListening returns true if server is listening/started, otherwise false
func (s *Server) IsListening() bool { func (s *Server) IsListening() bool {
if s == nil {
return false
}
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
return s.listener != nil && s.listener.Addr().String() != "" return s.listener != nil && s.listener.Addr().String() != ""
} }
// IsOpened checks if handler is not nil and returns true if not, otherwise false
// this is used to see if a server has opened, use IsListening if you want to see if the server is actually ready to serve connections
func (s *Server) IsOpened() bool {
if s == nil {
return false
}
return s.Server != nil && s.Server.Handler != nil
}
// IsSecure returns true if server uses TLS, otherwise false // IsSecure returns true if server uses TLS, otherwise false
func (s *Server) IsSecure() bool { func (s *Server) IsSecure() bool {
return s.tls return s.tls
@ -398,7 +411,11 @@ func (s *Server) serve(l net.Listener) error {
} }
// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen // Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
func (s *Server) Open() error { func (s *Server) Open(h fasthttp.RequestHandler) error {
if h == nil {
return errServerHandlerMissing.Return()
}
if s.IsListening() { if s.IsListening() {
return errServerAlreadyStarted.Return() return errServerAlreadyStarted.Return()
} }
@ -407,10 +424,6 @@ func (s *Server) Open() error {
return errServerConfigMissing.Return() return errServerConfigMissing.Return()
} }
if s.Handler == nil {
return errServerHandlerMissing.Return()
}
// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases // check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
a := s.Config.ListeningAddr a := s.Config.ListeningAddr
//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080 //check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
@ -419,9 +432,13 @@ func (s *Server) Open() error {
s.Config.ListeningAddr = config.DefaultServerHostname + a s.Config.ListeningAddr = config.DefaultServerHostname + a
} }
if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize {
s.Server.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
}
if s.Config.RedirectTo != "" { if s.Config.RedirectTo != "" {
// override the handler and redirect all requests to this addr // override the handler and redirect all requests to this addr
s.Handler = func(reqCtx *fasthttp.RequestCtx) { s.Server.Handler = func(reqCtx *fasthttp.RequestCtx) {
path := string(reqCtx.Path()) path := string(reqCtx.Path())
redirectTo := s.Config.RedirectTo redirectTo := s.Config.RedirectTo
if path != "/" { if path != "/" {
@ -429,12 +446,19 @@ func (s *Server) Open() error {
} }
reqCtx.Redirect(redirectTo, StatusMovedPermanently) reqCtx.Redirect(redirectTo, StatusMovedPermanently)
} }
} else {
s.Server.Handler = h
}
if s.Config.Virtual {
return nil
} }
if s.Config.Mode > 0 { if s.Config.Mode > 0 {
return s.listenUNIX() return s.listenUNIX()
} }
return s.listen() return s.listen()
} }
// Close terminates the server // Close terminates the server
@ -447,6 +471,122 @@ func (s *Server) Close() (err error) {
return return
} }
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------ServerList implementation-----------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// Add adds a server to the list by its config
// returns the new server
func (s *ServerList) Add(cfg config.Server) *Server {
srv := newServer(cfg)
s.servers = append(s.servers, srv)
return srv
}
// Len returns the size of the server list
func (s *ServerList) Len() int {
return len(s.servers)
}
// Main returns the main server,
// the last added server is the main server, even if's Virtual
func (s *ServerList) Main() (srv *Server) {
l := len(s.servers) - 1
for i := range s.servers {
if i == l {
return s.servers[i]
}
}
return nil
}
// Get returns the server by it's registered Address
func (s *ServerList) Get(addr string) (srv *Server) {
for i := range s.servers {
srv = s.servers[i]
if srv.Config.ListeningAddr == addr {
return
}
}
return
}
// GetAll returns all registered servers
func (s *ServerList) GetAll() []*Server {
return s.servers
}
// GetByIndex returns a server from the list by it's index
func (s *ServerList) GetByIndex(i int) *Server {
if len(s.servers) >= i+1 {
return s.servers[i]
}
return nil
}
// Remove deletes a server by it's registered Address
// returns true if something was removed, otherwise returns false
func (s *ServerList) Remove(addr string) bool {
servers := s.servers
for i := range servers {
srv := servers[i]
if srv.Config.ListeningAddr == addr {
copy(servers[i:], servers[i+1:])
servers[len(servers)-1] = nil
s.servers = servers[:len(servers)-1]
return true
}
}
return false
}
// CloseAll terminates all listening servers
// returns the first error, if erro happens it continues to closes the rest of the servers
func (s *ServerList) CloseAll() (err error) {
for i := range s.servers {
if err == nil {
err = s.servers[i].Close()
}
}
return
}
// OpenAll starts all servers
// returns the first error happens to one of these servers
// if one server gets error it closes the previous servers and exits from this process
func (s *ServerList) OpenAll() error {
l := len(s.servers) - 1
h := s.mux.ServeRequest()
for i := range s.servers {
if err := s.servers[i].Open(h); err != nil {
time.Sleep(2 * time.Second)
// for any case,
// we don't care about performance on initialization,
// we must make sure that the previous servers are running before closing them
s.CloseAll()
break
}
if i == l {
s.mux.setHostname(s.servers[i].VirtualHostname())
}
}
return nil
}
// GetAllOpened returns all opened/started servers
func (s *ServerList) GetAllOpened() (servers []*Server) {
for i := range s.servers {
if s.servers[i].IsOpened() {
servers = append(servers, s.servers[i])
}
}
return
}
// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context) // errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
// It seems to be a +type Points to: +pointer.' // It seems to be a +type Points to: +pointer.'
var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.") var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")

529
http_test.go Normal file
View File

@ -0,0 +1,529 @@
package iris
/*
This is the part we only care, these are end-to-end tests for the mux(router) and the server, the whole http file is made for these reasons only, so these tests are enough I think.
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
*/
import (
"fmt"
"io/ioutil"
"os"
"strconv"
"testing"
"time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris/config"
)
const (
testTLSCert = `-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIJAPDsxtKV4v3uMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
BAMMDTEyNy4wLjAuMTo0NDMwHhcNMTYwNjI5MTMxMjU4WhcNMjYwNjI3MTMxMjU4
WjAYMRYwFAYDVQQDDA0xMjcuMC4wLjE6NDQzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH
XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns
8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB
dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv
ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs
pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABo1AwTjAdBgNVHQ4EFgQU
zr1df/c9+NyTpmyiQO8g3a8NswYwHwYDVR0jBBgwFoAUzr1df/c9+NyTpmyiQO8g
3a8NswYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEACG5shtMSDgCd
MNjOF+YmD+PX3Wy9J9zehgaDJ1K1oDvBbQFl7EOJl8lRMWITSws22Wxwh8UXVibL
sscKBp14dR3e7DdbwCVIX/JyrJyOaCfy2nNBdf1B06jYFsIHvP3vtBAb9bPNOTBQ
QE0Ztu9kCqgsmu0//sHuBEeA3d3E7wvDhlqRSxTLcLtgC1NSgkFvBw0JvwgpkX6s
M5WpSBZwZv8qpplxhFfqNy8Uf+xrpSW0pGfkHumehkQGC6/Ry7raganS0aHhDPK9
Z1bEJ2com1bFFAQsm9yIXrRVMGGCtihB2Au0Q4jpEjUbzWYM+ItZyvRAGRM6Qex6
s/jogMeRsw==
-----END CERTIFICATE-----
`
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH
XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns
8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB
dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv
ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs
pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABAoIBAQDCd+bo9I0s8Fun
4z3Y5oYSDTZ5O/CY0O5GyXPrSzCSM4Cj7EWEj1mTdb9Ohv9tam7WNHHLrcd+4NfK
4ok5hLVs1vqM6h6IksB7taKATz+Jo0PzkzrsXvMqzERhEBo4aoGMIv2rXIkrEdas
S+pCsp8+nAWtAeBMCn0Slu65d16vQxwgfod6YZfvMKbvfhOIOShl9ejQ+JxVZcMw
Ti8sgvYmFUrdrEH3nCgptARwbx4QwlHGaw/cLGHdepfFsVaNQsEzc7m61fSO70m4
NYJv48ZgjOooF5AccbEcQW9IxxikwNc+wpFYy5vDGzrBwS5zLZQFpoyMWFhtWdjx
hbmNn1jlAoGBAPs0ZjqsfDrH5ja4dQIdu5ErOccsmoHfMMBftMRqNG5neQXEmoLc
Uz8WeQ/QDf302aTua6E9iSjd7gglbFukVwMndQ1Q8Rwxz10jkXfiE32lFnqK0csx
ltruU6hOeSGSJhtGWBuNrT93G2lmy23fSG6BqOzdU4rn/2GPXy5zaxM/AoGBANSm
/E96RcBUiI6rDVqKhY+7M1yjLB41JrErL9a0Qfa6kYnaXMr84pOqVN11IjhNNTgl
g1lwxlpXZcZh7rYu9b7EEMdiWrJDQV7OxLDHopqUWkQ+3MHwqs6CxchyCq7kv9Df
IKqat7Me6Cyeo0MqcW+UMxlCRBxKQ9jqC7hDfZuXAoGBAJmyS8ImerP0TtS4M08i
JfsCOY21qqs/hbKOXCm42W+be56d1fOvHngBJf0YzRbO0sNo5Q14ew04DEWLsCq5
+EsDv0hwd7VKfJd+BakV99ruQTyk5wutwaEeJK1bph12MD6L4aiqHJAyLeFldZ45
+TUzu8mA+XaJz+U/NXtUPvU9AoGBALtl9M+tdy6I0Fa50ujJTe5eEGNAwK5WNKTI
5D2XWNIvk/Yh4shXlux+nI8UnHV1RMMX++qkAYi3oE71GsKeG55jdk3fFQInVsJQ
APGw3FDRD8M4ip62ki+u+tEr/tIlcAyHtWfjNKO7RuubWVDlZFXqCiXmSdOMdsH/
bxiREW49AoGACWev/eOzBoQJCRN6EvU2OV0s3b6f1QsPvcaH0zc6bgbBFOGmJU8v
pXhD88tsu9exptLkGVoYZjR0n0QT/2Kkyu93jVDW/80P7VCz8DKYyAJDa4CVwZxO
MlobQSunSDKx/CCJhWkbytCyh1bngAtwSAYLXavYIlJbAzx6FvtAIw4=
-----END RSA PRIVATE KEY-----
`
)
// Contains the server test for multi running servers
func TestMultiRunningServers_v1(t *testing.T) {
host := "mydomain.com:443" // you have to add it to your hosts file( for windows, as 127.0.0.1 mydomain.com)
initDefault()
Config.DisableBanner = true
// create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert")
if ferr != nil {
t.Fatal(ferr.Error())
}
keyFile, ferr := ioutil.TempFile("", "key")
if ferr != nil {
t.Fatal(ferr.Error())
}
certFile.WriteString(testTLSCert)
keyFile.WriteString(testTLSKey)
defer func() {
certFile.Close()
time.Sleep(350 * time.Millisecond)
os.Remove(certFile.Name())
keyFile.Close()
time.Sleep(350 * time.Millisecond)
os.Remove(keyFile.Name())
}()
Get("/", func(ctx *Context) {
ctx.Write("Hello from %s", ctx.HostString())
})
// start the secondary server
SecondaryListen(config.Server{ListeningAddr: "mydomain.com:80", RedirectTo: "https://" + host, Virtual: true})
// start the main server
go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
// prepare test framework
if ok := <-Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
}
e := Tester(t)
e.Request("GET", "http://mydomain.com:80").Expect().Status(StatusOK).Body().Equal("Hello from " + host)
e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
}
// Contains the server test for multi running servers
func TestMultiRunningServers_v2(t *testing.T) {
domain := "mydomain.com"
host := domain + ":443"
initDefault()
Config.DisableBanner = true
Config.Tester.ListeningAddr = host
// create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert")
if ferr != nil {
t.Fatal(ferr.Error())
}
keyFile, ferr := ioutil.TempFile("", "key")
if ferr != nil {
t.Fatal(ferr.Error())
}
certFile.WriteString(testTLSCert)
keyFile.WriteString(testTLSKey)
defer func() {
certFile.Close()
time.Sleep(350 * time.Millisecond)
os.Remove(certFile.Name())
keyFile.Close()
time.Sleep(350 * time.Millisecond)
os.Remove(keyFile.Name())
}()
Get("/", func(ctx *Context) {
ctx.Write("Hello from %s", ctx.HostString())
})
// add a secondary server
Servers.Add(config.Server{ListeningAddr: domain + ":80", RedirectTo: "https://" + host, Virtual: true})
// add our primary/main server
Servers.Add(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
go Go()
// prepare test framework
if ok := <-Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
}
e := Tester(t)
e.Request("GET", "http://"+domain+":80").Expect().Status(StatusOK).Body().Equal("Hello from " + host)
e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
}
const (
testEnableSubdomain = false
testSubdomain = "mysubdomain.com"
)
func testSubdomainHost() string {
return testSubdomain + strconv.Itoa(Servers.Main().Port())
}
func testSubdomainURL() (subdomainURL string) {
subdomainHost := testSubdomainHost()
if Servers.Main().IsSecure() {
subdomainURL = "https://" + subdomainHost
} else {
subdomainURL = "http://" + subdomainHost
}
return
}
func subdomainTester(e *httpexpect.Expect) *httpexpect.Expect {
es := e.Builder(func(req *httpexpect.Request) {
req.WithURL(testSubdomainURL())
})
return es
}
type param struct {
Key string
Value string
}
type testRoute struct {
Method string
Path string
RequestPath string
RequestQuery string
Body string
Status int
Register bool
Params []param
URLParams []param
}
func TestMuxSimple(t *testing.T) {
testRoutes := []testRoute{
// FOUND - registed
{"GET", "/test_get", "/test_get", "", "hello, get!", 200, true, nil, nil},
{"POST", "/test_post", "/test_post", "", "hello, post!", 200, true, nil, nil},
{"PUT", "/test_put", "/test_put", "", "hello, put!", 200, true, nil, nil},
{"DELETE", "/test_delete", "/test_delete", "", "hello, delete!", 200, true, nil, nil},
{"HEAD", "/test_head", "/test_head", "", "hello, head!", 200, true, nil, nil},
{"OPTIONS", "/test_options", "/test_options", "", "hello, options!", 200, true, nil, nil},
{"CONNECT", "/test_connect", "/test_connect", "", "hello, connect!", 200, true, nil, nil},
{"PATCH", "/test_patch", "/test_patch", "", "hello, patch!", 200, true, nil, nil},
{"TRACE", "/test_trace", "/test_trace", "", "hello, trace!", 200, true, nil, nil},
// NOT FOUND - not registed
{"GET", "/test_get_nofound", "/test_get_nofound", "", "Not Found", 404, false, nil, nil},
{"POST", "/test_post_nofound", "/test_post_nofound", "", "Not Found", 404, false, nil, nil},
{"PUT", "/test_put_nofound", "/test_put_nofound", "", "Not Found", 404, false, nil, nil},
{"DELETE", "/test_delete_nofound", "/test_delete_nofound", "", "Not Found", 404, false, nil, nil},
{"HEAD", "/test_head_nofound", "/test_head_nofound", "", "Not Found", 404, false, nil, nil},
{"OPTIONS", "/test_options_nofound", "/test_options_nofound", "", "Not Found", 404, false, nil, nil},
{"CONNECT", "/test_connect_nofound", "/test_connect_nofound", "", "Not Found", 404, false, nil, nil},
{"PATCH", "/test_patch_nofound", "/test_patch_nofound", "", "Not Found", 404, false, nil, nil},
{"TRACE", "/test_trace_nofound", "/test_trace_nofound", "", "Not Found", 404, false, nil, nil},
// Parameters
{"GET", "/test_get_parameter1/:name", "/test_get_parameter1/iris", "", "name=iris", 200, true, []param{{"name", "iris"}}, nil},
{"GET", "/test_get_parameter2/:name/details/:something", "/test_get_parameter2/iris/details/anything", "", "name=iris,something=anything", 200, true, []param{{"name", "iris"}, {"something", "anything"}}, nil},
{"GET", "/test_get_parameter2/:name/details/:something/*else", "/test_get_parameter2/iris/details/anything/elsehere", "", "name=iris,something=anything,else=/elsehere", 200, true, []param{{"name", "iris"}, {"something", "anything"}, {"else", "elsehere"}}, nil},
// URL Parameters
{"GET", "/test_get_urlparameter1/first", "/test_get_urlparameter1/first", "name=irisurl", "name=irisurl", 200, true, nil, []param{{"name", "irisurl"}}},
{"GET", "/test_get_urlparameter2/second", "/test_get_urlparameter2/second", "name=irisurl&something=anything", "name=irisurl,something=anything", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}}},
{"GET", "/test_get_urlparameter2/first/second/third", "/test_get_urlparameter2/first/second/third", "name=irisurl&something=anything&else=elsehere", "name=irisurl,something=anything,else=elsehere", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}, {"else", "elsehere"}}},
}
initDefault()
for idx := range testRoutes {
r := testRoutes[idx]
if r.Register {
HandleFunc(r.Method, r.Path, func(ctx *Context) {
ctx.SetStatusCode(r.Status)
if r.Params != nil && len(r.Params) > 0 {
ctx.SetBodyString(ctx.Params.String())
} else if r.URLParams != nil && len(r.URLParams) > 0 {
if len(r.URLParams) != len(ctx.URLParams()) {
t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams()))
}
paramsKeyVal := ""
for idxp, p := range r.URLParams {
val := ctx.URLParam(p.Key)
paramsKeyVal += p.Key + "=" + val + ","
if idxp == len(r.URLParams)-1 {
paramsKeyVal = paramsKeyVal[0 : len(paramsKeyVal)-1]
}
}
ctx.SetBodyString(paramsKeyVal)
} else {
ctx.SetBodyString(r.Body)
}
})
}
}
e := Tester(t)
// run the tests (1)
for idx := range testRoutes {
r := testRoutes[idx]
e.Request(r.Method, r.RequestPath).WithQueryString(r.RequestQuery).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}
func TestMuxSimpleParty(t *testing.T) {
initDefault()
h := func(c *Context) { c.WriteString(c.HostString() + c.PathString()) }
if testEnableSubdomain {
subdomainParty := Party(testSubdomain + ".")
{
subdomainParty.Get("/", h)
subdomainParty.Get("/path1", h)
subdomainParty.Get("/path2", h)
subdomainParty.Get("/namedpath/:param1/something/:param2", h)
subdomainParty.Get("/namedpath/:param1/something/:param2/else", h)
}
}
// simple
p := Party("/party1")
{
p.Get("/", h)
p.Get("/path1", h)
p.Get("/path2", h)
p.Get("/namedpath/:param1/something/:param2", h)
p.Get("/namedpath/:param1/something/:param2/else", h)
}
e := Tester(t)
request := func(reqPath string) {
e.Request("GET", reqPath).
Expect().
Status(StatusOK).Body().Equal(Servers.Main().Host() + reqPath)
}
// run the tests
request("/party1/")
request("/party1/path1")
request("/party1/path2")
request("/party1/namedpath/theparam1/something/theparam2")
request("/party1/namedpath/theparam1/something/theparam2/else")
if testEnableSubdomain {
es := subdomainTester(e)
subdomainRequest := func(reqPath string) {
es.Request("GET", reqPath).
Expect().
Status(StatusOK).Body().Equal(testSubdomainHost() + reqPath)
}
subdomainRequest("/")
subdomainRequest("/path1")
subdomainRequest("/path2")
subdomainRequest("/namedpath/theparam1/something/theparam2")
subdomainRequest("/namedpath/theparam1/something/theparam2/else")
}
}
func TestMuxPathEscape(t *testing.T) {
initDefault()
Get("/details/:name", func(ctx *Context) {
name := ctx.Param("name")
highlight := ctx.URLParam("highlight")
ctx.Text(StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
})
e := Tester(t)
e.GET("/details/Sakamoto desu ga").
WithQuery("highlight", "text").
Expect().Status(StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text")
}
func TestMuxCustomErrors(t *testing.T) {
var (
notFoundMessage = "Iris custom message for 404 not found"
internalServerMessage = "Iris custom message for 500 internal server error"
testRoutesCustomErrors = []testRoute{
// NOT FOUND CUSTOM ERRORS - not registed
{"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
// SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registed
{"GET", "/test_get_panic_custom", "/test_get_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"POST", "/test_post_panic_custom", "/test_post_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"PUT", "/test_put_panic_custom", "/test_put_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", "", internalServerMessage, 500, true, nil, nil},
}
)
initDefault()
// first register the testRoutes needed
for _, r := range testRoutesCustomErrors {
if r.Register {
HandleFunc(r.Method, r.Path, func(ctx *Context) {
ctx.EmitError(r.Status)
})
}
}
// register the custom errors
OnError(404, func(ctx *Context) {
ctx.Write("%s", notFoundMessage)
})
OnError(500, func(ctx *Context) {
ctx.Write("%s", internalServerMessage)
})
// create httpexpect instance that will call fasthtpp.RequestHandler directly
e := Tester(t)
// run the tests
for _, r := range testRoutesCustomErrors {
e.Request(r.Method, r.RequestPath).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}
type testUserAPI struct {
*Context
}
// GET /users
func (u testUserAPI) Get() {
u.Write("Get Users\n")
}
// GET /users/:param1 which its value passed to the id argument
func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1")
u.Write("Get By %s\n", id)
}
// PUT /users
func (u testUserAPI) Put() {
u.Write("Put, name: %s\n", u.FormValue("name"))
}
// POST /users/:param1
func (u testUserAPI) PostBy(id string) {
u.Write("Post By %s, name: %s\n", id, u.FormValue("name"))
}
// DELETE /users/:param1
func (u testUserAPI) DeleteBy(id string) {
u.Write("Delete By %s\n", id)
}
func TestMuxAPI(t *testing.T) {
initDefault()
middlewareResponseText := "I assume that you are authenticated\n"
API("/users", testUserAPI{}, func(ctx *Context) { // optional middleware for .API
// do your work here, or render a login window if not logged in, get the user and send it to the next middleware, or do all here
ctx.Set("user", "username")
ctx.Next()
}, func(ctx *Context) {
if ctx.Get("user") == "username" {
ctx.Write(middlewareResponseText)
ctx.Next()
} else {
ctx.SetStatusCode(StatusUnauthorized)
}
})
e := Tester(t)
userID := "4077"
formname := "kataras"
e.GET("/users").Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get Users\n")
e.GET("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n")
e.PUT("/users").WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n")
e.POST("/users/"+userID).WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n")
e.DELETE("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n")
}
type myTestHandlerData struct {
Sysname string // this will be the same for all requests
Version int // this will be the same for all requests
DynamicPathParameter string // this will be different for each request
}
type myTestCustomHandler struct {
data myTestHandlerData
}
func (m *myTestCustomHandler) Serve(ctx *Context) {
data := &m.data
data.DynamicPathParameter = ctx.Param("myparam")
ctx.JSON(StatusOK, data)
}
func TestMuxCustomHandler(t *testing.T) {
initDefault()
myData := myTestHandlerData{
Sysname: "Redhat",
Version: 1,
}
Handle("GET", "/custom_handler_1/:myparam", &myTestCustomHandler{myData})
Handle("GET", "/custom_handler_2/:myparam", &myTestCustomHandler{myData})
e := Tester(t)
// two times per testRoute
param1 := "thisimyparam1"
expectedData1 := myData
expectedData1.DynamicPathParameter = param1
e.GET("/custom_handler_1/" + param1).Expect().Status(StatusOK).JSON().Equal(expectedData1)
param2 := "thisimyparam2"
expectedData2 := myData
expectedData2.DynamicPathParameter = param2
e.GET("/custom_handler_1/" + param2).Expect().Status(StatusOK).JSON().Equal(expectedData2)
param3 := "thisimyparam3"
expectedData3 := myData
expectedData3.DynamicPathParameter = param3
e.GET("/custom_handler_2/" + param3).Expect().Status(StatusOK).JSON().Equal(expectedData3)
param4 := "thisimyparam4"
expectedData4 := myData
expectedData4.DynamicPathParameter = param4
e.GET("/custom_handler_2/" + param4).Expect().Status(StatusOK).JSON().Equal(expectedData4)
}

View File

@ -1,225 +0,0 @@
package iris
import (
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/render/rest"
"github.com/kataras/iris/render/template"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/websocket"
///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled.
_ "github.com/kataras/iris/sessions/providers/memory"
_ "github.com/kataras/iris/sessions/providers/redis"
)
// Default entry, use it with iris.$anyPublicFunc
var (
Default *Framework
Config *config.Iris
Logger *logger.Logger
Plugins PluginContainer
Websocket websocket.Server
HTTPServer *Server
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
// never fires false, if the .Close called then the channel is re-allocating.
// the channel is always oepen until you close it when you don't need this.
//
// Note: it is a simple channel and decided to put it here and no inside HTTPServer, doesn't have statuses just true and false, simple as possible
// Where to use that?
// this is used on extreme cases when you don't know which .Listen/.NoListen will be called
// and you want to run/declare something external-not-Iris (all Iris functionality declared before .Listen/.NoListen) AFTER the server is started and plugins finished.
// see the server_test.go for an example
Available chan bool
)
func init() {
Default = New()
Config = Default.Config
Logger = Default.Logger
Plugins = Default.Plugins
Websocket = Default.Websocket
HTTPServer = Default.HTTPServer
Available = Default.Available
}
const (
/* conversional */
// HTMLEngine conversion for config.HTMLEngine
HTMLEngine = config.HTMLEngine
// PongoEngine conversion for config.PongoEngine
PongoEngine = config.PongoEngine
// MarkdownEngine conversion for config.MarkdownEngine
MarkdownEngine = config.MarkdownEngine
// JadeEngine conversion for config.JadeEngine
JadeEngine = config.JadeEngine
// AmberEngine conversion for config.AmberEngine
AmberEngine = config.AmberEngine
// HandlebarsEngine conversion for config.HandlebarsEngine
HandlebarsEngine = config.HandlebarsEngine
// DefaultEngine conversion for config.DefaultEngine
DefaultEngine = config.DefaultEngine
// NoEngine conversion for config.NoEngine
NoEngine = config.NoEngine
// NoLayout to disable layout for a particular template file
// conversion for config.NoLayout
NoLayout = config.NoLayout
/* end conversional */
)
// Framework is our God |\| Google.Search('Greek mythology Iris')
//
// Implements the FrameworkAPI
type Framework struct {
*muxAPI
rest *rest.Render
templates *template.Template
sessions *sessions.Manager
// fields which are useful to the user/dev
HTTPServer *Server
Config *config.Iris
Logger *logger.Logger
Plugins PluginContainer
Websocket websocket.Server
Available chan bool
// this is setted once when .Tester(t) is called
testFramework *httpexpect.Expect
}
// New creates and returns a new Iris station aka Framework.
//
// Receives an optional config.Iris as parameter
// If empty then config.Default() is used instead
func New(cfg ...config.Iris) *Framework {
c := config.Default().Merge(cfg)
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
// some things never change :)
s := &Framework{Config: &c, Available: make(chan bool)}
{
///NOTE: set all with s.Config pointer
// set the Logger
s.Logger = logger.New(s.Config.Logger)
// set the plugin container
s.Plugins = &pluginContainer{logger: s.Logger}
// set the websocket server
s.Websocket = websocket.NewServer(s.Config.Websocket)
// set the servemux, which will provide us the public API also, with its context pool
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
// set the public router API (and party)
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
// set the server
s.HTTPServer = newServer(&s.Config.Server)
}
return s
}
func (s *Framework) initialize() {
// set sessions
if s.Config.Sessions.Provider != "" {
s.sessions = sessions.New(s.Config.Sessions)
}
// set the rest
s.rest = rest.New(s.Config.Render.Rest)
// set templates if not already setted
s.prepareTemplates()
// listen to websocket connections
websocket.RegisterServer(s, s.Websocket, s.Logger)
// prepare the mux & the server
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
s.mux.setEscapePath(!s.Config.DisablePathEscape)
s.mux.setHostname(s.HTTPServer.VirtualHostname())
// set the debug profiling handlers if ProfilePath is setted
if debugPath := s.Config.ProfilePath; debugPath != "" {
s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
}
if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize {
s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
}
}
// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen
func (s *Framework) prepareTemplates() {
// prepare the templates
if s.templates == nil {
// These functions are directly contact with Iris' functionality.
funcs := map[string]interface{}{
"url": s.URL,
"urlpath": s.Path,
}
template.RegisterSharedFuncs(funcs)
s.templates = template.New(s.Config.Render.Template)
}
}
// openServer is internal method, open the server with specific options passed by the Listen and ListenTLS
// it's a blocking func
func (s *Framework) openServer() (err error) {
s.initialize()
s.Plugins.DoPreListen(s)
// set the server's handler now, in order to give the chance to the plugins to add their own middlewares and routes to this station
s.HTTPServer.SetHandler(s.mux)
if err = s.HTTPServer.Open(); err == nil {
// print the banner
if !s.Config.DisableBanner {
s.Logger.PrintBanner(banner,
fmt.Sprintf("%s: Running at %s\n", time.Now().Format(config.TimeFormat),
s.HTTPServer.Host()))
}
s.Plugins.DoPostListen(s)
s.Available <- true
ch := make(chan os.Signal)
<-ch
s.Close()
}
return
}
// closeServer is used to close the tcp listener from the server, returns an error
func (s *Framework) closeServer() error {
s.Plugins.DoPreClose(s)
s.Available = make(chan bool)
return s.HTTPServer.Close()
}
// justServe initializes the whole framework but server doesn't listens to a specific net.Listener
func (s *Framework) justServe(optionalAddr ...string) *Server {
s.HTTPServer.Config = &s.Config.Server
if len(optionalAddr) > 0 {
s.HTTPServer.Config.ListeningAddr = optionalAddr[0]
}
s.initialize()
s.Plugins.DoPreListen(s)
s.HTTPServer.SetHandler(s.mux)
s.Plugins.DoPostListen(s)
go func() {
s.Available <- true
}()
return s.HTTPServer
}
// tester returns the test framework
func (s *Framework) tester(t *testing.T) *httpexpect.Expect {
if s.testFramework == nil {
s.testFramework = NewTester(s, t)
}
return s.testFramework
}

610
iris.go
View File

@ -61,40 +61,109 @@ import (
"testing" "testing"
"time" "time"
"sync"
"github.com/gavv/httpexpect" "github.com/gavv/httpexpect"
"github.com/iris-contrib/errors" "github.com/iris-contrib/errors"
"github.com/kataras/iris/config" "github.com/kataras/iris/config"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/render/rest"
"github.com/kataras/iris/render/template"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/utils" "github.com/kataras/iris/utils"
"github.com/kataras/iris/websocket"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled.
_ "github.com/kataras/iris/sessions/providers/memory"
_ "github.com/kataras/iris/sessions/providers/redis"
) )
const ( const (
// Version of the iris // Version of the iris
Version = "3.0.0-rc.4" Version = "3.0.0-pre.release"
banner = ` _____ _
// HTMLEngine conversion for config.HTMLEngine
HTMLEngine = config.HTMLEngine
// PongoEngine conversion for config.PongoEngine
PongoEngine = config.PongoEngine
// MarkdownEngine conversion for config.MarkdownEngine
MarkdownEngine = config.MarkdownEngine
// JadeEngine conversion for config.JadeEngine
JadeEngine = config.JadeEngine
// AmberEngine conversion for config.AmberEngine
AmberEngine = config.AmberEngine
// HandlebarsEngine conversion for config.HandlebarsEngine
HandlebarsEngine = config.HandlebarsEngine
// DefaultEngine conversion for config.DefaultEngine
DefaultEngine = config.DefaultEngine
// NoEngine conversion for config.NoEngine
NoEngine = config.NoEngine
// NoLayout to disable layout for a particular template file
// conversion for config.NoLayout
NoLayout = config.NoLayout
banner = ` _____ _
|_ _| (_) |_ _| (_)
| | ____ _ ___ | | ____ _ ___
| | | __|| |/ __| | | | __|| |/ __|
_| |_| | | |\__ \ _| |_| | | |\__ \
|_____|_| |_||___/ ` + Version + ` |_____|_| |_||___/ ` + Version + ` `
`
) )
// Default entry, use it with iris.$anyPublicFunc
var (
Default *Framework
Config *config.Iris
Logger *logger.Logger
Plugins PluginContainer
Websocket websocket.Server
Servers *ServerList
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
// never fires false, if the .Close called then the channel is re-allocating.
// the channel is closed only when .ListenVirtual is used, otherwise it remains open until you close it.
//
// Note: it is a simple channel and decided to put it here and no inside HTTPServer, doesn't have statuses just true and false, simple as possible
// Where to use that?
// this is used on extreme cases when you don't know which .Listen/.NoListen will be called
// and you want to run/declare something external-not-Iris (all Iris functionality declared before .Listen/.NoListen) AFTER the server is started and plugins finished.
// see the server_test.go for an example
Available chan bool
)
func initDefault() {
Default = New()
Config = Default.Config
Logger = Default.Logger
Plugins = Default.Plugins
Websocket = Default.Websocket
Servers = Default.Servers
Available = Default.Available
}
func init() {
initDefault()
}
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------Framework implementation-----------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
type ( type (
// FrameworkAPI contains the main Iris Public API // FrameworkAPI contains the main Iris Public API
FrameworkAPI interface { FrameworkAPI interface {
MuxAPI MuxAPI
Must(error) Must(error)
ListenWithErr(string) error AddServer(config.Server) *Server
ListenTo(config.Server) error
Listen(string) Listen(string)
ListenTLSWithErr(string, string, string) error
ListenTLS(string, string, string) ListenTLS(string, string, string)
ListenUNIXWithErr(string, os.FileMode) error
ListenUNIX(string, os.FileMode) ListenUNIX(string, os.FileMode)
SecondaryListen(config.Server) *Server ListenVirtual(...string) *Server
NoListen(...string) *Server Go() error
Close() Close() error
// global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also
MustUse(...Handler) MustUse(...Handler)
MustUseFunc(...HandlerFunc) MustUseFunc(...HandlerFunc)
@ -108,56 +177,138 @@ type (
Tester(t *testing.T) *httpexpect.Expect Tester(t *testing.T) *httpexpect.Expect
} }
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route) // Framework is our God |\| Google.Search('Greek mythology Iris')
RouteNameFunc func(string) //
// MuxAPI the visible api for the serveMux // Implements the FrameworkAPI
MuxAPI interface { Framework struct {
Party(string, ...HandlerFunc) MuxAPI *muxAPI
// middleware serial, appending rest *rest.Render
Use(...Handler) templates *template.Template
UseFunc(...HandlerFunc) sessions *sessions.Manager
// fields which are useful to the user/dev
// main handlers // the last added server is the main server
Handle(string, string, ...Handler) RouteNameFunc Servers *ServerList
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc Config *config.Iris
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles Logger *logger.Logger
H_(string, string, func(context.IContext)) func(string) Plugins PluginContainer
API(string, HandlerAPI, ...HandlerFunc) Websocket websocket.Server
Available chan bool
// http methods // this is setted once when .Tester(t) is called
Get(string, ...HandlerFunc) RouteNameFunc testFramework *httpexpect.Expect
Post(string, ...HandlerFunc) RouteNameFunc
Put(string, ...HandlerFunc) RouteNameFunc
Delete(string, ...HandlerFunc) RouteNameFunc
Connect(string, ...HandlerFunc) RouteNameFunc
Head(string, ...HandlerFunc) RouteNameFunc
Options(string, ...HandlerFunc) RouteNameFunc
Patch(string, ...HandlerFunc) RouteNameFunc
Trace(string, ...HandlerFunc) RouteNameFunc
Any(string, ...HandlerFunc)
// static content
StaticHandler(string, int, bool, bool, []string) HandlerFunc
Static(string, string, int) RouteNameFunc
StaticFS(string, string, int) RouteNameFunc
StaticWeb(string, string, int) RouteNameFunc
StaticServe(string, ...string) RouteNameFunc
StaticContent(string, string, []byte) func(string)
Favicon(string, ...string) RouteNameFunc
// templates
Layout(string) MuxAPI // returns itself
} }
) )
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------Framework implementation-----------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
var _ FrameworkAPI = &Framework{} var _ FrameworkAPI = &Framework{}
// New creates and returns a new Iris station aka Framework.
//
// Receives an optional config.Iris as parameter
// If empty then config.Default() is used instead
func New(cfg ...config.Iris) *Framework {
c := config.Default().Merge(cfg)
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
// some things never change :)
s := &Framework{Config: &c, Available: make(chan bool)}
{
///NOTE: set all with s.Config pointer
// set the Logger
s.Logger = logger.New(s.Config.Logger)
// set the plugin container
s.Plugins = &pluginContainer{logger: s.Logger}
// set the websocket server
s.Websocket = websocket.NewServer(s.Config.Websocket)
// set the servemux, which will provide us the public API also, with its context pool
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
// set the public router API (and party)
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
s.Servers = &ServerList{mux: mux, servers: make([]*Server, 0)}
}
return s
}
func (s *Framework) initialize() {
// set sessions
if s.Config.Sessions.Provider != "" {
s.sessions = sessions.New(s.Config.Sessions)
}
// set the rest
s.rest = rest.New(s.Config.Render.Rest)
// set templates if not already setted
s.prepareTemplates()
// listen to websocket connections
websocket.RegisterServer(s, s.Websocket, s.Logger)
// prepare the mux & the server
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
s.mux.setEscapePath(!s.Config.DisablePathEscape)
// set the debug profiling handlers if ProfilePath is setted
if debugPath := s.Config.ProfilePath; debugPath != "" {
s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
}
}
// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen
func (s *Framework) prepareTemplates() {
// prepare the templates
if s.templates == nil {
// These functions are directly contact with Iris' functionality.
funcs := map[string]interface{}{
"url": s.URL,
"urlpath": s.Path,
}
template.RegisterSharedFuncs(funcs)
s.templates = template.New(s.Config.Render.Template)
}
}
// Go starts the iris station, listens to all registered servers, and prepare only if Virtual
func Go() error {
return Default.Go()
}
// Go starts the iris station, listens to all registered servers, and prepare only if Virtual
func (s *Framework) Go() error {
s.initialize()
s.Plugins.DoPreListen(s)
if firstErr := s.Servers.OpenAll(); firstErr != nil {
return firstErr
}
// print the banner
if !s.Config.DisableBanner {
openedServers := s.Servers.GetAllOpened()
l := len(openedServers)
hosts := make([]string, l, l)
for i, srv := range openedServers {
hosts[i] = srv.Host()
}
bannerMessage := time.Now().Format(config.TimeFormat) + ": Running at " + strings.Join(hosts, ", ")
s.Logger.PrintBanner(banner, bannerMessage)
}
s.Plugins.DoPostListen(s)
go func() { s.Available <- true }()
ch := make(chan os.Signal)
<-ch
s.Close() // btw, don't panic here
return nil
}
// Must panics on error, it panics on registed iris' logger // Must panics on error, it panics on registed iris' logger
func Must(err error) { func Must(err error) {
Default.Must(err) Default.Must(err)
@ -170,60 +321,74 @@ func (s *Framework) Must(err error) {
} }
} }
// ListenWithErr starts the standalone http server // AddServer same as .Servers.Add(config.Server) instead
// which listens to the addr parameter which as the form of
// host:port
// //
// It returns an error you are responsible how to handle this // AddServers starts a server which listens to this station
// if you need a func to panic on error use the Listen // Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
// ex: log.Fatal(iris.ListenWithErr(":8080")) //
func ListenWithErr(addr string) error { // this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
return Default.ListenWithErr(addr) //
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func AddServer(cfg config.Server) *Server {
return Default.AddServer(cfg)
}
// AddServer same as .Servers.Add(config.Server) instead
//
// AddServers starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func (s *Framework) AddServer(cfg config.Server) *Server {
return s.Servers.Add(cfg)
}
// ListenTo listens to a server but receives the full server's configuration
// returns an error, you're responsible to handle that
// or use the iris.Must(iris.ListenTo(config.Server{}))
//
// it's a blocking func
func ListenTo(cfg config.Server) error {
return Default.ListenTo(cfg)
}
// ListenTo listens to a server but receives the full server's configuration
// it's a blocking func
func (s *Framework) ListenTo(cfg config.Server) (err error) {
s.Servers.Add(cfg)
return s.Go()
} }
// Listen starts the standalone http server // Listen starts the standalone http server
// which listens to the addr parameter which as the form of // which listens to the addr parameter which as the form of
// host:port // host:port
// //
// It panics on error if you need a func to return an error use the ListenWithErr // It panics on error if you need a func to return an error, use the ListenTo
// ex: iris.Listen(":8080") // ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func Listen(addr string) { func Listen(addr string) {
Default.Listen(addr) Default.Listen(addr)
} }
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
func (s *Framework) ListenWithErr(addr string) error {
s.Config.Server.ListeningAddr = addr
return s.openServer()
}
// Listen starts the standalone http server // Listen starts the standalone http server
// which listens to the addr parameter which as the form of // which listens to the addr parameter which as the form of
// host:port // host:port
// //
// It panics on error if you need a func to return an error use the ListenWithErr // It panics on error if you need a func to return an error, use the ListenTo
// ex: iris.Listen(":8080") // ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func (s *Framework) Listen(addr string) { func (s *Framework) Listen(addr string) {
s.Must(s.ListenWithErr(addr)) s.Must(s.ListenTo(config.Server{ListeningAddr: addr}))
}
// ListenTLSWithErr Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
return Default.ListenTLSWithErr(addr, certFile, keyFile)
} }
// ListenTLS Starts a https server with certificates, // ListenTLS Starts a https server with certificates,
@ -232,142 +397,86 @@ func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
// which listens to the addr parameter which as the form of // which listens to the addr parameter which as the form of
// host:port // host:port
// //
// It panics on error if you need a func to return an error use the ListenTLSWithErr // It panics on error if you need a func to return an error, use the ListenTo
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key") // ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
func ListenTLS(addr string, certFile string, keyFile string) { func ListenTLS(addr string, certFile string, keyFile string) {
Default.ListenTLS(addr, certFile, keyFile) Default.ListenTLS(addr, certFile, keyFile)
} }
// ListenTLSWithErr Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error {
if certFile == "" || keyFile == "" {
return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL")
}
s.Config.Server.ListeningAddr = addr
s.Config.Server.CertFile = certFile
s.Config.Server.KeyFile = keyFile
return s.openServer()
}
// ListenTLS Starts a https server with certificates, // ListenTLS Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail // if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed // only https:// connections are allowed
// which listens to the addr parameter which as the form of // which listens to the addr parameter which as the form of
// host:port // host:port
// //
// It panics on error if you need a func to return an error use the ListenTLSWithErr // It panics on error if you need a func to return an error, use the ListenTo
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key") // ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) { func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
s.Must(s.ListenTLSWithErr(addr, certFile, keyFile)) if certFile == "" || keyFile == "" {
} s.Logger.Panic("You should provide certFile and keyFile for TLS/SSL")
}
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix s.Must(s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}))
// returns an error if something bad happens when trying to listen to
func ListenUNIXWithErr(addr string, mode os.FileMode) error {
return Default.ListenUNIXWithErr(addr, mode)
} }
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix // ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
// panics on error //
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
func ListenUNIX(addr string, mode os.FileMode) { func ListenUNIX(addr string, mode os.FileMode) {
Default.ListenUNIX(addr, mode) Default.ListenUNIX(addr, mode)
} }
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
// returns an error if something bad happens when trying to listen to
func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error {
s.Config.Server.ListeningAddr = addr
s.Config.Server.Mode = mode
return s.openServer()
}
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix // ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
// panics on error //
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) { func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
s.Must(s.ListenUNIXWithErr(addr, mode)) s.Must(ListenTo(config.Server{ListeningAddr: addr, Mode: mode}))
} }
// SecondaryListen starts a server which listens to this station // ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions.
//
// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen.
func SecondaryListen(cfg config.Server) *Server {
return Default.SecondaryListen(cfg)
}
// SecondaryListen starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions.
//
// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen.
func (s *Framework) SecondaryListen(cfg config.Server) *Server {
srv := newServer(&cfg)
// add a post listen event to start this server after the previous started
s.Plugins.Add(PostListenFunc(func(*Framework) {
go func() { // goroutine in order to not block any runtime post listeners
srv.Handler = s.HTTPServer.Handler
if err := srv.Open(); err == nil {
ch := make(chan os.Signal)
<-ch
srv.Close()
}
}()
}))
return srv
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
func NoListen(optionalAddr ...string) *Server {
return Default.NoListen(optionalAddr...)
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
// initializes the whole framework but server doesn't listens to a specific net.Listener // initializes the whole framework but server doesn't listens to a specific net.Listener
func (s *Framework) NoListen(optionalAddr ...string) *Server { // it is not blocking the app
return s.justServe(optionalAddr...) func ListenVirtual(optionalAddr ...string) *Server {
return Default.ListenVirtual(optionalAddr...)
} }
// CloseWithErr terminates the server and returns an error if any // ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
func CloseWithErr() error { // initializes the whole framework but server doesn't listens to a specific net.Listener
return Default.CloseWithErr() // it is not blocking the app
func (s *Framework) ListenVirtual(optionalAddr ...string) *Server {
s.Config.DisableBanner = true
cfg := config.DefaultServer()
if len(optionalAddr) > 0 && optionalAddr[0] != "" {
cfg.ListeningAddr = optionalAddr[0]
}
cfg.Virtual = true
go func() {
s.Must(s.ListenTo(cfg))
}()
if ok := <-s.Available; !ok {
s.Logger.Panic("Unexpected error:Virtual server cannot start, please report this as bug!!")
}
close(s.Available)
return s.Servers.Main()
} }
//Close terminates the server and panic if error occurs // Close terminates all the registered servers and returns an error if any
func Close() { // if you want to panic on this error use the iris.Must(iris.Close())
Default.Close() func Close() error {
return Default.Close()
} }
// CloseWithErr terminates the server and returns an error if any // Close terminates all the registered servers and returns an error if any
func (s *Framework) CloseWithErr() error { // if you want to panic on this error use the iris.Must(iris.Close())
return s.closeServer() func (s *Framework) Close() error {
} s.Plugins.DoPreClose(s)
s.Available = make(chan bool)
//Close terminates the server and panic if error occurs return s.Servers.CloseAll()
func (s *Framework) Close() {
s.Must(s.CloseWithErr())
} }
// MustUse registers Handler middleware to the beginning, prepends them instead of append // MustUse registers Handler middleware to the beginning, prepends them instead of append
@ -541,13 +650,13 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
if r == nil { if r == nil {
return return
} }
srv := s.Servers.Main()
scheme := "http://" scheme := "http://"
if s.HTTPServer.IsSecure() { if srv.IsSecure() {
scheme = "https://" scheme = "https://"
} }
host := s.HTTPServer.VirtualHost() host := srv.VirtualHost()
arguments := args[0:] arguments := args[0:]
// join arrays as arguments // join arrays as arguments
@ -608,21 +717,35 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{},
// NewTester Prepares and returns a new test framework based on the api // NewTester Prepares and returns a new test framework based on the api
// is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T) // is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T)
func NewTester(api *Framework, t *testing.T) *httpexpect.Expect { func NewTester(api *Framework, t *testing.T) *httpexpect.Expect {
api.Config.DisableBanner = true srv := api.Servers.Main()
if !api.HTTPServer.IsListening() { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app if srv == nil { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app
api.NoListen() srv = api.ListenVirtual(api.Config.Tester.ListeningAddr)
if ok := <-api.Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
}
close(api.Available)
} }
handler := api.HTTPServer.Handler opened := api.Servers.GetAllOpened()
h := srv.Handler
baseURL := srv.FullHost()
if len(opened) > 1 {
baseURL = ""
//we have more than one server, so we will create a handler here and redirect by registered listening addresses
h = func(reqCtx *fasthttp.RequestCtx) {
for _, s := range opened {
if strings.HasPrefix(reqCtx.URI().String(), s.FullHost()) { // yes on :80 should be passed :80 also, this is inneed for multiserver testing
s.Handler(reqCtx)
break
}
}
}
}
if api.Config.Tester.ExplicitURL {
baseURL = ""
}
testConfiguration := httpexpect.Config{ testConfiguration := httpexpect.Config{
BaseURL: api.HTTPServer.FullHost(), BaseURL: baseURL,
Client: &http.Client{ Client: &http.Client{
Transport: httpexpect.NewFastBinder(handler), Transport: httpexpect.NewFastBinder(h),
Jar: httpexpect.NewJar(), Jar: httpexpect.NewJar(),
}, },
Reporter: httpexpect.NewAssertReporter(t), Reporter: httpexpect.NewAssertReporter(t),
@ -644,7 +767,10 @@ func Tester(t *testing.T) *httpexpect.Expect {
// Tester returns the test framework for this iris insance // Tester returns the test framework for this iris insance
func (s *Framework) Tester(t *testing.T) *httpexpect.Expect { func (s *Framework) Tester(t *testing.T) *httpexpect.Expect {
return s.tester(t) if s.testFramework == nil {
s.testFramework = NewTester(s, t)
}
return s.testFramework
} }
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
@ -652,12 +778,56 @@ func (s *Framework) Tester(t *testing.T) *httpexpect.Expect {
// ----------------------------------MuxAPI implementation------------------------------ // ----------------------------------MuxAPI implementation------------------------------
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
type (
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
RouteNameFunc func(string)
// MuxAPI the visible api for the serveMux
MuxAPI interface {
Party(string, ...HandlerFunc) MuxAPI
// middleware serial, appending
Use(...Handler)
UseFunc(...HandlerFunc)
type muxAPI struct { // main handlers
mux *serveMux Handle(string, string, ...Handler) RouteNameFunc
relativePath string HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
middleware Middleware // H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
} H_(string, string, func(context.IContext)) func(string)
API(string, HandlerAPI, ...HandlerFunc)
// http methods
Get(string, ...HandlerFunc) RouteNameFunc
Post(string, ...HandlerFunc) RouteNameFunc
Put(string, ...HandlerFunc) RouteNameFunc
Delete(string, ...HandlerFunc) RouteNameFunc
Connect(string, ...HandlerFunc) RouteNameFunc
Head(string, ...HandlerFunc) RouteNameFunc
Options(string, ...HandlerFunc) RouteNameFunc
Patch(string, ...HandlerFunc) RouteNameFunc
Trace(string, ...HandlerFunc) RouteNameFunc
Any(string, ...HandlerFunc)
// static content
StaticHandler(string, int, bool, bool, []string) HandlerFunc
Static(string, string, int) RouteNameFunc
StaticFS(string, string, int) RouteNameFunc
StaticWeb(string, string, int) RouteNameFunc
StaticServe(string, ...string) RouteNameFunc
StaticContent(string, string, []byte) func(string)
Favicon(string, ...string) RouteNameFunc
// templates
Layout(string) MuxAPI // returns itself
}
muxAPI struct {
mux *serveMux
relativePath string
middleware Middleware
}
)
var _ MuxAPI = &muxAPI{}
var ( var (
// errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..' // errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..'
@ -666,8 +836,6 @@ var (
errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s") errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
) )
var _ MuxAPI = &muxAPI{}
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun // Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI { func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {

14
iris_test.go Normal file
View File

@ -0,0 +1,14 @@
package iris
/*
The most iris.go file implementation tested at other files like context_test, http_test, the untested are the Static methods, the favicon and some interfaces, which I already
tested them on production and I don't expect unexpected behavior but if you think we need more:
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
*/
// Notes:
//
// We use Default := New() via initDefault() and not api := New() neither just Default. because we want to cover as much code as possible
// The tests are mostly end-to-end, except some features like plugins.
//

View File

@ -214,11 +214,13 @@ type pluginContainer struct {
customEvents map[string][]func() customEvents map[string][]func()
downloader *pluginDownloadManager downloader *pluginDownloadManager
logger *logger.Logger logger *logger.Logger
mu sync.Mutex
} }
// Add activates the plugins and if succeed then adds it to the activated plugins list // Add activates the plugins and if succeed then adds it to the activated plugins list
func (p *pluginContainer) Add(plugins ...Plugin) error { func (p *pluginContainer) Add(plugins ...Plugin) error {
for _, plugin := range plugins { for _, plugin := range plugins {
if p.activatedPlugins == nil { if p.activatedPlugins == nil {
p.activatedPlugins = make([]Plugin, 0) p.activatedPlugins = make([]Plugin, 0)
} }
@ -232,10 +234,16 @@ func (p *pluginContainer) Add(plugins ...Plugin) error {
} }
// Activate the plugin, if no error then add it to the plugins // Activate the plugin, if no error then add it to the plugins
if pluginObj, ok := plugin.(pluginActivate); ok { if pluginObj, ok := plugin.(pluginActivate); ok {
err := pluginObj.Activate(p) tempPluginContainer := *p
err := pluginObj.Activate(&tempPluginContainer)
if err != nil { if err != nil {
return errPluginActivate.Format(pName, err.Error()) return errPluginActivate.Format(pName, err.Error())
} }
tempActivatedPluginsLen := len(tempPluginContainer.activatedPlugins)
if tempActivatedPluginsLen != len(p.activatedPlugins)+tempActivatedPluginsLen+1 { // see test: plugin_test.go TestPluginActivate && TestPluginActivationError
p.activatedPlugins = tempPluginContainer.activatedPlugins
}
} }
// All ok, add it to the plugins list // All ok, add it to the plugins list

200
plugin_test.go Normal file
View File

@ -0,0 +1,200 @@
package iris
/*
Contains tests for plugin, no end-to-end, just local-object tests, these are enoguh for now.
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
*/
import (
"fmt"
"testing"
)
const (
testPluginExDescription = "Description for My test plugin"
testPluginExName = "My test plugin"
)
type testPluginEx struct {
named, activated, descriptioned bool
prelistenran, postlistenran, precloseran bool
}
func (t *testPluginEx) GetName() string {
fmt.Println("GetName Struct")
t.named = true
return testPluginExName
}
func (t *testPluginEx) GetDescription() string {
fmt.Println("GetDescription Struct")
t.descriptioned = true
return testPluginExDescription
}
func (t *testPluginEx) Activate(p PluginContainer) error {
fmt.Println("Activate Struct")
t.activated = true
return nil
}
func (t *testPluginEx) PreListen(*Framework) {
fmt.Println("PreListen Struct")
t.prelistenran = true
}
func (t *testPluginEx) PostListen(*Framework) {
fmt.Println("PostListen Struct")
t.postlistenran = true
}
func (t *testPluginEx) PreClose(*Framework) {
fmt.Println("PreClose Struct")
t.precloseran = true
}
func ExamplePlugins_Add() {
initDefault()
Plugins.Add(PreListenFunc(func(*Framework) {
fmt.Println("PreListen Func")
}))
Plugins.Add(PostListenFunc(func(*Framework) {
fmt.Println("PostListen Func")
}))
Plugins.Add(PreCloseFunc(func(*Framework) {
fmt.Println("PreClose Func")
}))
myplugin := &testPluginEx{}
Plugins.Add(myplugin)
desc := Plugins.GetDescription(myplugin)
fmt.Println(desc)
ListenVirtual()
Close()
// Output:
// GetName Struct
// Activate Struct
// GetDescription Struct
// Description for My test plugin
// PreListen Func
// PreListen Struct
// PostListen Func
// PostListen Struct
// PreClose Func
// PreClose Struct
}
// if a plugin has GetName, then it should be registered only one time, the name exists for that reason, it's like unique ID
func TestPluginDublicateName(t *testing.T) {
var plugins pluginContainer
firstNamedPlugin := &testPluginEx{}
sameNamedPlugin := &testPluginEx{}
// err := plugins.Add(firstNamedPlugin, sameNamedPlugin) or
err := plugins.Add(firstNamedPlugin)
if err != nil {
t.Fatalf("Unexpected error when adding a plugin with name: %s", testPluginExName)
}
err = plugins.Add(sameNamedPlugin)
if err == nil {
t.Fatalf("Expected an error because of dublicate named plugin!")
}
if len(plugins.activatedPlugins) != 1 {
t.Fatalf("Expected: %d activated plugin but we got: %d", 1, len(plugins.activatedPlugins))
}
}
type testPluginActivationType struct {
shouldError bool
}
func (t testPluginActivationType) Activate(p PluginContainer) error {
p.Add(&testPluginEx{})
if t.shouldError {
return fmt.Errorf("An error happens, this plugin and the added plugins by this plugin should not be registered")
}
return nil
}
func TestPluginActivate(t *testing.T) {
var plugins pluginContainer
myplugin := testPluginActivationType{shouldError: false}
plugins.Add(myplugin)
if len(plugins.activatedPlugins) != 2 { // 2 because it registeres a second plugin also
t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins))
}
}
// if any error returned from the Activate plugin's method, then this plugin and the plugins it registers should not be registered at all
func TestPluginActivationError(t *testing.T) {
var plugins pluginContainer
myplugin := testPluginActivationType{shouldError: true}
plugins.Add(myplugin)
if len(plugins.activatedPlugins) > 0 {
t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins))
}
}
func TestPluginEvents(t *testing.T) {
var plugins pluginContainer
var prelistenran, postlistenran, precloseran bool
plugins.Add(PreListenFunc(func(*Framework) {
prelistenran = true
}))
plugins.Add(PostListenFunc(func(*Framework) {
postlistenran = true
}))
plugins.Add(PreCloseFunc(func(*Framework) {
precloseran = true
}))
myplugin := &testPluginEx{}
plugins.Add(myplugin)
if len(plugins.activatedPlugins) != 4 {
t.Fatalf("Expected: %d plugins to be registed but we got: %d", 4, len(plugins.activatedPlugins))
}
desc := plugins.GetDescription(myplugin)
if desc != testPluginExDescription {
t.Fatalf("Expected: %s as Description of the plugin but got: %s", testPluginExDescription, desc)
}
plugins.DoPreListen(nil)
plugins.DoPostListen(nil)
plugins.DoPreClose(nil)
if !prelistenran {
t.Fatalf("Expected to run PreListen Func but it doesnt!")
}
if !postlistenran {
t.Fatalf("Expected to run PostListen Func but it doesnt!")
}
if !precloseran {
t.Fatalf("Expected to run PostListen Func but it doesnt!")
}
if !myplugin.named {
t.Fatalf("Plugin should be named with: %s!", testPluginExName)
}
if !myplugin.activated {
t.Fatalf("Plugin should be activated but it's not!")
}
if !myplugin.prelistenran {
t.Fatalf("Expected to run PreListen Struct but it doesnt!")
}
if !myplugin.postlistenran {
t.Fatalf("Expected to run PostListen Struct but it doesnt!")
}
if !myplugin.precloseran {
t.Fatalf("Expected to run PostListen Struct but it doesnt!")
}
}

View File

@ -19,15 +19,11 @@ var _ store.IStore = &Store{}
// GetAll returns all values // GetAll returns all values
func (s *Store) GetAll() map[string]interface{} { func (s *Store) GetAll() map[string]interface{} {
s.mu.Lock()
defer s.mu.Unlock()
return s.values return s.values
} }
// VisitAll loop each one entry and calls the callback function func(key,value) // VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k string, v interface{})) { func (s *Store) VisitAll(cb func(k string, v interface{})) {
s.mu.Lock()
defer s.mu.Unlock()
for key := range s.values { for key := range s.values {
cb(key, s.values[key]) cb(key, s.values[key])
} }
@ -36,9 +32,7 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) {
// Get returns the value of an entry by its key // Get returns the value of an entry by its key
func (s *Store) Get(key string) interface{} { func (s *Store) Get(key string) interface{} {
Provider.Update(s.sid) Provider.Update(s.sid)
s.mu.Lock()
if value, found := s.values[key]; found { if value, found := s.values[key]; found {
s.mu.Unlock()
return value return value
} }
s.mu.Unlock() s.mu.Unlock()

View File

@ -81,15 +81,11 @@ func (s *Store) update() {
// GetAll returns all values // GetAll returns all values
func (s *Store) GetAll() map[string]interface{} { func (s *Store) GetAll() map[string]interface{} {
s.mu.Lock()
defer s.mu.Unlock()
return s.values return s.values
} }
// VisitAll loop each one entry and calls the callback function func(key,value) // VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k string, v interface{})) { func (s *Store) VisitAll(cb func(k string, v interface{})) {
s.mu.Lock()
defer s.mu.Unlock()
for key := range s.values { for key := range s.values {
cb(key, s.values[key]) cb(key, s.values[key])
} }
@ -98,12 +94,10 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) {
// Get returns the value of an entry by its key // Get returns the value of an entry by its key
func (s *Store) Get(key string) interface{} { func (s *Store) Get(key string) interface{} {
Provider.Update(s.sid) Provider.Update(s.sid)
s.mu.Lock()
if value, found := s.values[key]; found { if value, found := s.values[key]; found {
s.mu.Unlock() s.mu.Unlock()
return value return value
} }
s.mu.Unlock()
return nil return nil
} }