Add support for more than one listening server to one station, virtual and no virtual

This commit is contained in:
Makis Maropoulos 2016-07-06 20:24:34 +02:00
parent d76b73427b
commit 2cc75817b7
12 changed files with 674 additions and 390 deletions

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
@ -135,13 +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(),
Tester: Tester{Debug: false}, Tester: DefaultTester(),
} }
} }

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
@ -43,7 +53,8 @@ type Server struct {
// 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
@ -59,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

@ -3,4 +3,11 @@ package config
// Tester configuration // Tester configuration
type Tester struct { type Tester struct {
Debug bool Debug bool
ListeningAddr string
}
// 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{Debug: false, ListeningAddr: "iris-go.com:1993"}
} }

View File

@ -218,7 +218,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]
@ -480,7 +480,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

View File

@ -26,6 +26,7 @@ type testBinderXMLData struct {
func TestBindForm(t *testing.T) { func TestBindForm(t *testing.T) {
initDefault() initDefault()
Post("/form", func(ctx *Context) { Post("/form", func(ctx *Context) {
obj := testBinderData{} obj := testBinderData{}
err := ctx.ReadForm(&obj) err := ctx.ReadForm(&obj)
@ -36,6 +37,7 @@ func TestBindForm(t *testing.T) {
}) })
e := Tester(t) e := Tester(t)
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"}, passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"},
"[1]": []string{"mydata2"}}} "[1]": []string{"mydata2"}}}

111
context_test.go Normal file
View File

@ -0,0 +1,111 @@
package iris
import "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 TestContextParam(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)
}
}
func TestContextURLParam(t *testing.T) {
}

169
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")
) )
type (
// Server the http server // Server the http server
type Server struct { Server struct {
*fasthttp.Server *fasthttp.Server
listener net.Listener listener net.Listener
Config *config.Server Config config.Server
tls bool tls bool
mu sync.Mutex 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,6 +446,8 @@ func (s *Server) Open() error {
} }
reqCtx.Redirect(redirectTo, StatusMovedPermanently) reqCtx.Redirect(redirectTo, StatusMovedPermanently)
} }
} else {
s.Server.Handler = h
} }
if s.Config.Virtual { if s.Config.Virtual {
@ -452,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.")

View File

@ -1,206 +0,0 @@
package iris
import (
"fmt"
"os"
"sync"
"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 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 init() {
initDefault()
}
func initDefault() {
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 with the default configuration, which is changed on Listen functions
defaultServerCfg := config.DefaultServer()
s.HTTPServer = newServer(&defaultServerCfg)
}
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)
go func() { 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()
}

397
iris.go
View File

@ -61,26 +61,96 @@ 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-rc.4"
// 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 = ` _____ _ 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 {
@ -110,56 +180,140 @@ 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 {
panic("iris:287")
return firstErr
}
// print the banner
if !s.Config.DisableBanner {
serversMessage := time.Now().Format(config.TimeFormat) + ": Running at "
openedServers := s.Servers.GetAllOpened()
if len(openedServers) == 1 {
// if only one server then don't need to add a new line
serversMessage += openedServers[0].Host()
} else {
for _, srv := range openedServers {
serversMessage += "\n" + srv.Host()
}
}
s.Logger.PrintBanner(banner, serversMessage)
}
s.Plugins.DoPostListen(s)
go func() { s.Available <- true }()
ch := make(chan os.Signal)
<-ch
s.CloseWithErr() // 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)
@ -180,9 +334,9 @@ func ListenTo(cfg config.Server) error {
// ListenTo listens to a server but receives the full server's configuration // ListenTo listens to a server but receives the full server's configuration
// it's a blocking func // it's a blocking func
func (s *Framework) ListenTo(cfg config.Server) error { func (s *Framework) ListenTo(cfg config.Server) (err error) {
s.HTTPServer.Config = &cfg s.Servers.Add(cfg)
return s.openServer() return s.Go()
} }
// ListenWithErr starts the standalone http server // ListenWithErr starts the standalone http server
@ -214,12 +368,7 @@ func Listen(addr string) {
// if you need a func to panic on error use the Listen // if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080")) // ex: log.Fatal(iris.ListenWithErr(":8080"))
func (s *Framework) ListenWithErr(addr string) error { func (s *Framework) ListenWithErr(addr string) error {
cfg := config.DefaultServer() return s.ListenTo(config.Server{ListeningAddr: addr})
if len(addr) > 0 {
cfg.ListeningAddr = addr
}
return s.ListenTo(cfg)
} }
// Listen starts the standalone http server // Listen starts the standalone http server
@ -267,14 +416,10 @@ func ListenTLS(addr string, certFile string, keyFile string) {
// if you need a func to panic on error use the ListenTLS // if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key")) // ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error { func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error {
cfg := config.DefaultServer()
if certFile == "" || keyFile == "" { if certFile == "" || keyFile == "" {
return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL") return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL")
} }
cfg.ListeningAddr = addr return s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile})
cfg.CertFile = certFile
cfg.KeyFile = keyFile
return s.ListenTo(cfg)
} }
// ListenTLS Starts a https server with certificates, // ListenTLS Starts a https server with certificates,
@ -304,10 +449,7 @@ func ListenUNIX(addr string, mode os.FileMode) {
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix // 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 // returns an error if something bad happens when trying to listen to
func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error { func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error {
cfg := config.DefaultServer() return s.ListenTo(config.Server{ListeningAddr: addr, Mode: mode})
cfg.ListeningAddr = addr
cfg.Mode = mode
return s.ListenTo(cfg)
} }
// 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
@ -316,6 +458,9 @@ func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
s.Must(s.ListenUNIXWithErr(addr, mode)) s.Must(s.ListenUNIXWithErr(addr, mode))
} }
// SecondaryListen NOTE: This will be deprecated
// Use .Servers.Add(config.Server) instead
//
// SecondaryListen starts a server which listens to this station // 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) // Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
// //
@ -331,6 +476,9 @@ func SecondaryListen(cfg config.Server) *Server {
return Default.SecondaryListen(cfg) return Default.SecondaryListen(cfg)
} }
// SecondaryListen NOTE: This will be deprecated
// Use .Servers.Add(config.Server) instead
//
// SecondaryListen starts a server which listens to this station // 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) // Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
// //
@ -343,22 +491,7 @@ func SecondaryListen(cfg config.Server) *Server {
// //
// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. // 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 { func (s *Framework) SecondaryListen(cfg config.Server) *Server {
srv := newServer(&cfg) return s.Servers.Add(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 {
if !cfg.Virtual {
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 // NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
@ -391,34 +524,41 @@ func (s *Framework) ListenVirtual(optionalAddr ...string) *Server {
s.Config.DisableBanner = true s.Config.DisableBanner = true
cfg := config.DefaultServer() cfg := config.DefaultServer()
if len(optionalAddr) > 0 { if len(optionalAddr) > 0 && optionalAddr[0] != "" {
cfg.ListeningAddr = optionalAddr[0] cfg.ListeningAddr = optionalAddr[0]
} }
cfg.Virtual = true cfg.Virtual = true
go s.ListenTo(cfg)
go func() {
s.Must(s.ListenTo(cfg))
}()
if ok := <-s.Available; !ok { if ok := <-s.Available; !ok {
s.Logger.Panic("Unexpected error:Virtual server cannot start, please report this as bug!!") s.Logger.Panic("Unexpected error:Virtual server cannot start, please report this as bug!!")
} }
close(s.Available) close(s.Available)
return s.HTTPServer return s.Servers.Main()
} }
// CloseWithErr terminates the server and returns an error if any // CloseWithErr terminates all the registered servers and returns an error if any
func CloseWithErr() error { func CloseWithErr() error {
return Default.CloseWithErr() return Default.CloseWithErr()
} }
//Close terminates the server and panic if error occurs //Close terminates all the registered servers and panic if error occurs
func Close() { func Close() {
Default.Close() Default.Close()
} }
// CloseWithErr terminates the server and returns an error if any // CloseWithErr terminates all the registered servers and returns an error if any
func (s *Framework) CloseWithErr() error { func (s *Framework) CloseWithErr() error {
return s.closeServer() s.Plugins.DoPreClose(s)
s.Available = make(chan bool)
return s.Servers.CloseAll()
} }
//Close terminates the server and panic if error occurs //Close terminates all the registered servers and panic if error occurs
func (s *Framework) Close() { func (s *Framework) Close() {
s.Must(s.CloseWithErr()) s.Must(s.CloseWithErr())
} }
@ -594,13 +734,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
@ -661,16 +801,31 @@ 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 {
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 srv := api.Servers.Main()
api.ListenVirtual() if srv == nil { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app
srv = api.ListenVirtual(api.Config.Tester.ListeningAddr)
} }
handler := api.HTTPServer.Handler opened := api.Servers.GetAllOpened()
h := srv.Handler
baseURL := srv.FullHost()
if len(opened) > 1 {
baseURL = ""
//we have more than one server, so we will create a handler here and redirect by registered listening addresses
h = func(reqCtx *fasthttp.RequestCtx) {
for _, s := range opened {
if strings.HasPrefix(reqCtx.URI().String(), s.FullHost()) { // yes on :80 should be passed :80 also, this is inneed for multiserver testing
s.Handler(reqCtx)
break
}
}
}
}
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),
@ -703,12 +858,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
Handle(string, string, ...Handler) RouteNameFunc
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
H_(string, string, func(context.IContext)) func(string)
API(string, HandlerAPI, ...HandlerFunc)
// http methods
Get(string, ...HandlerFunc) RouteNameFunc
Post(string, ...HandlerFunc) RouteNameFunc
Put(string, ...HandlerFunc) RouteNameFunc
Delete(string, ...HandlerFunc) RouteNameFunc
Connect(string, ...HandlerFunc) RouteNameFunc
Head(string, ...HandlerFunc) RouteNameFunc
Options(string, ...HandlerFunc) RouteNameFunc
Patch(string, ...HandlerFunc) RouteNameFunc
Trace(string, ...HandlerFunc) RouteNameFunc
Any(string, ...HandlerFunc)
// static content
StaticHandler(string, int, bool, bool, []string) HandlerFunc
Static(string, string, int) RouteNameFunc
StaticFS(string, string, int) RouteNameFunc
StaticWeb(string, string, int) RouteNameFunc
StaticServe(string, ...string) RouteNameFunc
StaticContent(string, string, []byte) func(string)
Favicon(string, ...string) RouteNameFunc
// templates
Layout(string) MuxAPI // returns itself
}
muxAPI struct {
mux *serveMux mux *serveMux
relativePath string relativePath string
middleware Middleware 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..'
@ -717,8 +916,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 {

View File

@ -16,12 +16,12 @@ const (
) )
func testSubdomainHost() string { func testSubdomainHost() string {
return testSubdomain + strconv.Itoa(HTTPServer.Port()) return testSubdomain + strconv.Itoa(Servers.Main().Port())
} }
func testSubdomainURL() (subdomainURL string) { func testSubdomainURL() (subdomainURL string) {
subdomainHost := testSubdomainHost() subdomainHost := testSubdomainHost()
if HTTPServer.IsSecure() { if Servers.Main().IsSecure() {
subdomainURL = "https://" + subdomainHost subdomainURL = "https://" + subdomainHost
} else { } else {
subdomainURL = "http://" + subdomainHost subdomainURL = "http://" + subdomainHost
@ -159,7 +159,7 @@ func TestMuxSimpleParty(t *testing.T) {
request := func(reqPath string) { request := func(reqPath string) {
e.Request("GET", reqPath). e.Request("GET", reqPath).
Expect(). Expect().
Status(StatusOK).Body().Equal(HTTPServer.Host() + reqPath) Status(StatusOK).Body().Equal(Servers.Main().Host() + reqPath)
} }
// run the tests // run the tests

View File

@ -2,12 +2,10 @@ package iris
import ( import (
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris/config" "github.com/kataras/iris/config"
) )
@ -63,10 +61,10 @@ const (
) )
// Contains the server test for multi running servers // Contains the server test for multi running servers
// Note: this test runs two standalone (real) servers func TestMultiRunningServers_v1(t *testing.T) {
func TestMultiRunningServers(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) 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 // create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert") certFile, ferr := ioutil.TempFile("", "cert")
@ -92,48 +90,77 @@ func TestMultiRunningServers(t *testing.T) {
os.Remove(keyFile.Name()) os.Remove(keyFile.Name())
}() }()
initDefault()
Config.DisableBanner = true
Get("/", func(ctx *Context) { Get("/", func(ctx *Context) {
ctx.Write("Hello from %s", ctx.HostString()) ctx.Write("Hello from %s", ctx.HostString())
}) })
// start the secondary server // start the secondary server
secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host, Virtual: true}) SecondaryListen(config.Server{ListeningAddr: "mydomain.com:80", RedirectTo: "https://" + host, Virtual: true})
// start the main server // start the main server
go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true}) go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
defer func() {
go secondary.Close()
go CloseWithErr()
close(Available)
}()
// prepare test framework // prepare test framework
if ok := <-Available; !ok { if ok := <-Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!") t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
} }
handler := HTTPServer.Handler e := Tester(t)
testConfiguration := httpexpect.Config{
Client: &http.Client{
Transport: httpexpect.NewFastBinder(handler),
Jar: httpexpect.NewJar(),
},
Reporter: httpexpect.NewAssertReporter(t),
}
if Config.Tester.Debug {
testConfiguration.Printers = []httpexpect.Printer{
httpexpect.NewDebugPrinter(t, true),
}
}
//
e := httpexpect.WithConfig(testConfiguration)
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) e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
e.Request("GET", "http://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host)
} }

View File

@ -15,7 +15,6 @@ func TestSessions(t *testing.T) {
} }
initDefault() initDefault()
HTTPServer.Config.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions
Config.Sessions.Cookie = "mycustomsessionid" Config.Sessions.Cookie = "mycustomsessionid"
writeValues := func(ctx *Context) { writeValues := func(ctx *Context) {