From 93dc7c7e48d14ca8cafd40b5902b9baf5b0f7518 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Tue, 5 Jul 2016 13:37:10 +0200 Subject: [PATCH 01/10] Add some tests --- context_binder_test.go | 89 ++++++++++ initiatory.go | 4 + iris.go | 1 - iris_test.go | 7 + mux_test.go | 365 +++++++++++++++++++++++++++++++++++++++++ plugin.go | 10 +- plugin_test.go | 195 ++++++++++++++++++++++ render_test.go | 85 ++++++++++ server_test.go | 158 ++++++++++++++++++ sessions_test.go | 121 ++++++++++++++ 10 files changed, 1033 insertions(+), 2 deletions(-) create mode 100644 context_binder_test.go create mode 100644 iris_test.go create mode 100644 mux_test.go create mode 100644 plugin_test.go create mode 100644 render_test.go create mode 100644 server_test.go create mode 100644 sessions_test.go diff --git a/context_binder_test.go b/context_binder_test.go new file mode 100644 index 00000000..74d1a160 --- /dev/null +++ b/context_binder_test.go @@ -0,0 +1,89 @@ +package iris + +import ( + "encoding/xml" + "net/url" + "strconv" + "testing" +) + +// Contains tests for context.ReadJSON/ReadXML/ReadFORM + +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 TestBindForm(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 TestBindJSON(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 TestBindXML(t *testing.T) { + initDefault() + + Post("/xml", func(ctx *Context) { + obj := testBinderXMLData{} + err := ctx.ReadXML(&obj) + if err != nil { + t.Fatalf("Error when parsing the XML body: %s", err.Error()) + } + ctx.XML(StatusOK, obj) + }) + + e := Tester(t) + expectedObj := testBinderXMLData{ + XMLName: xml.Name{Local: "info", Space: "info"}, + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + // so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting + expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `">` + expectedObj.Name + `` + expectedObj.Birth + `` + strconv.Itoa(expectedObj.Stars) + `` + e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody) +} diff --git a/initiatory.go b/initiatory.go index 6d238e18..ec3f3186 100644 --- a/initiatory.go +++ b/initiatory.go @@ -40,6 +40,10 @@ var ( ) func init() { + initDefault() +} + +func initDefault() { Default = New() Config = Default.Config Logger = Default.Logger diff --git a/iris.go b/iris.go index 7eae811d..fc1352e2 100644 --- a/iris.go +++ b/iris.go @@ -254,7 +254,6 @@ func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile strin s.Config.Server.ListeningAddr = addr s.Config.Server.CertFile = certFile s.Config.Server.KeyFile = keyFile - return s.openServer() } diff --git a/iris_test.go b/iris_test.go new file mode 100644 index 00000000..e14220d7 --- /dev/null +++ b/iris_test.go @@ -0,0 +1,7 @@ +package iris + +// Notes: +// +// We use Default := New() and not api := New() or just Default because we want to cover as much code as possible +// The tests are usually end-to-end, except some features like plugins, which we have normal unit testing and end-to-end tests +// diff --git a/mux_test.go b/mux_test.go new file mode 100644 index 00000000..2e3b28eb --- /dev/null +++ b/mux_test.go @@ -0,0 +1,365 @@ +package iris + +// Contains tests for the mux(Router) + +import ( + "fmt" + "strconv" + "testing" + + "github.com/gavv/httpexpect" +) + +const ( + testEnableSubdomain = false + testSubdomain = "mysubdomain.com" +) + +func testSubdomainHost() string { + return testSubdomain + strconv.Itoa(HTTPServer.Port()) +} + +func testSubdomainURL() (subdomainURL string) { + subdomainHost := testSubdomainHost() + if HTTPServer.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(HTTPServer.Host() + reqPath) + } + + // run the tests + request("/party1/") + request("/party1/path1") + request("/party1/path2") + request("/party1/namedpath/theparam1/something/theparam2") + request("/party1/namedpath/theparam1/something/theparam2/else") + + if testEnableSubdomain { + es := subdomainTester(e) + subdomainRequest := func(reqPath string) { + es.Request("GET", reqPath). + Expect(). + Status(StatusOK).Body().Equal(testSubdomainHost() + reqPath) + } + + subdomainRequest("/") + subdomainRequest("/path1") + subdomainRequest("/path2") + subdomainRequest("/namedpath/theparam1/something/theparam2") + subdomainRequest("/namedpath/theparam1/something/theparam2/else") + } +} + +func TestMuxPathEscape(t *testing.T) { + initDefault() + + Get("/details/:name", func(ctx *Context) { + name := ctx.Param("name") + highlight := ctx.URLParam("highlight") + ctx.Text(StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight)) + }) + + e := Tester(t) + + e.GET("/details/Sakamoto desu ga"). + WithQuery("highlight", "text"). + Expect().Status(StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text") +} + +func TestMuxCustomErrors(t *testing.T) { + var ( + notFoundMessage = "Iris custom message for 404 not found" + internalServerMessage = "Iris custom message for 500 internal server error" + testRoutesCustomErrors = []testRoute{ + // NOT FOUND CUSTOM ERRORS - not registed + {"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + // SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registed + {"GET", "/test_get_panic_custom", "/test_get_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"POST", "/test_post_panic_custom", "/test_post_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"PUT", "/test_put_panic_custom", "/test_put_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + } + ) + initDefault() + // first register the testRoutes needed + for _, r := range testRoutesCustomErrors { + if r.Register { + HandleFunc(r.Method, r.Path, func(ctx *Context) { + ctx.EmitError(r.Status) + }) + } + } + + // register the custom errors + OnError(404, func(ctx *Context) { + ctx.Write("%s", notFoundMessage) + }) + + OnError(500, func(ctx *Context) { + ctx.Write("%s", internalServerMessage) + }) + + // create httpexpect instance that will call fasthtpp.RequestHandler directly + e := Tester(t) + + // run the tests + for _, r := range testRoutesCustomErrors { + e.Request(r.Method, r.RequestPath). + Expect(). + Status(r.Status).Body().Equal(r.Body) + } +} + +type testUserAPI struct { + *Context +} + +// GET /users +func (u testUserAPI) Get() { + u.Write("Get Users\n") +} + +// GET /users/:param1 which its value passed to the id argument +func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1") + u.Write("Get By %s\n", id) +} + +// PUT /users +func (u testUserAPI) Put() { + u.Write("Put, name: %s\n", u.FormValue("name")) +} + +// POST /users/:param1 +func (u testUserAPI) PostBy(id string) { + u.Write("Post By %s, name: %s\n", id, u.FormValue("name")) +} + +// DELETE /users/:param1 +func (u testUserAPI) DeleteBy(id string) { + u.Write("Delete By %s\n", id) +} + +func TestMuxAPI(t *testing.T) { + initDefault() + + middlewareResponseText := "I assume that you are authenticated\n" + API("/users", testUserAPI{}, func(ctx *Context) { // optional middleware for .API + // do your work here, or render a login window if not logged in, get the user and send it to the next middleware, or do all here + ctx.Set("user", "username") + ctx.Next() + }, func(ctx *Context) { + if ctx.Get("user") == "username" { + ctx.Write(middlewareResponseText) + ctx.Next() + } else { + ctx.SetStatusCode(StatusUnauthorized) + } + }) + + e := Tester(t) + + userID := "4077" + formname := "kataras" + + e.GET("/users").Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get Users\n") + e.GET("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n") + e.PUT("/users").WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n") + e.POST("/users/"+userID).WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n") + e.DELETE("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n") +} + +type myTestHandlerData struct { + Sysname string // this will be the same for all requests + Version int // this will be the same for all requests + DynamicPathParameter string // this will be different for each request +} + +type myTestCustomHandler struct { + data myTestHandlerData +} + +func (m *myTestCustomHandler) Serve(ctx *Context) { + data := &m.data + data.DynamicPathParameter = ctx.Param("myparam") + ctx.JSON(StatusOK, data) +} + +func TestMuxCustomHandler(t *testing.T) { + initDefault() + myData := myTestHandlerData{ + Sysname: "Redhat", + Version: 1, + } + Handle("GET", "/custom_handler_1/:myparam", &myTestCustomHandler{myData}) + Handle("GET", "/custom_handler_2/:myparam", &myTestCustomHandler{myData}) + + e := Tester(t) + // two times per testRoute + param1 := "thisimyparam1" + expectedData1 := myData + expectedData1.DynamicPathParameter = param1 + e.GET("/custom_handler_1/" + param1).Expect().Status(StatusOK).JSON().Equal(expectedData1) + + param2 := "thisimyparam2" + expectedData2 := myData + expectedData2.DynamicPathParameter = param2 + e.GET("/custom_handler_1/" + param2).Expect().Status(StatusOK).JSON().Equal(expectedData2) + + param3 := "thisimyparam3" + expectedData3 := myData + expectedData3.DynamicPathParameter = param3 + e.GET("/custom_handler_2/" + param3).Expect().Status(StatusOK).JSON().Equal(expectedData3) + + param4 := "thisimyparam4" + expectedData4 := myData + expectedData4.DynamicPathParameter = param4 + e.GET("/custom_handler_2/" + param4).Expect().Status(StatusOK).JSON().Equal(expectedData4) +} diff --git a/plugin.go b/plugin.go index d0a49e72..4f24abec 100644 --- a/plugin.go +++ b/plugin.go @@ -214,11 +214,13 @@ type pluginContainer struct { customEvents map[string][]func() downloader *pluginDownloadManager logger *logger.Logger + mu sync.Mutex } // Add activates the plugins and if succeed then adds it to the activated plugins list func (p *pluginContainer) Add(plugins ...Plugin) error { for _, plugin := range plugins { + if p.activatedPlugins == nil { p.activatedPlugins = make([]Plugin, 0) } @@ -232,10 +234,16 @@ func (p *pluginContainer) Add(plugins ...Plugin) error { } // Activate the plugin, if no error then add it to the plugins if pluginObj, ok := plugin.(pluginActivate); ok { - err := pluginObj.Activate(p) + tempPluginContainer := *p + err := pluginObj.Activate(&tempPluginContainer) if err != nil { return errPluginActivate.Format(pName, err.Error()) } + tempActivatedPluginsLen := len(tempPluginContainer.activatedPlugins) + if tempActivatedPluginsLen != len(p.activatedPlugins)+tempActivatedPluginsLen+1 { // see test: plugin_test.go TestPluginActivate && TestPluginActivationError + p.activatedPlugins = tempPluginContainer.activatedPlugins + } + } // All ok, add it to the plugins list diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 00000000..9b1c2f63 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,195 @@ +package iris + +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) + + NoListen() + CloseWithErr() + + // Output: + // GetName Struct + // Activate Struct + // GetDescription Struct + // Description for My test plugin + // PreListen Func + // PreListen Struct + // PostListen Func + // PostListen Struct + // PreClose Func + // PreClose Struct +} + +// if a plugin has GetName, then it should be registered only one time, the name exists for that reason, it's like unique ID +func TestPluginDublicateName(t *testing.T) { + var plugins pluginContainer + firstNamedPlugin := &testPluginEx{} + sameNamedPlugin := &testPluginEx{} + // err := plugins.Add(firstNamedPlugin, sameNamedPlugin) or + err := plugins.Add(firstNamedPlugin) + if err != nil { + t.Fatalf("Unexpected error when adding a plugin with name: %s", testPluginExName) + } + err = plugins.Add(sameNamedPlugin) + if err == nil { + t.Fatalf("Expected an error because of dublicate named plugin!") + } + if len(plugins.activatedPlugins) != 1 { + t.Fatalf("Expected: %d activated plugin but we got: %d", 1, len(plugins.activatedPlugins)) + } +} + +type testPluginActivationType struct { + shouldError bool +} + +func (t testPluginActivationType) Activate(p PluginContainer) error { + p.Add(&testPluginEx{}) + if t.shouldError { + return fmt.Errorf("An error happens, this plugin and the added plugins by this plugin should not be registered") + } + return nil +} + +func TestPluginActivate(t *testing.T) { + var plugins pluginContainer + myplugin := testPluginActivationType{shouldError: false} + plugins.Add(myplugin) + + if len(plugins.activatedPlugins) != 2 { // 2 because it registeres a second plugin also + t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins)) + } +} + +// if any error returned from the Activate plugin's method, then this plugin and the plugins it registers should not be registered at all +func TestPluginActivationError(t *testing.T) { + var plugins pluginContainer + myplugin := testPluginActivationType{shouldError: true} + plugins.Add(myplugin) + + if len(plugins.activatedPlugins) > 0 { + t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins)) + } +} + +func TestPluginEvents(t *testing.T) { + var plugins pluginContainer + var prelistenran, postlistenran, precloseran bool + + plugins.Add(PreListenFunc(func(*Framework) { + prelistenran = true + })) + + plugins.Add(PostListenFunc(func(*Framework) { + postlistenran = true + })) + + plugins.Add(PreCloseFunc(func(*Framework) { + precloseran = true + })) + + myplugin := &testPluginEx{} + plugins.Add(myplugin) + if len(plugins.activatedPlugins) != 4 { + t.Fatalf("Expected: %d plugins to be registed but we got: %d", 4, len(plugins.activatedPlugins)) + } + desc := plugins.GetDescription(myplugin) + if desc != testPluginExDescription { + t.Fatalf("Expected: %s as Description of the plugin but got: %s", testPluginExDescription, desc) + } + + plugins.DoPreListen(nil) + plugins.DoPostListen(nil) + plugins.DoPreClose(nil) + + if !prelistenran { + t.Fatalf("Expected to run PreListen Func but it doesnt!") + } + if !postlistenran { + t.Fatalf("Expected to run PostListen Func but it doesnt!") + } + if !precloseran { + t.Fatalf("Expected to run PostListen Func but it doesnt!") + } + + if !myplugin.named { + t.Fatalf("Plugin should be named with: %s!", testPluginExName) + } + if !myplugin.activated { + t.Fatalf("Plugin should be activated but it's not!") + } + if !myplugin.prelistenran { + t.Fatalf("Expected to run PreListen Struct but it doesnt!") + } + if !myplugin.postlistenran { + t.Fatalf("Expected to run PostListen Struct but it doesnt!") + } + if !myplugin.precloseran { + t.Fatalf("Expected to run PostListen Struct but it doesnt!") + } + +} diff --git a/render_test.go b/render_test.go new file mode 100644 index 00000000..d1c98401 --- /dev/null +++ b/render_test.go @@ -0,0 +1,85 @@ +package iris + +// Contains tests for render/rest & render/template + +import ( + "encoding/xml" + "strconv" + "testing" +) + +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 TestRenderRest(t *testing.T) { + initDefault() + + dataContents := []byte("Some binary data here.") + textContents := "Plain text here" + JSONPContents := map[string]string{"hello": "jsonp"} + JSONPCallback := "callbackName" + JSONXMLContents := renderTestInformationType{ + XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + markdownContents := "# Hello dynamic markdown from Iris" + + Get("/data", func(ctx *Context) { + ctx.Data(StatusOK, dataContents) + }) + + Get("/text", func(ctx *Context) { + ctx.Text(StatusOK, textContents) + }) + + Get("/jsonp", func(ctx *Context) { + ctx.JSONP(StatusOK, JSONPCallback, JSONPContents) + }) + + Get("/json", func(ctx *Context) { + ctx.JSON(StatusOK, JSONXMLContents) + }) + Get("/xml", func(ctx *Context) { + ctx.XML(StatusOK, JSONXMLContents) + }) + + Get("/markdown", func(ctx *Context) { + ctx.Markdown(StatusOK, markdownContents) + }) + + e := Tester(t) + dataT := e.GET("/data").Expect().Status(StatusOK) + dataT.Header("Content-Type").Equal("application/octet-stream") + dataT.Body().Equal(string(dataContents)) + + textT := e.GET("/text").Expect().Status(StatusOK) + textT.Header("Content-Type").Equal("text/plain; charset=UTF-8") + textT.Body().Equal(textContents) + + JSONPT := e.GET("/jsonp").Expect().Status(StatusOK) + JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8") + JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`) + + JSONT := e.GET("/json").Expect().Status(StatusOK) + JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8") + JSONT.JSON().Object().Equal(JSONXMLContents) + + XMLT := e.GET("/xml").Expect().Status(StatusOK) + XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8") + XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `">` + JSONXMLContents.Name + `` + JSONXMLContents.Birth + `` + strconv.Itoa(JSONXMLContents.Stars) + ``) + + markdownT := e.GET("/markdown").Expect().Status(StatusOK) + markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8") + markdownT.Body().Equal("

" + markdownContents[2:] + "

\n") + +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 00000000..15720145 --- /dev/null +++ b/server_test.go @@ -0,0 +1,158 @@ +package iris + +/* +Linux: /etc/hosts +Windows: $Drive:/windows/system32/drivers/etc/hosts + +127.0.0.1 mydomain.com +127.0.0.1 mysubdomain.mydomain.com + +Windows: + go test -v +Linux: + $ su + $ go test -v +*/ + +import ( + "net/http" + "os" + "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----- +` + + testCertFilename = "mycert.cert" + testKeyFilename = "mykey.key" +) + +// Contains the server test for multi running servers +// Note: this test runs two standalone (real) servers +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) + + // create the key and cert files on the fly, and delete them when this test finished + certFile, ferr := os.Create(testCertFilename) + if ferr != nil { + t.Fatal(ferr.Error()) + } + + keyFile, ferr := os.Create(testKeyFilename) + if ferr != nil { + t.Fatal(ferr.Error()) + } + + certFile.WriteString(testTLSCert) + keyFile.WriteString(testTLSKey) + + defer func() { + certFile.Close() + time.Sleep(350 * time.Millisecond) + os.Remove(testCertFilename) + + keyFile.Close() + time.Sleep(350 * time.Millisecond) + os.Remove(testKeyFilename) + }() + + initDefault() + Config.DisableBanner = true + + Get("/", func(ctx *Context) { + ctx.Write("Hello from %s", ctx.HostString()) + }) + + secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host}) // start one secondary server + // start the main server + go func() { + err := ListenTLSWithErr(host, testCertFilename, testKeyFilename) + if err != nil { + t.Fatalf("Error on server_test ListenTLSWithErr: %s", err.Error()) + } + }() + + defer func() { + go secondary.Close() + go CloseWithErr() + close(Available) + }() + // prepare test framework + if ok := <-Available; !ok { + t.Fatal("Unexpected error: server cannot start, please report this as bug!!") + } + + handler := HTTPServer.Handler + + 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", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host) + e.Request("GET", "http://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host) + +} diff --git a/sessions_test.go b/sessions_test.go new file mode 100644 index 00000000..f9488b2e --- /dev/null +++ b/sessions_test.go @@ -0,0 +1,121 @@ +package iris + +// Contains tests for sessions(sessions package) & flash messages(context) + +import ( + "testing" +) + +func TestSessions(t *testing.T) { + + values := map[string]interface{}{ + "Name": "iris", + "Months": "4", + "Secret": "dsads£2132215£%%Ssdsa", + } + + initDefault() + Config.Server.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions + 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() +} + +func FlashMessagesTest(t *testing.T) { + initDefault() + values := map[string]string{"name": "kataras", "package": "iris"} + + Put("/set", func(ctx *Context) { + for k, v := range values { + ctx.SetFlash(k, v) + } + }) + + //we don't get the flash so on the next request the flash messages should be available. + Get("/get_no_getflash", func(ctx *Context) {}) + + Get("/get", func(ctx *Context) { + // one time one handler + kv := make(map[string]string) + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + //second time on the same handler + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + + }, 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. + kv := make(map[string]string) + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + // print them to the client for test the response also + ctx.JSON(StatusOK, kv) + }) + + e := Tester(t) + e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty() + // 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).Cookies().NotEmpty() + e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values) + // second request ,the flash messages here should be not available and cookie has been removed + // (the true is that the cookie is removed from the first GetFlash, but is available though the whole request saved on context's values for faster get, keep that secret!)* + g := e.GET("/get").Expect().Status(StatusOK) + g.JSON().Object().Empty() + g.Cookies().Empty() + +} From afa5b57dc7864443c39f1bdd785bdd8cad99e43c Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Tue, 5 Jul 2016 14:29:32 +0200 Subject: [PATCH 02/10] Add virtual listen same as NoListen- some changes in order the server_test no need any system modification --- config/server.go | 2 ++ http.go | 5 +++++ initiatory.go | 34 +++++++++++----------------------- iris.go | 37 ++++++++++++++++++++++++++++++++----- server_test.go | 20 +++++++++----------- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/config/server.go b/config/server.go index b580fce5..089ced10 100644 --- a/config/server.go +++ b/config/server.go @@ -37,6 +37,8 @@ type Server struct { // // example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2 RedirectTo string + // Virtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications + Virtual bool } // DefaultServer returns the default configs for the server diff --git a/http.go b/http.go index 6d72aaa5..c3ff9e57 100644 --- a/http.go +++ b/http.go @@ -431,10 +431,15 @@ func (s *Server) Open() error { } } + if s.Config.Virtual { + return nil + } + if s.Config.Mode > 0 { return s.listenUNIX() } return s.listen() + } // Close terminates the server diff --git a/initiatory.go b/initiatory.go index ec3f3186..2c6fa358 100644 --- a/initiatory.go +++ b/initiatory.go @@ -179,6 +179,7 @@ func (s *Framework) openServer() (err error) { // 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, @@ -186,10 +187,16 @@ func (s *Framework) openServer() (err error) { s.HTTPServer.Host())) } s.Plugins.DoPostListen(s) - s.Available <- true - ch := make(chan os.Signal) - <-ch - s.Close() + go func() { + s.Available <- true + }() + + if !s.Config.Server.Virtual { + ch := make(chan os.Signal) + <-ch + s.Close() + } + } return } @@ -201,25 +208,6 @@ func (s *Framework) closeServer() error { 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 { diff --git a/iris.go b/iris.go index fc1352e2..f4d783c1 100644 --- a/iris.go +++ b/iris.go @@ -94,6 +94,7 @@ type ( ListenUNIX(string, os.FileMode) SecondaryListen(config.Server) *Server NoListen(...string) *Server + ListenVirtual(cfg ...config.Server) *Server Close() // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also MustUse(...Handler) @@ -328,9 +329,11 @@ func (s *Framework) SecondaryListen(cfg config.Server) *Server { 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() + if !cfg.Virtual { + ch := make(chan os.Signal) + <-ch + srv.Close() + } } }() })) @@ -346,7 +349,31 @@ func NoListen(optionalAddr ...string) *Server { // 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 func (s *Framework) NoListen(optionalAddr ...string) *Server { - return s.justServe(optionalAddr...) + if len(optionalAddr) > 0 { + s.Config.Server.ListeningAddr = optionalAddr[0] + } + return s.ListenVirtual() +} + +// ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it +// initializes the whole framework but server doesn't listens to a specific net.Listener +// same as NoListen +func ListenVirtual(cfg ...config.Server) *Server { + return Default.ListenVirtual(cfg...) +} + +// ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it +// initializes the whole framework but server doesn't listens to a specific net.Listener +// same as NoListen +func (s *Framework) ListenVirtual(cfg ...config.Server) *Server { + if len(cfg) > 0 { + s.Config.Server = cfg[0] + } + s.Config.DisableBanner = true + s.Config.Server.Virtual = true + + s.Must(s.openServer()) + return s.HTTPServer } // CloseWithErr terminates the server and returns an error if any @@ -609,7 +636,7 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{}, func NewTester(api *Framework, t *testing.T) *httpexpect.Expect { api.Config.DisableBanner = true if !api.HTTPServer.IsListening() { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app - api.NoListen() + api.ListenVirtual() if ok := <-api.Available; !ok { t.Fatal("Unexpected error: server cannot start, please report this as bug!!") } diff --git a/server_test.go b/server_test.go index 15720145..e4276769 100644 --- a/server_test.go +++ b/server_test.go @@ -15,6 +15,7 @@ Linux: */ import ( + "io/ioutil" "net/http" "os" "testing" @@ -22,6 +23,7 @@ import ( "github.com/gavv/httpexpect" "github.com/kataras/iris/config" + "github.com/kataras/iris/utils" ) const ( @@ -84,12 +86,13 @@ 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) // create the key and cert files on the fly, and delete them when this test finished - certFile, ferr := os.Create(testCertFilename) + certFile, ferr := ioutil.TempFile(utils.AssetsDirectory, "_iris") + if ferr != nil { t.Fatal(ferr.Error()) } - keyFile, ferr := os.Create(testKeyFilename) + keyFile, ferr := ioutil.TempFile(utils.AssetsDirectory, "_iris") if ferr != nil { t.Fatal(ferr.Error()) } @@ -100,11 +103,11 @@ func TestMultiRunningServers(t *testing.T) { defer func() { certFile.Close() time.Sleep(350 * time.Millisecond) - os.Remove(testCertFilename) + os.Remove(certFile.Name()) keyFile.Close() time.Sleep(350 * time.Millisecond) - os.Remove(testKeyFilename) + os.Remove(keyFile.Name()) }() initDefault() @@ -114,14 +117,9 @@ func TestMultiRunningServers(t *testing.T) { ctx.Write("Hello from %s", ctx.HostString()) }) - secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host}) // start one secondary server + secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host, Virtual: true}) // start one secondary server // start the main server - go func() { - err := ListenTLSWithErr(host, testCertFilename, testKeyFilename) - if err != nil { - t.Fatalf("Error on server_test ListenTLSWithErr: %s", err.Error()) - } - }() + go ListenVirtual(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name()}) defer func() { go secondary.Close() From d76b73427bfc5c7f04b6ec8f4ca446450a28ed01 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Tue, 5 Jul 2016 15:26:47 +0200 Subject: [PATCH 03/10] New: ListenVirtual and ListenTo specific configs - server_test fixed --- config/iris.go | 8 ----- initiatory.go | 29 +++++------------ iris.go | 85 +++++++++++++++++++++++++++++++----------------- plugin_test.go | 3 +- server_test.go | 27 +++------------ sessions_test.go | 2 +- 6 files changed, 71 insertions(+), 83 deletions(-) diff --git a/config/iris.go b/config/iris.go index c7d7310b..930b0883 100644 --- a/config/iris.go +++ b/config/iris.go @@ -103,13 +103,6 @@ type ( // Websocket contains the configs for Websocket's server integration Websocket *Websocket - // Server contains the configs for the http server - // Server configs are the only one which are setted inside base Iris package (from Listen, ListenTLS, ListenUNIX) NO from users - // - // this field is useful only when you need to READ which is the server's address, certfile & keyfile or unix's mode. - // - Server Server - // Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself Tester Tester } @@ -148,7 +141,6 @@ func Default() Iris { Sessions: DefaultSessions(), Render: DefaultRender(), Websocket: DefaultWebsocket(), - Server: DefaultServer(), Tester: Tester{Debug: false}, } } diff --git a/initiatory.go b/initiatory.go index 2c6fa358..111861c2 100644 --- a/initiatory.go +++ b/initiatory.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "sync" - "testing" "time" "github.com/gavv/httpexpect" @@ -29,7 +28,7 @@ var ( 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. + // 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? @@ -119,8 +118,9 @@ func New(cfg ...config.Iris) *Framework { 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) + // set the server with the default configuration, which is changed on Listen functions + defaultServerCfg := config.DefaultServer() + s.HTTPServer = newServer(&defaultServerCfg) } return s @@ -187,15 +187,12 @@ func (s *Framework) openServer() (err error) { s.HTTPServer.Host())) } s.Plugins.DoPostListen(s) - go func() { - s.Available <- true - }() - if !s.Config.Server.Virtual { - ch := make(chan os.Signal) - <-ch - s.Close() - } + go func() { s.Available <- true }() + + ch := make(chan os.Signal) + <-ch + s.Close() } return @@ -207,11 +204,3 @@ func (s *Framework) closeServer() error { s.Available = make(chan bool) return s.HTTPServer.Close() } - -// tester returns the test framework -func (s *Framework) tester(t *testing.T) *httpexpect.Expect { - if s.testFramework == nil { - s.testFramework = NewTester(s, t) - } - return s.testFramework -} diff --git a/iris.go b/iris.go index f4d783c1..42f419ab 100644 --- a/iris.go +++ b/iris.go @@ -86,6 +86,7 @@ type ( FrameworkAPI interface { MuxAPI Must(error) + ListenTo(config.Server) error ListenWithErr(string) error Listen(string) ListenTLSWithErr(string, string, string) error @@ -93,8 +94,8 @@ type ( ListenUNIXWithErr(string, os.FileMode) error ListenUNIX(string, os.FileMode) SecondaryListen(config.Server) *Server + ListenVirtual(...string) *Server NoListen(...string) *Server - ListenVirtual(cfg ...config.Server) *Server Close() // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also MustUse(...Handler) @@ -171,6 +172,19 @@ func (s *Framework) Must(err error) { } } +// ListenTo listens to a server but receives the full server's configuration +// 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) error { + s.HTTPServer.Config = &cfg + return s.openServer() +} + // ListenWithErr starts the standalone http server // which listens to the addr parameter which as the form of // host:port @@ -200,8 +214,12 @@ func Listen(addr string) { // 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() + cfg := config.DefaultServer() + if len(addr) > 0 { + cfg.ListeningAddr = addr + } + + return s.ListenTo(cfg) } // Listen starts the standalone http server @@ -249,13 +267,14 @@ func ListenTLS(addr string, certFile string, keyFile string) { // 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 { + cfg := config.DefaultServer() 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() + cfg.ListeningAddr = addr + cfg.CertFile = certFile + cfg.KeyFile = keyFile + return s.ListenTo(cfg) } // ListenTLS Starts a https server with certificates, @@ -285,9 +304,10 @@ 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 // 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() + cfg := config.DefaultServer() + 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 @@ -342,37 +362,44 @@ func (s *Framework) SecondaryListen(cfg config.Server) *Server { } // 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 +// it is not blocking the app +// same as ListenVirtual 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 +// it is not blocking the app +// same as ListenVirtual func (s *Framework) NoListen(optionalAddr ...string) *Server { - if len(optionalAddr) > 0 { - s.Config.Server.ListeningAddr = optionalAddr[0] - } - return s.ListenVirtual() + return s.ListenVirtual(optionalAddr...) } // ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it // initializes the whole framework but server doesn't listens to a specific net.Listener -// same as NoListen -func ListenVirtual(cfg ...config.Server) *Server { - return Default.ListenVirtual(cfg...) +// it is not blocking the app +func ListenVirtual(optionalAddr ...string) *Server { + return Default.ListenVirtual(optionalAddr...) } // ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it // initializes the whole framework but server doesn't listens to a specific net.Listener -// same as NoListen -func (s *Framework) ListenVirtual(cfg ...config.Server) *Server { - if len(cfg) > 0 { - s.Config.Server = cfg[0] - } +// it is not blocking the app +func (s *Framework) ListenVirtual(optionalAddr ...string) *Server { s.Config.DisableBanner = true - s.Config.Server.Virtual = true + cfg := config.DefaultServer() - s.Must(s.openServer()) + if len(optionalAddr) > 0 { + cfg.ListeningAddr = optionalAddr[0] + } + cfg.Virtual = true + go 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.HTTPServer } @@ -634,13 +661,8 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{}, // NewTester Prepares and returns a new test framework based on the api // is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T) func NewTester(api *Framework, t *testing.T) *httpexpect.Expect { - api.Config.DisableBanner = true if !api.HTTPServer.IsListening() { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app api.ListenVirtual() - if ok := <-api.Available; !ok { - t.Fatal("Unexpected error: server cannot start, please report this as bug!!") - } - close(api.Available) } handler := api.HTTPServer.Handler @@ -670,7 +692,10 @@ func Tester(t *testing.T) *httpexpect.Expect { // Tester returns the test framework for this iris insance func (s *Framework) Tester(t *testing.T) *httpexpect.Expect { - return s.tester(t) + if s.testFramework == nil { + s.testFramework = NewTester(s, t) + } + return s.testFramework } // ------------------------------------------------------------------------------------- diff --git a/plugin_test.go b/plugin_test.go index 9b1c2f63..4f072426 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -50,7 +50,6 @@ func (t *testPluginEx) PreClose(*Framework) { func ExamplePlugins_Add() { initDefault() - Plugins.Add(PreListenFunc(func(*Framework) { fmt.Println("PreListen Func") })) @@ -68,7 +67,7 @@ func ExamplePlugins_Add() { desc := Plugins.GetDescription(myplugin) fmt.Println(desc) - NoListen() + ListenVirtual() CloseWithErr() // Output: diff --git a/server_test.go b/server_test.go index e4276769..1e457862 100644 --- a/server_test.go +++ b/server_test.go @@ -1,19 +1,5 @@ package iris -/* -Linux: /etc/hosts -Windows: $Drive:/windows/system32/drivers/etc/hosts - -127.0.0.1 mydomain.com -127.0.0.1 mysubdomain.mydomain.com - -Windows: - go test -v -Linux: - $ su - $ go test -v -*/ - import ( "io/ioutil" "net/http" @@ -23,7 +9,6 @@ import ( "github.com/gavv/httpexpect" "github.com/kataras/iris/config" - "github.com/kataras/iris/utils" ) const ( @@ -75,9 +60,6 @@ const ( MlobQSunSDKx/CCJhWkbytCyh1bngAtwSAYLXavYIlJbAzx6FvtAIw4= -----END RSA PRIVATE KEY----- ` - - testCertFilename = "mycert.cert" - testKeyFilename = "mykey.key" ) // Contains the server test for multi running servers @@ -86,13 +68,13 @@ 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) // create the key and cert files on the fly, and delete them when this test finished - certFile, ferr := ioutil.TempFile(utils.AssetsDirectory, "_iris") + certFile, ferr := ioutil.TempFile("", "cert") if ferr != nil { t.Fatal(ferr.Error()) } - keyFile, ferr := ioutil.TempFile(utils.AssetsDirectory, "_iris") + keyFile, ferr := ioutil.TempFile("", "key") if ferr != nil { t.Fatal(ferr.Error()) } @@ -117,9 +99,10 @@ func TestMultiRunningServers(t *testing.T) { ctx.Write("Hello from %s", ctx.HostString()) }) - secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host, Virtual: true}) // start one secondary server + // start the secondary server + secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host, Virtual: true}) // start the main server - go ListenVirtual(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name()}) + go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true}) defer func() { go secondary.Close() diff --git a/sessions_test.go b/sessions_test.go index f9488b2e..88cbac36 100644 --- a/sessions_test.go +++ b/sessions_test.go @@ -15,7 +15,7 @@ func TestSessions(t *testing.T) { } initDefault() - Config.Server.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions + HTTPServer.Config.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions Config.Sessions.Cookie = "mycustomsessionid" writeValues := func(ctx *Context) { From 2cc75817b7b32a204ee04789d9b581c2d572013f Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Wed, 6 Jul 2016 20:24:34 +0200 Subject: [PATCH 04/10] Add support for more than one listening server to one station, virtual and no virtual --- config/iris.go | 16 +- config/server.go | 22 ++- config/tester.go | 9 +- context.go | 8 +- context_binder_test.go | 2 + context_test.go | 111 +++++++++++ http.go | 181 +++++++++++++++--- initiatory.go | 206 --------------------- iris.go | 407 ++++++++++++++++++++++++++++++----------- mux_test.go | 6 +- server_test.go | 95 ++++++---- sessions_test.go | 1 - 12 files changed, 674 insertions(+), 390 deletions(-) create mode 100644 context_test.go delete mode 100644 initiatory.go diff --git a/config/iris.go b/config/iris.go index 930b0883..247356a2 100644 --- a/config/iris.go +++ b/config/iris.go @@ -1,15 +1,11 @@ package config -import ( - "github.com/imdario/mergo" - "github.com/valyala/fasthttp" -) +import "github.com/imdario/mergo" // Default values for base Iris conf const ( DefaultDisablePathCorrection = false DefaultDisablePathEscape = false - DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize ) type ( @@ -54,13 +50,6 @@ type ( // Default is false DisableBanner bool - // MaxRequestBodySize Maximum request body size. - // - // The server rejects requests with bodies exceeding this limit. - // - // By default request body size is 4MB. - MaxRequestBodySize int64 - // ProfilePath a the route path, set it to enable http pprof tool // Default is empty, if you set it to a $path, these routes will handled: // $path/cmdline @@ -135,13 +124,12 @@ func Default() Iris { DisablePathCorrection: DefaultDisablePathCorrection, DisablePathEscape: DefaultDisablePathEscape, DisableBanner: false, - MaxRequestBodySize: DefaultMaxRequestBodySize, ProfilePath: "", Logger: DefaultLogger(), Sessions: DefaultSessions(), Render: DefaultRender(), Websocket: DefaultWebsocket(), - Tester: Tester{Debug: false}, + Tester: DefaultTester(), } } diff --git a/config/server.go b/config/server.go index 089ced10..29be03bc 100644 --- a/config/server.go +++ b/config/server.go @@ -5,13 +5,17 @@ import ( "strconv" "github.com/imdario/mergo" + "github.com/kataras/fasthttp" ) +// Default values for base Server conf const ( // DefaultServerHostname returns the default hostname which is 127.0.0.1 DefaultServerHostname = "127.0.0.1" // DefaultServerPort returns the default port which is 8080 DefaultServerPort = 8080 + // DefaultMaxRequestBodySize is 4MB + DefaultMaxRequestBodySize = fasthttp.DefaultMaxRequestBodySize ) var ( @@ -30,6 +34,12 @@ type Server struct { KeyFile string // Mode this is for unix only Mode os.FileMode + // MaxRequestBodySize Maximum request body size. + // + // The server rejects requests with bodies exceeding this limit. + // + // By default request body size is 4MB. + MaxRequestBodySize int64 // RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT) // // NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server @@ -43,7 +53,8 @@ type Server struct { // DefaultServer returns the default configs for the server func DefaultServer() Server { - return Server{ListeningAddr: DefaultServerAddr} + return Server{ListeningAddr: DefaultServerAddr, + MaxRequestBodySize: DefaultMaxRequestBodySize} } // Merge merges the default with the given config and returns the result @@ -59,3 +70,12 @@ func (c Server) Merge(cfg []Server) (config Server) { return } + +// MergeSingle merges the default with the given config and returns the result +func (c Server) MergeSingle(cfg Server) (config Server) { + + config = cfg + mergo.Merge(&config, c) + + return +} diff --git a/config/tester.go b/config/tester.go index 11488068..da56e2f5 100644 --- a/config/tester.go +++ b/config/tester.go @@ -2,5 +2,12 @@ package config // Tester configuration 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"} } diff --git a/context.go b/context.go index 201491c5..b80a9c00 100644 --- a/context.go +++ b/context.go @@ -218,7 +218,7 @@ func (ctx *Context) HostString() string { func (ctx *Context) VirtualHostname() string { realhost := ctx.HostString() hostname := realhost - virtualhost := ctx.framework.HTTPServer.VirtualHostname() + virtualhost := ctx.framework.Servers.Main().VirtualHostname() if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 { hostname = hostname[0:portIdx] @@ -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) 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 diff --git a/context_binder_test.go b/context_binder_test.go index 74d1a160..99f6fad4 100644 --- a/context_binder_test.go +++ b/context_binder_test.go @@ -26,6 +26,7 @@ type testBinderXMLData struct { func TestBindForm(t *testing.T) { initDefault() + Post("/form", func(ctx *Context) { obj := testBinderData{} err := ctx.ReadForm(&obj) @@ -36,6 +37,7 @@ func TestBindForm(t *testing.T) { }) e := Tester(t) + passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"}, "[1]": []string{"mydata2"}}} diff --git a/context_test.go b/context_test.go new file mode 100644 index 00000000..596ad3b0 --- /dev/null +++ b/context_test.go @@ -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) { + +} diff --git a/http.go b/http.go index c3ff9e57..66011974 100644 --- a/http.go +++ b/http.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/iris-contrib/errors" "github.com/kataras/iris/config" @@ -236,35 +237,47 @@ var ( errServerChmod = errors.New("Cannot chmod %#o for %q: %s") ) -// Server the http server -type Server struct { - *fasthttp.Server - listener net.Listener - Config *config.Server - tls bool - mu sync.Mutex -} +type ( + // Server the http server + Server struct { + *fasthttp.Server + listener net.Listener + Config config.Server + tls bool + mu sync.Mutex + } + // ServerList contains the servers connected to the Iris station + ServerList struct { + mux *serveMux + servers []*Server + } +) // newServer returns a pointer to a Server object, and set it's options if any, nothing more -func newServer(c *config.Server) *Server { - s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c} +func newServer(cfg config.Server) *Server { + s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: cfg} return s } -// SetHandler sets the handler in order to listen on client requests -func (s *Server) SetHandler(mux *serveMux) { - if s.Server != nil { - s.Server.Handler = mux.ServeRequest() - } -} - // IsListening returns true if server is listening/started, otherwise false func (s *Server) IsListening() bool { + if s == nil { + return false + } s.mu.Lock() defer s.mu.Unlock() return s.listener != nil && s.listener.Addr().String() != "" } +// IsOpened checks if handler is not nil and returns true if not, otherwise false +// this is used to see if a server has opened, use IsListening if you want to see if the server is actually ready to serve connections +func (s *Server) IsOpened() bool { + if s == nil { + return false + } + return s.Server != nil && s.Server.Handler != nil +} + // IsSecure returns true if server uses TLS, otherwise false func (s *Server) IsSecure() bool { return s.tls @@ -398,7 +411,11 @@ func (s *Server) serve(l net.Listener) error { } // Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen -func (s *Server) Open() error { +func (s *Server) Open(h fasthttp.RequestHandler) error { + if h == nil { + return errServerHandlerMissing.Return() + } + if s.IsListening() { return errServerAlreadyStarted.Return() } @@ -407,10 +424,6 @@ func (s *Server) Open() error { return errServerConfigMissing.Return() } - if s.Handler == nil { - return errServerHandlerMissing.Return() - } - // check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases a := s.Config.ListeningAddr //check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080 @@ -419,9 +432,13 @@ func (s *Server) Open() error { s.Config.ListeningAddr = config.DefaultServerHostname + a } + if s.Config.MaxRequestBodySize > config.DefaultMaxRequestBodySize { + s.Server.MaxRequestBodySize = int(s.Config.MaxRequestBodySize) + } + if s.Config.RedirectTo != "" { // override the handler and redirect all requests to this addr - s.Handler = func(reqCtx *fasthttp.RequestCtx) { + s.Server.Handler = func(reqCtx *fasthttp.RequestCtx) { path := string(reqCtx.Path()) redirectTo := s.Config.RedirectTo if path != "/" { @@ -429,6 +446,8 @@ func (s *Server) Open() error { } reqCtx.Redirect(redirectTo, StatusMovedPermanently) } + } else { + s.Server.Handler = h } if s.Config.Virtual { @@ -452,6 +471,122 @@ func (s *Server) Close() (err error) { return } +// ------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------- +// --------------------------------ServerList implementation----------------------------- +// ------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------- + +// Add adds a server to the list by its config +// returns the new server +func (s *ServerList) Add(cfg config.Server) *Server { + srv := newServer(cfg) + s.servers = append(s.servers, srv) + return srv +} + +// Len returns the size of the server list +func (s *ServerList) Len() int { + return len(s.servers) +} + +// Main returns the main server, +// the last added server is the main server, even if's Virtual +func (s *ServerList) Main() (srv *Server) { + l := len(s.servers) - 1 + for i := range s.servers { + if i == l { + return s.servers[i] + } + } + return nil +} + +// Get returns the server by it's registered Address +func (s *ServerList) Get(addr string) (srv *Server) { + for i := range s.servers { + srv = s.servers[i] + if srv.Config.ListeningAddr == addr { + return + } + } + return +} + +// GetAll returns all registered servers +func (s *ServerList) GetAll() []*Server { + return s.servers +} + +// GetByIndex returns a server from the list by it's index +func (s *ServerList) GetByIndex(i int) *Server { + if len(s.servers) >= i+1 { + return s.servers[i] + } + return nil +} + +// Remove deletes a server by it's registered Address +// returns true if something was removed, otherwise returns false +func (s *ServerList) Remove(addr string) bool { + servers := s.servers + for i := range servers { + srv := servers[i] + if srv.Config.ListeningAddr == addr { + copy(servers[i:], servers[i+1:]) + servers[len(servers)-1] = nil + s.servers = servers[:len(servers)-1] + return true + } + } + return false +} + +// CloseAll terminates all listening servers +// returns the first error, if erro happens it continues to closes the rest of the servers +func (s *ServerList) CloseAll() (err error) { + for i := range s.servers { + if err == nil { + err = s.servers[i].Close() + } + } + return +} + +// OpenAll starts all servers +// returns the first error happens to one of these servers +// if one server gets error it closes the previous servers and exits from this process +func (s *ServerList) OpenAll() error { + l := len(s.servers) - 1 + h := s.mux.ServeRequest() + for i := range s.servers { + + if err := s.servers[i].Open(h); err != nil { + time.Sleep(2 * time.Second) + // for any case, + // we don't care about performance on initialization, + // we must make sure that the previous servers are running before closing them + s.CloseAll() + break + } + if i == l { + s.mux.setHostname(s.servers[i].VirtualHostname()) + } + + } + return nil +} + +// GetAllOpened returns all opened/started servers +func (s *ServerList) GetAllOpened() (servers []*Server) { + for i := range s.servers { + if s.servers[i].IsOpened() { + servers = append(servers, s.servers[i]) + } + } + return +} + // errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context) // It seems to be a +type Points to: +pointer.' var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.") diff --git a/initiatory.go b/initiatory.go deleted file mode 100644 index 111861c2..00000000 --- a/initiatory.go +++ /dev/null @@ -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() -} diff --git a/iris.go b/iris.go index 42f419ab..4d77d24f 100644 --- a/iris.go +++ b/iris.go @@ -61,26 +61,96 @@ import ( "testing" "time" + "sync" + "github.com/gavv/httpexpect" "github.com/iris-contrib/errors" "github.com/kataras/iris/config" "github.com/kataras/iris/context" + "github.com/kataras/iris/logger" + "github.com/kataras/iris/render/rest" + "github.com/kataras/iris/render/template" + "github.com/kataras/iris/sessions" "github.com/kataras/iris/utils" + "github.com/kataras/iris/websocket" "github.com/valyala/fasthttp" + ///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled. + _ "github.com/kataras/iris/sessions/providers/memory" + _ "github.com/kataras/iris/sessions/providers/redis" ) const ( // Version of the iris Version = "3.0.0-rc.4" - banner = ` _____ _ + + // HTMLEngine conversion for config.HTMLEngine + HTMLEngine = config.HTMLEngine + // PongoEngine conversion for config.PongoEngine + PongoEngine = config.PongoEngine + // MarkdownEngine conversion for config.MarkdownEngine + MarkdownEngine = config.MarkdownEngine + // JadeEngine conversion for config.JadeEngine + JadeEngine = config.JadeEngine + // AmberEngine conversion for config.AmberEngine + AmberEngine = config.AmberEngine + // HandlebarsEngine conversion for config.HandlebarsEngine + HandlebarsEngine = config.HandlebarsEngine + // DefaultEngine conversion for config.DefaultEngine + DefaultEngine = config.DefaultEngine + // NoEngine conversion for config.NoEngine + NoEngine = config.NoEngine + // NoLayout to disable layout for a particular template file + // conversion for config.NoLayout + NoLayout = config.NoLayout + + banner = ` _____ _ |_ _| (_) | | ____ _ ___ | | | __|| |/ __| _| |_| | | |\__ \ - |_____|_| |_||___/ ` + Version + ` - ` + |_____|_| |_||___/ ` + Version + ` ` ) +// Default entry, use it with iris.$anyPublicFunc +var ( + Default *Framework + Config *config.Iris + Logger *logger.Logger + Plugins PluginContainer + Websocket websocket.Server + Servers *ServerList + // Available is a channel type of bool, fired to true when the server is opened and all plugins ran + // never fires false, if the .Close called then the channel is re-allocating. + // the channel is closed only when .ListenVirtual is used, otherwise it remains open until you close it. + // + // Note: it is a simple channel and decided to put it here and no inside HTTPServer, doesn't have statuses just true and false, simple as possible + // Where to use that? + // this is used on extreme cases when you don't know which .Listen/.NoListen will be called + // and you want to run/declare something external-not-Iris (all Iris functionality declared before .Listen/.NoListen) AFTER the server is started and plugins finished. + // see the server_test.go for an example + Available chan bool +) + +func initDefault() { + Default = New() + Config = Default.Config + Logger = Default.Logger + Plugins = Default.Plugins + Websocket = Default.Websocket + Servers = Default.Servers + Available = Default.Available +} + +func init() { + initDefault() +} + +// ------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------- +// --------------------------------Framework implementation----------------------------- +// ------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------- + type ( // FrameworkAPI contains the main Iris Public API FrameworkAPI interface { @@ -110,56 +180,140 @@ type ( Tester(t *testing.T) *httpexpect.Expect } - // RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route) - RouteNameFunc func(string) - // MuxAPI the visible api for the serveMux - MuxAPI interface { - Party(string, ...HandlerFunc) MuxAPI - // middleware serial, appending - Use(...Handler) - UseFunc(...HandlerFunc) - - // main handlers - Handle(string, string, ...Handler) RouteNameFunc - HandleFunc(string, string, ...HandlerFunc) RouteNameFunc - // H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles - H_(string, string, func(context.IContext)) func(string) - API(string, HandlerAPI, ...HandlerFunc) - - // http methods - Get(string, ...HandlerFunc) RouteNameFunc - Post(string, ...HandlerFunc) RouteNameFunc - Put(string, ...HandlerFunc) RouteNameFunc - Delete(string, ...HandlerFunc) RouteNameFunc - Connect(string, ...HandlerFunc) RouteNameFunc - Head(string, ...HandlerFunc) RouteNameFunc - Options(string, ...HandlerFunc) RouteNameFunc - Patch(string, ...HandlerFunc) RouteNameFunc - Trace(string, ...HandlerFunc) RouteNameFunc - Any(string, ...HandlerFunc) - - // static content - StaticHandler(string, int, bool, bool, []string) HandlerFunc - Static(string, string, int) RouteNameFunc - StaticFS(string, string, int) RouteNameFunc - StaticWeb(string, string, int) RouteNameFunc - StaticServe(string, ...string) RouteNameFunc - StaticContent(string, string, []byte) func(string) - Favicon(string, ...string) RouteNameFunc - - // templates - Layout(string) MuxAPI // returns itself + // Framework is our God |\| Google.Search('Greek mythology Iris') + // + // Implements the FrameworkAPI + Framework struct { + *muxAPI + rest *rest.Render + templates *template.Template + sessions *sessions.Manager + // fields which are useful to the user/dev + // the last added server is the main server + Servers *ServerList + Config *config.Iris + Logger *logger.Logger + Plugins PluginContainer + Websocket websocket.Server + Available chan bool + // this is setted once when .Tester(t) is called + testFramework *httpexpect.Expect } ) -// ------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------- -// --------------------------------Framework implementation----------------------------- -// ------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------- - var _ FrameworkAPI = &Framework{} +// New creates and returns a new Iris station aka Framework. +// +// Receives an optional config.Iris as parameter +// If empty then config.Default() is used instead +func New(cfg ...config.Iris) *Framework { + c := config.Default().Merge(cfg) + + // we always use 's' no 'f' because 's' is easier for me to remember because of 'station' + // some things never change :) + s := &Framework{Config: &c, Available: make(chan bool)} + { + ///NOTE: set all with s.Config pointer + // set the Logger + s.Logger = logger.New(s.Config.Logger) + // set the plugin container + s.Plugins = &pluginContainer{logger: s.Logger} + // set the websocket server + s.Websocket = websocket.NewServer(s.Config.Websocket) + // set the servemux, which will provide us the public API also, with its context pool + mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger) + // set the public router API (and party) + s.muxAPI = &muxAPI{mux: mux, relativePath: "/"} + + s.Servers = &ServerList{mux: mux, servers: make([]*Server, 0)} + } + + return s +} + +func (s *Framework) initialize() { + // set sessions + if s.Config.Sessions.Provider != "" { + s.sessions = sessions.New(s.Config.Sessions) + } + + // set the rest + s.rest = rest.New(s.Config.Render.Rest) + + // set templates if not already setted + s.prepareTemplates() + + // listen to websocket connections + websocket.RegisterServer(s, s.Websocket, s.Logger) + + // prepare the mux & the server + s.mux.setCorrectPath(!s.Config.DisablePathCorrection) + s.mux.setEscapePath(!s.Config.DisablePathEscape) + + // set the debug profiling handlers if ProfilePath is setted + if debugPath := s.Config.ProfilePath; debugPath != "" { + s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...) + } +} + +// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen +func (s *Framework) prepareTemplates() { + // prepare the templates + if s.templates == nil { + // These functions are directly contact with Iris' functionality. + funcs := map[string]interface{}{ + "url": s.URL, + "urlpath": s.Path, + } + + template.RegisterSharedFuncs(funcs) + + s.templates = template.New(s.Config.Render.Template) + } +} + +// Go starts the iris station, listens to all registered servers, and prepare only if Virtual +func Go() error { + return Default.Go() +} + +// Go starts the iris station, listens to all registered servers, and prepare only if Virtual +func (s *Framework) Go() error { + s.initialize() + s.Plugins.DoPreListen(s) + + if firstErr := s.Servers.OpenAll(); firstErr != nil { + 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 func Must(err error) { 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 // it's a blocking func -func (s *Framework) ListenTo(cfg config.Server) error { - s.HTTPServer.Config = &cfg - return s.openServer() +func (s *Framework) ListenTo(cfg config.Server) (err error) { + s.Servers.Add(cfg) + return s.Go() } // 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 // ex: log.Fatal(iris.ListenWithErr(":8080")) func (s *Framework) ListenWithErr(addr string) error { - cfg := config.DefaultServer() - if len(addr) > 0 { - cfg.ListeningAddr = addr - } - - return s.ListenTo(cfg) + return s.ListenTo(config.Server{ListeningAddr: addr}) } // 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 // ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key")) func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error { - cfg := config.DefaultServer() if certFile == "" || keyFile == "" { return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL") } - cfg.ListeningAddr = addr - cfg.CertFile = certFile - cfg.KeyFile = keyFile - return s.ListenTo(cfg) + return s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}) } // 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 // returns an error if something bad happens when trying to listen to func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error { - cfg := config.DefaultServer() - cfg.ListeningAddr = addr - cfg.Mode = mode - return s.ListenTo(cfg) + return s.ListenTo(config.Server{ListeningAddr: addr, Mode: mode}) } // 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)) } +// SecondaryListen NOTE: This will be deprecated +// Use .Servers.Add(config.Server) instead +// // 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) // @@ -331,6 +476,9 @@ func SecondaryListen(cfg config.Server) *Server { 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 // 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. 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 { - if !cfg.Virtual { - ch := make(chan os.Signal) - <-ch - srv.Close() - } - } - }() - })) - - return srv + 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 @@ -391,34 +524,41 @@ func (s *Framework) ListenVirtual(optionalAddr ...string) *Server { s.Config.DisableBanner = true cfg := config.DefaultServer() - if len(optionalAddr) > 0 { + if len(optionalAddr) > 0 && optionalAddr[0] != "" { cfg.ListeningAddr = optionalAddr[0] } cfg.Virtual = true - go s.ListenTo(cfg) + + 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.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 { 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() { 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 { - 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() { s.Must(s.CloseWithErr()) } @@ -594,13 +734,13 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) { if r == nil { return } - + srv := s.Servers.Main() scheme := "http://" - if s.HTTPServer.IsSecure() { + if srv.IsSecure() { scheme = "https://" } - host := s.HTTPServer.VirtualHost() + host := srv.VirtualHost() arguments := args[0:] // join arrays as arguments @@ -661,16 +801,31 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{}, // NewTester Prepares and returns a new test framework based on the api // is useful when you need to have more than one test framework for the same iris insttance, otherwise you can use the iris.Tester(t *testing.T)/variable.Tester(t *testing.T) func NewTester(api *Framework, t *testing.T) *httpexpect.Expect { - if !api.HTTPServer.IsListening() { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app - api.ListenVirtual() + srv := api.Servers.Main() + if srv == nil { // maybe the user called this after .Listen/ListenTLS/ListenUNIX, the tester can be used as standalone (with no running iris instance) or inside a running instance/app + srv = api.ListenVirtual(api.Config.Tester.ListeningAddr) } - handler := api.HTTPServer.Handler + opened := api.Servers.GetAllOpened() + h := srv.Handler + baseURL := srv.FullHost() + if len(opened) > 1 { + baseURL = "" + //we have more than one server, so we will create a handler here and redirect by registered listening addresses + h = func(reqCtx *fasthttp.RequestCtx) { + for _, s := range opened { + if strings.HasPrefix(reqCtx.URI().String(), s.FullHost()) { // yes on :80 should be passed :80 also, this is inneed for multiserver testing + s.Handler(reqCtx) + break + } + } + } + } testConfiguration := httpexpect.Config{ - BaseURL: api.HTTPServer.FullHost(), + BaseURL: baseURL, Client: &http.Client{ - Transport: httpexpect.NewFastBinder(handler), + Transport: httpexpect.NewFastBinder(h), Jar: httpexpect.NewJar(), }, Reporter: httpexpect.NewAssertReporter(t), @@ -703,12 +858,56 @@ func (s *Framework) Tester(t *testing.T) *httpexpect.Expect { // ----------------------------------MuxAPI implementation------------------------------ // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- +type ( + // RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route) + RouteNameFunc func(string) + // MuxAPI the visible api for the serveMux + MuxAPI interface { + Party(string, ...HandlerFunc) MuxAPI + // middleware serial, appending + Use(...Handler) + UseFunc(...HandlerFunc) -type muxAPI struct { - mux *serveMux - relativePath string - middleware Middleware -} + // main handlers + Handle(string, string, ...Handler) RouteNameFunc + HandleFunc(string, string, ...HandlerFunc) RouteNameFunc + // H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles + H_(string, string, func(context.IContext)) func(string) + API(string, HandlerAPI, ...HandlerFunc) + + // http methods + Get(string, ...HandlerFunc) RouteNameFunc + Post(string, ...HandlerFunc) RouteNameFunc + Put(string, ...HandlerFunc) RouteNameFunc + Delete(string, ...HandlerFunc) RouteNameFunc + Connect(string, ...HandlerFunc) RouteNameFunc + Head(string, ...HandlerFunc) RouteNameFunc + Options(string, ...HandlerFunc) RouteNameFunc + Patch(string, ...HandlerFunc) RouteNameFunc + Trace(string, ...HandlerFunc) RouteNameFunc + Any(string, ...HandlerFunc) + + // static content + StaticHandler(string, int, bool, bool, []string) HandlerFunc + Static(string, string, int) RouteNameFunc + StaticFS(string, string, int) RouteNameFunc + StaticWeb(string, string, int) RouteNameFunc + StaticServe(string, ...string) RouteNameFunc + StaticContent(string, string, []byte) func(string) + Favicon(string, ...string) RouteNameFunc + + // templates + Layout(string) MuxAPI // returns itself + } + + muxAPI struct { + mux *serveMux + relativePath string + middleware Middleware + } +) + +var _ MuxAPI = &muxAPI{} var ( // errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..' @@ -717,8 +916,6 @@ var ( errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s") ) -var _ MuxAPI = &muxAPI{} - // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. // Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI { diff --git a/mux_test.go b/mux_test.go index 2e3b28eb..95501fdd 100644 --- a/mux_test.go +++ b/mux_test.go @@ -16,12 +16,12 @@ const ( ) func testSubdomainHost() string { - return testSubdomain + strconv.Itoa(HTTPServer.Port()) + return testSubdomain + strconv.Itoa(Servers.Main().Port()) } func testSubdomainURL() (subdomainURL string) { subdomainHost := testSubdomainHost() - if HTTPServer.IsSecure() { + if Servers.Main().IsSecure() { subdomainURL = "https://" + subdomainHost } else { subdomainURL = "http://" + subdomainHost @@ -159,7 +159,7 @@ func TestMuxSimpleParty(t *testing.T) { request := func(reqPath string) { e.Request("GET", reqPath). Expect(). - Status(StatusOK).Body().Equal(HTTPServer.Host() + reqPath) + Status(StatusOK).Body().Equal(Servers.Main().Host() + reqPath) } // run the tests diff --git a/server_test.go b/server_test.go index 1e457862..ba403168 100644 --- a/server_test.go +++ b/server_test.go @@ -2,12 +2,10 @@ package iris import ( "io/ioutil" - "net/http" "os" "testing" "time" - "github.com/gavv/httpexpect" "github.com/kataras/iris/config" ) @@ -63,10 +61,10 @@ const ( ) // Contains the server test for multi running servers -// Note: this test runs two standalone (real) servers -func TestMultiRunningServers(t *testing.T) { +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") @@ -92,48 +90,77 @@ func TestMultiRunningServers(t *testing.T) { os.Remove(keyFile.Name()) }() - initDefault() - Config.DisableBanner = true - Get("/", func(ctx *Context) { ctx.Write("Hello from %s", ctx.HostString()) }) // 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 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 if ok := <-Available; !ok { t.Fatal("Unexpected error: server cannot start, please report this as bug!!") } - handler := HTTPServer.Handler - - 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 := 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) - e.Request("GET", "http://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host) } diff --git a/sessions_test.go b/sessions_test.go index 88cbac36..84845de1 100644 --- a/sessions_test.go +++ b/sessions_test.go @@ -15,7 +15,6 @@ func TestSessions(t *testing.T) { } initDefault() - HTTPServer.Config.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions Config.Sessions.Cookie = "mycustomsessionid" writeValues := func(ctx *Context) { From 01914b6c3753b0d069e153dd8a4d425fd534f4ec Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 00:25:50 +0200 Subject: [PATCH 05/10] Fasthttp seems have little bug with headers, so make a VisitAllCookies function and GetFlashes completed- tests added also --- config/sessions.go | 2 +- config/tester.go | 5 +- context.go | 126 ++++++-- context_binder_test.go | 91 ------ context_test.go | 401 ++++++++++++++++++++++++- iris.go | 5 +- sessions/providers/memory/store.go | 6 - sessions/providers/redis/redisstore.go | 6 - sessions_test.go | 120 -------- 9 files changed, 502 insertions(+), 260 deletions(-) delete mode 100644 context_binder_test.go delete mode 100644 sessions_test.go diff --git a/config/sessions.go b/config/sessions.go index 85f56f9f..c062bacf 100644 --- a/config/sessions.go +++ b/config/sessions.go @@ -34,7 +34,7 @@ type ( Redis struct { // Network "tcp" Network string - // Addr "127.0.01:6379" + // Addr "127.0.0.1:6379" Addr string // Password string .If no password then no 'AUTH'. Default "" Password string diff --git a/config/tester.go b/config/tester.go index da56e2f5..c933a366 100644 --- a/config/tester.go +++ b/config/tester.go @@ -2,12 +2,13 @@ package config // Tester configuration type Tester struct { - Debug bool ListeningAddr string + ExplicitURL bool + Debug bool } // DefaultTester returns the default configuration for a tester // the ListeningAddr is used as virtual only when no running server is founded func DefaultTester() Tester { - return Tester{Debug: false, ListeningAddr: "iris-go.com:1993"} + return Tester{ListeningAddr: "iris-go.com:1993", ExplicitURL: false, Debug: false} } diff --git a/context.go b/context.go index b80a9c00..898b3cf3 100644 --- a/context.go +++ b/context.go @@ -1,6 +1,5 @@ /* -Context.go Implements: ./context/context.go , -files: context_renderer.go, context_storage.go, context_request.go, context_response.go +Context.go Implements: ./context/context.go */ package iris @@ -56,6 +55,9 @@ const ( stopExecutionPosition = 255 // used inside GetFlash to store the lifetime request flash messages flashMessagesStoreContextKey = "_iris_flash_messages_" + flashMessageCookiePrefix = "_iris_flash_message_" + cookieHeaderID = "Cookie: " + cookieHeaderIDLen = len(cookieHeaderID) ) // this pool is used everywhere needed in the iris for example inside party-> Static @@ -286,6 +288,11 @@ func (ctx *Context) RequestHeader(k string) string { // PostFormValue returns a single value from post request's data func (ctx *Context) PostFormValue(name string) string { + return ctx.FormValueString(name) +} + +// FormValueString returns a single value, as string, from post request's data +func (ctx *Context) FormValueString(name string) string { return string(ctx.FormValue(name)) } @@ -314,7 +321,7 @@ func (ctx *Context) Subdomain() (subdomain string) { // use it only for special cases, when the default behavior doesn't suits you. // // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm -/* Credits to Manish Singh @kryptodev for URLEncode */ +/* Credits to Manish Singh @kryptodev for URLEncode by post issue share code */ func URLEncode(path string) string { if path == "" { return "" @@ -691,6 +698,28 @@ func (ctx *Context) Set(key string, value interface{}) { ctx.RequestCtx.SetUserValue(key, value) } +// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value +// +// Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment. +// This is the reason which this function exists and should be used instead of fasthttp's built'n. +func (ctx *Context) VisitAllCookies(visitor func(key string, value string)) { + // strange bug, this doesnt works also: cookieHeaderContent := ctx.Request.Header.Peek("Cookie")/User-Agent tested also + headerbody := string(ctx.Request.Header.Header()) + headerlines := strings.Split(headerbody, "\n") + for _, s := range headerlines { + if len(s) > cookieHeaderIDLen { + if s[0:cookieHeaderIDLen] == cookieHeaderID { + contents := s[cookieHeaderIDLen:] + values := strings.Split(contents, "; ") + for _, s := range values { + keyvalue := strings.SplitN(s, "=", 2) + visitor(keyvalue[0], keyvalue[1]) + } + } + } + } +} + // GetCookie returns cookie's value by it's name // returns empty string if nothing was found func (ctx *Context) GetCookie(name string) (val string) { @@ -723,19 +752,64 @@ func (ctx *Context) RemoveCookie(name string) { ctx.RequestCtx.Response.Header.DelClientCookie(name) } +// GetFlashes returns all the flash messages for available for this request +func (ctx *Context) GetFlashes() map[string]string { + // if already taken at least one time, this will be filled + if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil { + if m, isMap := messages.(map[string]string); isMap { + return m + } + } else { + flashMessageFound := false + // else first time, get all flash cookie keys(the prefix will tell us which is a flash message), and after get all one-by-one using the GetFlash. + flashMessageCookiePrefixLen := len(flashMessageCookiePrefix) + ctx.VisitAllCookies(func(key string, value string) { + if len(key) > flashMessageCookiePrefixLen { + if key[0:flashMessageCookiePrefixLen] == flashMessageCookiePrefix { + unprefixedKey := key[flashMessageCookiePrefixLen:] + _, err := ctx.GetFlash(unprefixedKey) // this func will add to the list (flashMessagesStoreContextKey) also + if err == nil { + flashMessageFound = true + } + } + + } + }) + // if we found at least one flash message then re-execute this function to return the list + if flashMessageFound { + return ctx.GetFlashes() + } + } + return nil +} + +func (ctx *Context) decodeFlashCookie(key string) (string, string) { + cookieKey := flashMessageCookiePrefix + key + cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(cookieKey)) + + if cookieValue != "" { + v, e := base64.URLEncoding.DecodeString(cookieValue) + if e == nil { + return cookieKey, string(v) + } + } + return "", "" +} + // GetFlash get a flash message by it's key // returns the value as string and an error // // if the cookie doesn't exists the string is empty and the error is filled // after the request's life the value is removed -func (ctx *Context) GetFlash(key string) (value string, err error) { +func (ctx *Context) GetFlash(key string) (string, error) { // first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie storeExists := false + if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil { m, isMap := messages.(map[string]string) if !isMap { - return "", fmt.Errorf("Messages request's store is not a map[string]string. This suppose will never happen, please report this bug.") + return "", fmt.Errorf("Flash store is not a map[string]string. This suppose will never happen, please report this bug.") } storeExists = true // in order to skip the check later for k, v := range m { @@ -745,38 +819,32 @@ func (ctx *Context) GetFlash(key string) (value string, err error) { } } - cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key)) - + cookieKey, cookieValue := ctx.decodeFlashCookie(key) if cookieValue == "" { - err = errFlashNotFound.Return() - } else { - v, e := base64.URLEncoding.DecodeString(cookieValue) - if e != nil { - return "", err - } - value = string(v) - // store this flash message to the lifetime request's local storage, - // I choose this method because no need to store it if not used at all - if storeExists { - ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = value - } else { - flashStoreMap := make(map[string]string) - flashStoreMap[key] = value - ctx.Set(flashMessagesStoreContextKey, flashStoreMap) - } - - //remove the real cookie, no need to have that, we stored it on lifetime request - ctx.RemoveCookie(key) - //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "") + return "", errFlashNotFound.Return() } - return + // store this flash message to the lifetime request's local storage, + // I choose this method because no need to store it if not used at all + if storeExists { + ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = cookieValue + } else { + flashStoreMap := make(map[string]string) + flashStoreMap[key] = cookieValue + ctx.Set(flashMessagesStoreContextKey, flashStoreMap) + } + + //remove the real cookie, no need to have that, we stored it on lifetime request + ctx.RemoveCookie(cookieKey) + return cookieValue, nil + //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "") + } // SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string) // the value will be available on the NEXT request func (ctx *Context) SetFlash(key string, value string) { c := fasthttp.AcquireCookie() - c.SetKey(key) + c.SetKey(flashMessageCookiePrefix + key) c.SetValue(base64.URLEncoding.EncodeToString([]byte(value))) c.SetPath("/") c.SetHTTPOnly(true) diff --git a/context_binder_test.go b/context_binder_test.go deleted file mode 100644 index 99f6fad4..00000000 --- a/context_binder_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package iris - -import ( - "encoding/xml" - "net/url" - "strconv" - "testing" -) - -// Contains tests for context.ReadJSON/ReadXML/ReadFORM - -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 TestBindForm(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 TestBindJSON(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 TestBindXML(t *testing.T) { - initDefault() - - Post("/xml", func(ctx *Context) { - obj := testBinderXMLData{} - err := ctx.ReadXML(&obj) - if err != nil { - t.Fatalf("Error when parsing the XML body: %s", err.Error()) - } - ctx.XML(StatusOK, obj) - }) - - e := Tester(t) - expectedObj := testBinderXMLData{ - XMLName: xml.Name{Local: "info", Space: "info"}, - FirstAttr: "this is the first attr", - SecondAttr: "this is the second attr", - Name: "Iris web framework", - Birth: "13 March 2016", - Stars: 4064, - } - // so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting - expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `">` + expectedObj.Name + `` + expectedObj.Birth + `` + strconv.Itoa(expectedObj.Stars) + `` - e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody) -} diff --git a/context_test.go b/context_test.go index 596ad3b0..372b1a78 100644 --- a/context_test.go +++ b/context_test.go @@ -1,6 +1,11 @@ package iris -import "testing" +import ( + "encoding/xml" + "net/url" + "strconv" + "testing" +) func TestContextReset(t *testing.T) { var context Context @@ -72,7 +77,7 @@ func TestContextDoNextStop(t *testing.T) { } } -func TestContextParam(t *testing.T) { +func TestContextParams(t *testing.T) { var context Context params := PathParameters{ PathParameter{Key: "testkey", Value: "testvalue"}, @@ -104,8 +109,396 @@ func TestContextParam(t *testing.T) { } else if vi != 548921854390354 { t.Fatalf("Expecting to receive %d but we got %d", 548921854390354, vi) } -} -func TestContextURLParam(t *testing.T) { + // end-to-end test now, note that we will not test the whole mux here, this happens on http_test.go + + initDefault() + expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like" + Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *Context) { + paramsStr := ctx.Params.String() + ctx.Write(paramsStr) + }) + + Tester(t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(StatusOK).Body().Equal(expectedParamsStr) } + +func TestContextURLParams(t *testing.T) { + initDefault() + passedParams := map[string]string{"param1": "value1", "param2": "value2"} + Get("/", func(ctx *Context) { + params := ctx.URLParams() + ctx.JSON(StatusOK, params) + }) + e := Tester(t) + + e.GET("/").WithQueryObject(passedParams).Expect().Status(StatusOK).JSON().Equal(passedParams) +} + +// hoststring returns the full host, will return the HOST:IP +func TestContextHostString(t *testing.T) { + initDefault() + Config.Tester.ListeningAddr = "localhost:8080" + Get("/", func(ctx *Context) { + ctx.Write(ctx.HostString()) + }) + + Get("/wrong", func(ctx *Context) { + ctx.Write(ctx.HostString() + "w") + }) + + e := Tester(t) + e.GET("/").Expect().Status(StatusOK).Body().Equal(Config.Tester.ListeningAddr) + e.GET("/wrong").Expect().Body().NotEqual(Config.Tester.ListeningAddr) +} + +// VirtualHostname returns the hostname only, +// if the host starts with 127.0.0.1 or localhost it gives the registered hostname part of the listening addr +func TestContextVirtualHostName(t *testing.T) { + initDefault() + vhost := "mycustomvirtualname.com" + Config.Tester.ListeningAddr = vhost + ":8080" + Get("/", func(ctx *Context) { + ctx.Write(ctx.VirtualHostname()) + }) + + Get("/wrong", func(ctx *Context) { + ctx.Write(ctx.VirtualHostname() + "w") + }) + + e := Tester(t) + e.GET("/").Expect().Status(StatusOK).Body().Equal(vhost) + e.GET("/wrong").Expect().Body().NotEqual(vhost) +} + +func TestContextFormValueString(t *testing.T) { + initDefault() + var k, v string + k = "postkey" + v = "postvalue" + Post("/", func(ctx *Context) { + ctx.Write(k + "=" + ctx.FormValueString(k)) + }) + e := Tester(t) + + e.POST("/").WithFormField(k, v).Expect().Status(StatusOK).Body().Equal(k + "=" + v) +} + +func TestContextSubdomain(t *testing.T) { + initDefault() + Config.Tester.ListeningAddr = "mydomain.com:9999" + //Config.Tester.ExplicitURL = true + Party("mysubdomain.").Get("/mypath", func(ctx *Context) { + ctx.Write(ctx.Subdomain()) + }) + + e := Tester(t) + e.GET("/").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound) + e.GET("/mypath").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusOK).Body().Equal("mysubdomain") + + //e.GET("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound) + //e.GET("http://mysubdomain.mydomain.com:9999/mypath").Expect().Status(StatusOK).Body().Equal("mysubdomain") +} + +type testBinderData struct { + Username string + Mail string + Data []string `form:"mydata" json:"mydata"` +} + +type testBinderXMLData struct { + XMLName xml.Name `xml:"info"` + FirstAttr string `xml:"first,attr"` + SecondAttr string `xml:"second,attr"` + Name string `xml:"name",json:"name"` + Birth string `xml:"birth",json:"birth"` + Stars int `xml:"stars",json:"stars"` +} + +func TestContextReadForm(t *testing.T) { + initDefault() + + Post("/form", func(ctx *Context) { + obj := testBinderData{} + err := ctx.ReadForm(&obj) + if err != nil { + t.Fatalf("Error when parsing the FORM: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + e := Tester(t) + + passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"}, + "[1]": []string{"mydata2"}}} + + expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} + + e.POST("/form").WithForm(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) +} + +func TestContextReadJSON(t *testing.T) { + initDefault() + Post("/json", func(ctx *Context) { + obj := testBinderData{} + err := ctx.ReadJSON(&obj) + if err != nil { + t.Fatalf("Error when parsing the JSON body: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + e := Tester(t) + passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}} + expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} + + e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) +} + +func TestContextReadXML(t *testing.T) { + initDefault() + + Post("/xml", func(ctx *Context) { + obj := testBinderXMLData{} + err := ctx.ReadXML(&obj) + if err != nil { + t.Fatalf("Error when parsing the XML body: %s", err.Error()) + } + ctx.XML(StatusOK, obj) + }) + + e := Tester(t) + expectedObj := testBinderXMLData{ + XMLName: xml.Name{Local: "info", Space: "info"}, + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + // so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting + expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `">` + expectedObj.Name + `` + expectedObj.Birth + `` + strconv.Itoa(expectedObj.Stars) + `` + e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody) +} + +func TestContextRedirect(t *testing.T) { + +} + +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) + +} + +/* +NOTES OTAN ERTHW: + +EDW EXW TO PROVLIMA GIATI ENOEITE OTI TO CONTEXT DN DIATERIRE SE OLA ARA DN BORW NA ELENKSW STO GETFLASHES kAI STO GETFLASH TAUTOXRONA TO IDIO CONTEXT KEY SE DIAFORETIKA REQUESTS, NA DW TO APO PANW TEST, NA TO KOITAKSW +TA MAPS DOULEVOUN KALA KATI ALLO EINAI TO PROVLIMA +*/ +func TestContextFlashMessages(t *testing.T) { + initDefault() + firstKey := "name" + lastKey := "package" + + values := map[string]string{firstKey: "kataras", lastKey: "iris"} + + // set the flashes, the cookies are filled + Put("/set", func(ctx *Context) { + for k, v := range values { + ctx.SetFlash(k, v) + } + }) + + // get the first flash, the next should be avaiable to the next requess + Get("/get_first_flash", func(ctx *Context) { + for k := range values { + v, _ := ctx.GetFlash(k) + ctx.JSON(StatusOK, map[string]string{k: v}) + 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) { + i := 0 + for k := range values { + i++ + if i == len(values) { + v, _ := ctx.GetFlash(k) + ctx.JSON(StatusOK, map[string]string{k: v}) + } + } + }) + + 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 k := range values { + v, err := ctx.GetFlash(k) + if err == nil { + kv[k] = v + } + } + 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(values) + // 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(values) + // 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() +} diff --git a/iris.go b/iris.go index 4d77d24f..bad072e5 100644 --- a/iris.go +++ b/iris.go @@ -284,7 +284,6 @@ func (s *Framework) Go() error { s.Plugins.DoPreListen(s) if firstErr := s.Servers.OpenAll(); firstErr != nil { - panic("iris:287") return firstErr } @@ -822,6 +821,10 @@ func NewTester(api *Framework, t *testing.T) *httpexpect.Expect { } } + if api.Config.Tester.ExplicitURL { + baseURL = "" + } + testConfiguration := httpexpect.Config{ BaseURL: baseURL, Client: &http.Client{ diff --git a/sessions/providers/memory/store.go b/sessions/providers/memory/store.go index b96835f4..40c8d91d 100644 --- a/sessions/providers/memory/store.go +++ b/sessions/providers/memory/store.go @@ -19,15 +19,11 @@ var _ store.IStore = &Store{} // GetAll returns all values func (s *Store) GetAll() map[string]interface{} { - s.mu.Lock() - defer s.mu.Unlock() return s.values } // VisitAll loop each one entry and calls the callback function func(key,value) func (s *Store) VisitAll(cb func(k string, v interface{})) { - s.mu.Lock() - defer s.mu.Unlock() for key := range s.values { cb(key, s.values[key]) } @@ -36,9 +32,7 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) { // Get returns the value of an entry by its key func (s *Store) Get(key string) interface{} { Provider.Update(s.sid) - s.mu.Lock() if value, found := s.values[key]; found { - s.mu.Unlock() return value } s.mu.Unlock() diff --git a/sessions/providers/redis/redisstore.go b/sessions/providers/redis/redisstore.go index fa43213c..58a62834 100644 --- a/sessions/providers/redis/redisstore.go +++ b/sessions/providers/redis/redisstore.go @@ -81,15 +81,11 @@ func (s *Store) update() { // GetAll returns all values func (s *Store) GetAll() map[string]interface{} { - s.mu.Lock() - defer s.mu.Unlock() return s.values } // VisitAll loop each one entry and calls the callback function func(key,value) func (s *Store) VisitAll(cb func(k string, v interface{})) { - s.mu.Lock() - defer s.mu.Unlock() for key := range s.values { cb(key, s.values[key]) } @@ -98,12 +94,10 @@ func (s *Store) VisitAll(cb func(k string, v interface{})) { // Get returns the value of an entry by its key func (s *Store) Get(key string) interface{} { Provider.Update(s.sid) - s.mu.Lock() if value, found := s.values[key]; found { s.mu.Unlock() return value } - s.mu.Unlock() return nil } diff --git a/sessions_test.go b/sessions_test.go deleted file mode 100644 index 84845de1..00000000 --- a/sessions_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package iris - -// Contains tests for sessions(sessions package) & flash messages(context) - -import ( - "testing" -) - -func TestSessions(t *testing.T) { - - 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() -} - -func FlashMessagesTest(t *testing.T) { - initDefault() - values := map[string]string{"name": "kataras", "package": "iris"} - - Put("/set", func(ctx *Context) { - for k, v := range values { - ctx.SetFlash(k, v) - } - }) - - //we don't get the flash so on the next request the flash messages should be available. - Get("/get_no_getflash", func(ctx *Context) {}) - - Get("/get", func(ctx *Context) { - // one time one handler - kv := make(map[string]string) - for k := range values { - kv[k], _ = ctx.GetFlash(k) - } - //second time on the same handler - for k := range values { - kv[k], _ = ctx.GetFlash(k) - } - - }, 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. - kv := make(map[string]string) - for k := range values { - kv[k], _ = ctx.GetFlash(k) - } - // print them to the client for test the response also - ctx.JSON(StatusOK, kv) - }) - - e := Tester(t) - e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty() - // 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).Cookies().NotEmpty() - e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values) - // second request ,the flash messages here should be not available and cookie has been removed - // (the true is that the cookie is removed from the first GetFlash, but is available though the whole request saved on context's values for faster get, keep that secret!)* - g := e.GET("/get").Expect().Status(StatusOK) - g.JSON().Object().Empty() - g.Cookies().Empty() - -} From 728cba60f707c8531b2e913f92f3e09a1d53aae9 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 01:20:04 +0200 Subject: [PATCH 06/10] Add test for RedirectTo and fix the wrong-way-to-do-test-with-map-which-keys-some-times-change-order-lol --- context.go | 2 +- context_test.go | 72 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/context.go b/context.go index 898b3cf3..b1fadd50 100644 --- a/context.go +++ b/context.go @@ -426,7 +426,7 @@ func (ctx *Context) SetHeader(k string, v string) { // first parameter is the url to redirect // second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) { - httpStatus := StatusFound // temporary redirect + httpStatus := StatusFound // a 'temporary-redirect-like' wich works better than for our purpose if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 { httpStatus = statusHeader[0] } diff --git a/context_test.go b/context_test.go index 372b1a78..0a4d3b9b 100644 --- a/context_test.go +++ b/context_test.go @@ -4,6 +4,7 @@ import ( "encoding/xml" "net/url" "strconv" + "strings" "testing" ) @@ -281,8 +282,37 @@ func TestContextReadXML(t *testing.T) { e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody) } -func TestContextRedirect(t *testing.T) { +// 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) { @@ -322,31 +352,25 @@ func TestContextUserValues(t *testing.T) { } -/* -NOTES OTAN ERTHW: - -EDW EXW TO PROVLIMA GIATI ENOEITE OTI TO CONTEXT DN DIATERIRE SE OLA ARA DN BORW NA ELENKSW STO GETFLASHES kAI STO GETFLASH TAUTOXRONA TO IDIO CONTEXT KEY SE DIAFORETIKA REQUESTS, NA DW TO APO PANW TEST, NA TO KOITAKSW -TA MAPS DOULEVOUN KALA KATI ALLO EINAI TO PROVLIMA -*/ func TestContextFlashMessages(t *testing.T) { initDefault() firstKey := "name" lastKey := "package" - values := map[string]string{firstKey: "kataras", lastKey: "iris"} - + 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 k, v := range values { - ctx.SetFlash(k, v) + 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 k := range values { - v, _ := ctx.GetFlash(k) - ctx.JSON(StatusOK, map[string]string{k: v}) + for _, v := range values { + val, _ := ctx.GetFlash(v.Key) + ctx.JSON(StatusOK, map[string]string{v.Key: val}) break } @@ -358,12 +382,10 @@ func TestContextFlashMessages(t *testing.T) { // get the last flash, the next should be avaiable to the next requess Get("/get_last_flash", func(ctx *Context) { - i := 0 - for k := range values { - i++ - if i == len(values) { - v, _ := ctx.GetFlash(k) - ctx.JSON(StatusOK, map[string]string{k: v}) + for i, v := range values { + if i == len(values)-1 { + val, _ := ctx.GetFlash(v.Key) + ctx.JSON(StatusOK, map[string]string{v.Key: val}) } } }) @@ -375,10 +397,10 @@ func TestContextFlashMessages(t *testing.T) { // 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 k := range values { - v, err := ctx.GetFlash(k) + for _, v := range values { + val, err := ctx.GetFlash(v.Key) if err == nil { - kv[k] = v + kv[v.Key] = val } } ctx.JSON(StatusOK, kv) @@ -422,7 +444,7 @@ func TestContextFlashMessages(t *testing.T) { // 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(values) + 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() @@ -430,7 +452,7 @@ func TestContextFlashMessages(t *testing.T) { //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(values) + 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() From f5f7f22245cbb73ffc6f9ab0cd48331274a00b03 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 01:26:05 +0200 Subject: [PATCH 07/10] Ok finish the context's tests --- context/context.go | 2 ++ context_test.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/context/context.go b/context/context.go index 2116a83e..66650aa6 100644 --- a/context/context.go +++ b/context/context.go @@ -66,9 +66,11 @@ type ( GetString(string) string GetInt(string) int Set(string, interface{}) + VisitAllCookies(func(string, string)) SetCookie(*fasthttp.Cookie) SetCookieKV(string, string) RemoveCookie(string) + GetFlashes() map[string]string GetFlash(string) (string, error) SetFlash(string, string) Session() store.IStore diff --git a/context_test.go b/context_test.go index 0a4d3b9b..0907d59b 100644 --- a/context_test.go +++ b/context_test.go @@ -1,5 +1,12 @@ package iris +/* The most part of the context covered, +the other part contains serving static methods, +find remote ip and GetInt, +I am not waiting unexpected behaviors from the rest of the funcs, +so that's all with context's tests. +*/ + import ( "encoding/xml" "net/url" @@ -308,6 +315,7 @@ func TestContextRedirectTo(t *testing.T) { //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") From 7723c22ef6aabfca92770e168b050d1c6b0312c2 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 01:36:38 +0200 Subject: [PATCH 08/10] Ok finish the important staff, the other are already tested hundreds of times --- context_test.go | 83 +++++++++++++++++- mux_test.go => http_test.go | 166 +++++++++++++++++++++++++++++++++++- iris_test.go | 11 ++- plugin_test.go | 6 ++ render_test.go | 85 ------------------ server_test.go | 166 ------------------------------------ 6 files changed, 261 insertions(+), 256 deletions(-) rename mux_test.go => http_test.go (68%) delete mode 100644 render_test.go delete mode 100644 server_test.go diff --git a/context_test.go b/context_test.go index 0907d59b..7045131a 100644 --- a/context_test.go +++ b/context_test.go @@ -1,10 +1,13 @@ package iris -/* The most part of the context covered, +/* +The most part of the context covered, the other part contains serving static methods, -find remote ip and GetInt, +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 ( @@ -532,3 +535,79 @@ func TestContextSessions(t *testing.T) { e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty() e.GET("/clear").Expect().Status(StatusOK).JSON().Object().Empty() } + +type renderTestInformationType struct { + XMLName xml.Name `xml:"info"` + FirstAttr string `xml:"first,attr"` + SecondAttr string `xml:"second,attr"` + Name string `xml:"name",json:"name"` + Birth string `xml:"birth",json:"birth"` + Stars int `xml:"stars",json:"stars"` +} + +func TestContextRenderRest(t *testing.T) { + initDefault() + + dataContents := []byte("Some binary data here.") + textContents := "Plain text here" + JSONPContents := map[string]string{"hello": "jsonp"} + JSONPCallback := "callbackName" + JSONXMLContents := renderTestInformationType{ + XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + markdownContents := "# Hello dynamic markdown from Iris" + + Get("/data", func(ctx *Context) { + ctx.Data(StatusOK, dataContents) + }) + + Get("/text", func(ctx *Context) { + ctx.Text(StatusOK, textContents) + }) + + Get("/jsonp", func(ctx *Context) { + ctx.JSONP(StatusOK, JSONPCallback, JSONPContents) + }) + + Get("/json", func(ctx *Context) { + ctx.JSON(StatusOK, JSONXMLContents) + }) + Get("/xml", func(ctx *Context) { + ctx.XML(StatusOK, JSONXMLContents) + }) + + Get("/markdown", func(ctx *Context) { + ctx.Markdown(StatusOK, markdownContents) + }) + + e := Tester(t) + dataT := e.GET("/data").Expect().Status(StatusOK) + dataT.Header("Content-Type").Equal("application/octet-stream") + dataT.Body().Equal(string(dataContents)) + + textT := e.GET("/text").Expect().Status(StatusOK) + textT.Header("Content-Type").Equal("text/plain; charset=UTF-8") + textT.Body().Equal(textContents) + + JSONPT := e.GET("/jsonp").Expect().Status(StatusOK) + JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8") + JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`) + + JSONT := e.GET("/json").Expect().Status(StatusOK) + JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8") + JSONT.JSON().Object().Equal(JSONXMLContents) + + XMLT := e.GET("/xml").Expect().Status(StatusOK) + XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8") + XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `">` + JSONXMLContents.Name + `` + JSONXMLContents.Birth + `` + strconv.Itoa(JSONXMLContents.Stars) + ``) + + markdownT := e.GET("/markdown").Expect().Status(StatusOK) + markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8") + markdownT.Body().Equal("

" + markdownContents[2:] + "

\n") + +} diff --git a/mux_test.go b/http_test.go similarity index 68% rename from mux_test.go rename to http_test.go index 95501fdd..e99f45cf 100644 --- a/mux_test.go +++ b/http_test.go @@ -1,15 +1,179 @@ package iris -// Contains tests for the mux(Router) +/* +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" diff --git a/iris_test.go b/iris_test.go index e14220d7..69504f1c 100644 --- a/iris_test.go +++ b/iris_test.go @@ -1,7 +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() and not api := New() or just Default because we want to cover as much code as possible -// The tests are usually end-to-end, except some features like plugins, which we have normal unit testing and end-to-end tests +// We use Default := New() via initDefault() and not api := New() neither just Default. because we want to cover as much code as possible +// The tests are mostly end-to-end, except some features like plugins. // diff --git a/plugin_test.go b/plugin_test.go index 4f072426..b22daf02 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -1,5 +1,11 @@ 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" diff --git a/render_test.go b/render_test.go deleted file mode 100644 index d1c98401..00000000 --- a/render_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package iris - -// Contains tests for render/rest & render/template - -import ( - "encoding/xml" - "strconv" - "testing" -) - -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 TestRenderRest(t *testing.T) { - initDefault() - - dataContents := []byte("Some binary data here.") - textContents := "Plain text here" - JSONPContents := map[string]string{"hello": "jsonp"} - JSONPCallback := "callbackName" - JSONXMLContents := renderTestInformationType{ - XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later - FirstAttr: "this is the first attr", - SecondAttr: "this is the second attr", - Name: "Iris web framework", - Birth: "13 March 2016", - Stars: 4064, - } - markdownContents := "# Hello dynamic markdown from Iris" - - Get("/data", func(ctx *Context) { - ctx.Data(StatusOK, dataContents) - }) - - Get("/text", func(ctx *Context) { - ctx.Text(StatusOK, textContents) - }) - - Get("/jsonp", func(ctx *Context) { - ctx.JSONP(StatusOK, JSONPCallback, JSONPContents) - }) - - Get("/json", func(ctx *Context) { - ctx.JSON(StatusOK, JSONXMLContents) - }) - Get("/xml", func(ctx *Context) { - ctx.XML(StatusOK, JSONXMLContents) - }) - - Get("/markdown", func(ctx *Context) { - ctx.Markdown(StatusOK, markdownContents) - }) - - e := Tester(t) - dataT := e.GET("/data").Expect().Status(StatusOK) - dataT.Header("Content-Type").Equal("application/octet-stream") - dataT.Body().Equal(string(dataContents)) - - textT := e.GET("/text").Expect().Status(StatusOK) - textT.Header("Content-Type").Equal("text/plain; charset=UTF-8") - textT.Body().Equal(textContents) - - JSONPT := e.GET("/jsonp").Expect().Status(StatusOK) - JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8") - JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`) - - JSONT := e.GET("/json").Expect().Status(StatusOK) - JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8") - JSONT.JSON().Object().Equal(JSONXMLContents) - - XMLT := e.GET("/xml").Expect().Status(StatusOK) - XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8") - XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `">` + JSONXMLContents.Name + `` + JSONXMLContents.Birth + `` + strconv.Itoa(JSONXMLContents.Stars) + ``) - - markdownT := e.GET("/markdown").Expect().Status(StatusOK) - markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8") - markdownT.Body().Equal("

" + markdownContents[2:] + "

\n") - -} diff --git a/server_test.go b/server_test.go deleted file mode 100644 index ba403168..00000000 --- a/server_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package iris - -import ( - "io/ioutil" - "os" - "testing" - "time" - - "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) - -} From 0b8cb29e0c411f01e8caf32a7178acad6c1f39a9 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 02:36:48 +0200 Subject: [PATCH 09/10] Update to 3.0.0-pre.release, Read HISTORY.md for new features and changes Yes new features, with Iris you always get them on each version or revision or non-semantic version --- HISTORY.md | 23 +++++ README.md | 4 +- context.go | 9 +- context/context.go | 8 +- deprecated.go | 82 ++++++++++++++++ iris.go | 237 +++++++++++++++------------------------------ plugin_test.go | 2 +- 7 files changed, 193 insertions(+), 172 deletions(-) create mode 100644 deprecated.go diff --git a/HISTORY.md b/HISTORY.md index 608ef6e9..2689307b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,29 @@ **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)` + +**NEW FEATURE**: Build'n support for your API's end-to-end tests + +- Added `tester := iris.Tester(*testing.T)` , look inside: [http_test.go](https://github.com/kataras/iris/blob/master/http_test.go) & [./context_test.go](https://github.com/kataras/iris/blob/master/context_test.go) for `Tester` usage, you can also look inside the [httpexpect's repo](https://github.com/gavv/httpexpect/blob/master/example/iris_test.go) for extended examples with Iris. + + + ## 3.0.0-rc.3 -> 3.0.0-rc.4 **NEW FEATURE**: **Handlebars** template engine support with all Iris' view engine's functions/helpers support, as requested [here](https://github.com/kataras/iris/issues/239): diff --git a/README.md b/README.md index cff7b320..01ebcd90 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v3.0.0-rc.4** +Current: **v3.0.0-pre.release** > Iris is an active project @@ -185,7 +185,7 @@ License can be found [here](LICENSE). [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.4-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v3.0.0--pre.release-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square [Chat]: https://kataras.rocket.chat/channel/iris diff --git a/context.go b/context.go index b1fadd50..1136c1c6 100644 --- a/context.go +++ b/context.go @@ -286,18 +286,13 @@ func (ctx *Context) RequestHeader(k string) string { return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k)) } -// PostFormValue returns a single value from post request's data -func (ctx *Context) PostFormValue(name string) string { - return ctx.FormValueString(name) -} - // FormValueString returns a single value, as string, from post request's data func (ctx *Context) FormValueString(name string) string { return string(ctx.FormValue(name)) } -// PostFormMulti returns a slice of string from post request's data -func (ctx *Context) PostFormMulti(name string) []string { +// FormValues returns a slice of string from post request's data +func (ctx *Context) FormValues(name string) []string { arrBytes := ctx.PostArgs().PeekMulti(name) arrStr := make([]string, len(arrBytes)) for i, v := range arrBytes { diff --git a/context/context.go b/context/context.go index 66650aa6..166455dc 100644 --- a/context/context.go +++ b/context/context.go @@ -14,6 +14,10 @@ type ( // IContext the interface for the iris/context // Used mostly inside packages which shouldn't be import ,directly, the kataras/iris. IContext interface { + // deprecated Start + PostFormValue(string) string + PostFormMulti(string) []string + // deprecated End Param(string) string ParamInt(string) (int, error) ParamInt64(string) (int64, error) @@ -29,8 +33,8 @@ type ( RequestIP() string RemoteAddr() string RequestHeader(k string) string - PostFormValue(string) string - PostFormMulti(string) []string + FormValueString(string) string + FormValues(string) []string SetStatusCode(int) SetContentType(string) SetHeader(string, string) diff --git a/deprecated.go b/deprecated.go new file mode 100644 index 00000000..4d24938e --- /dev/null +++ b/deprecated.go @@ -0,0 +1,82 @@ +package iris + +import "github.com/kataras/iris/config" + +/* Contains some different functions of context.go & iris.go which will be removed on the next revision */ + +// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead +// DEPRECATED: use .AddServer instead +// AddServers starts a server which listens to this station +// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) +// +// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station +// +// receives one parameter which is the config.Server for the new server +// returns the new standalone server( you can close this server by the returning reference) +// +// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo +// +// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo +func SecondaryListen(cfg config.Server) *Server { + return Default.SecondaryListen(cfg) +} + +// SecondaryListen same as .AddServer/.Servers.Add(config.Server) instead +// DEPRECATED: use .AddServer instead +// AddServers starts a server which listens to this station +// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) +// +// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station +// +// receives one parameter which is the config.Server for the new server +// returns the new standalone server( you can close this server by the returning reference) +// +// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo +// +// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo +func (s *Framework) SecondaryListen(cfg config.Server) *Server { + return s.Servers.Add(cfg) +} + +// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it +// DEPRECATED: use ListenVirtual instead +// initializes the whole framework but server doesn't listens to a specific net.Listener +// it is not blocking the app +func NoListen(optionalAddr ...string) *Server { + return Default.NoListen(optionalAddr...) +} + +// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it +// DEPRECATED: use ListenVirtual instead +// initializes the whole framework but server doesn't listens to a specific net.Listener +// it is not blocking the app +func (s *Framework) NoListen(optionalAddr ...string) *Server { + return s.ListenVirtual(optionalAddr...) +} + +// CloseWithErr terminates all the registered servers and returns an error if any +// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close()) +// if you want to panic on this error use the iris.Must(iris.Close()) +func CloseWithErr() error { + return Default.Close() +} + +// CloseWithErr terminates all the registered servers and returns an error if any +// DEPRECATED: use Close instead, and if you want to panic on errors : iris.Must(iris.Close()) +// if you want to panic on this error use the iris.Must(iris.Close()) +func (s *Framework) CloseWithErr() error { + return s.Close() +} + +// PostFormMulti returns a slice of string from post request's data +// DEPRECATED: Plase use FormValues instead +func (ctx *Context) PostFormMulti(name string) []string { + return ctx.FormValues(name) +} + +// PostFormValue This will be deprecated +///DEPRECATED: please use FormValueString instead +// PostFormValue returns a single value from post request's data +func (ctx *Context) PostFormValue(name string) string { + return ctx.FormValueString(name) +} diff --git a/iris.go b/iris.go index bad072e5..578fa141 100644 --- a/iris.go +++ b/iris.go @@ -81,7 +81,7 @@ import ( const ( // Version of the iris - Version = "3.0.0-rc.4" + Version = "3.0.0-pre.release" // HTMLEngine conversion for config.HTMLEngine HTMLEngine = config.HTMLEngine @@ -156,17 +156,14 @@ type ( FrameworkAPI interface { MuxAPI Must(error) + AddServer(config.Server) *Server ListenTo(config.Server) error - ListenWithErr(string) error Listen(string) - ListenTLSWithErr(string, string, string) error ListenTLS(string, string, string) - ListenUNIXWithErr(string, os.FileMode) error ListenUNIX(string, os.FileMode) - SecondaryListen(config.Server) *Server ListenVirtual(...string) *Server - NoListen(...string) *Server - Close() + Go() error + Close() error // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also MustUse(...Handler) MustUseFunc(...HandlerFunc) @@ -289,18 +286,17 @@ func (s *Framework) Go() error { // 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() - } + l := len(openedServers) + hosts := make([]string, l, l) + for i, srv := range openedServers { + hosts[i] = srv.Host() } - s.Logger.PrintBanner(banner, serversMessage) + bannerMessage := time.Now().Format(config.TimeFormat) + ": Running at " + strings.Join(hosts, ", ") + s.Logger.PrintBanner(banner, bannerMessage) + } s.Plugins.DoPostListen(s) @@ -308,7 +304,7 @@ func (s *Framework) Go() error { go func() { s.Available <- true }() ch := make(chan os.Signal) <-ch - s.CloseWithErr() // btw, don't panic here + s.Close() // btw, don't panic here return nil } @@ -325,7 +321,44 @@ func (s *Framework) Must(err error) { } } +// 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 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) @@ -338,59 +371,24 @@ func (s *Framework) ListenTo(cfg config.Server) (err error) { return s.Go() } -// 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 ListenWithErr(addr string) error { - return Default.ListenWithErr(addr) -} - // Listen starts the standalone http server // which listens to the addr parameter which as the form of // host:port // -// It panics on error if you need a func to return an error use the ListenWithErr -// ex: iris.Listen(":8080") +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"}) func Listen(addr string) { Default.Listen(addr) } -// ListenWithErr starts the standalone http server -// which listens to the addr parameter which as the form of -// host:port -// -// It returns an error you are responsible how to handle this -// if you need a func to panic on error use the Listen -// ex: log.Fatal(iris.ListenWithErr(":8080")) -func (s *Framework) ListenWithErr(addr string) error { - return s.ListenTo(config.Server{ListeningAddr: addr}) -} - // Listen starts the standalone http server // which listens to the addr parameter which as the form of // host:port // -// It panics on error if you need a func to return an error use the ListenWithErr -// ex: iris.Listen(":8080") +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"}) func (s *Framework) Listen(addr string) { - s.Must(s.ListenWithErr(addr)) -} - -// ListenTLSWithErr Starts a https server with certificates, -// if you use this method the requests of the form of 'http://' will fail -// only https:// connections are allowed -// which listens to the addr parameter which as the form of -// host:port -// -// It returns an error you are responsible how to handle this -// if you need a func to panic on error use the ListenTLS -// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key")) -func ListenTLSWithErr(addr string, certFile string, keyFile string) error { - return Default.ListenTLSWithErr(addr, certFile, keyFile) + s.Must(s.ListenTo(config.Server{ListeningAddr: addr})) } // ListenTLS Starts a https server with certificates, @@ -399,114 +397,41 @@ func ListenTLSWithErr(addr string, certFile string, keyFile string) error { // which listens to the addr parameter which as the form of // host:port // -// It panics on error if you need a func to return an error use the ListenTLSWithErr -// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key") +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key") func ListenTLS(addr string, certFile string, keyFile string) { Default.ListenTLS(addr, certFile, keyFile) } -// ListenTLSWithErr Starts a https server with certificates, -// if you use this method the requests of the form of 'http://' will fail -// only https:// connections are allowed -// which listens to the addr parameter which as the form of -// host:port -// -// It returns an error you are responsible how to handle this -// if you need a func to panic on error use the ListenTLS -// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key")) -func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error { - if certFile == "" || keyFile == "" { - return fmt.Errorf("You should provide certFile and keyFile for TLS/SSL") - } - return s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}) -} - // ListenTLS Starts a https server with certificates, // if you use this method the requests of the form of 'http://' will fail // only https:// connections are allowed // which listens to the addr parameter which as the form of // host:port // -// It panics on error if you need a func to return an error use the ListenTLSWithErr -// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key") +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key") func (s *Framework) ListenTLS(addr string, certFile, keyFile string) { - s.Must(s.ListenTLSWithErr(addr, certFile, keyFile)) -} - -// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix -// returns an error if something bad happens when trying to listen to -func ListenUNIXWithErr(addr string, mode os.FileMode) error { - return Default.ListenUNIXWithErr(addr, mode) + if certFile == "" || keyFile == "" { + s.Logger.Panic("You should provide certFile and keyFile for TLS/SSL") + } + s.Must(s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile})) } // ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix -// panics on error +// +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(":8080", Mode: os.FileMode) func ListenUNIX(addr string, mode os.FileMode) { Default.ListenUNIX(addr, mode) } -// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix -// returns an error if something bad happens when trying to listen to -func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error { - return s.ListenTo(config.Server{ListeningAddr: addr, Mode: mode}) -} - // ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix -// panics on error +// +// It panics on error if you need a func to return an error, use the ListenTo +// ex: err := iris.ListenTo(":8080", Mode: os.FileMode) func (s *Framework) ListenUNIX(addr string, mode os.FileMode) { - s.Must(s.ListenUNIXWithErr(addr, mode)) -} - -// SecondaryListen NOTE: This will be deprecated -// Use .Servers.Add(config.Server) instead -// -// SecondaryListen starts a server which listens to this station -// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) -// -// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station -// -// receives one parameter which is the config.Server for the new server -// returns the new standalone server( you can close this server by the returning reference) -// -// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions. -// -// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. -func SecondaryListen(cfg config.Server) *Server { - return Default.SecondaryListen(cfg) -} - -// SecondaryListen NOTE: This will be deprecated -// Use .Servers.Add(config.Server) instead -// -// 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 { - 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 -// initializes the whole framework but server doesn't listens to a specific net.Listener -// it is not blocking the app -// same as ListenVirtual -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 -// it is not blocking the app -// same as ListenVirtual -func (s *Framework) NoListen(optionalAddr ...string) *Server { - return s.ListenVirtual(optionalAddr...) + s.Must(ListenTo(config.Server{ListeningAddr: addr, Mode: mode})) } // ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it @@ -540,28 +465,20 @@ func (s *Framework) ListenVirtual(optionalAddr ...string) *Server { return s.Servers.Main() } -// CloseWithErr terminates all the registered servers and returns an error if any -func CloseWithErr() error { - return Default.CloseWithErr() +// Close terminates all the registered servers and returns an error if any +// if you want to panic on this error use the iris.Must(iris.Close()) +func Close() error { + return Default.Close() } -//Close terminates all the registered servers and panic if error occurs -func Close() { - Default.Close() -} - -// CloseWithErr terminates all the registered servers and returns an error if any -func (s *Framework) CloseWithErr() error { +// Close terminates all the registered servers and returns an error if any +// if you want to panic on this error use the iris.Must(iris.Close()) +func (s *Framework) Close() error { s.Plugins.DoPreClose(s) s.Available = make(chan bool) return s.Servers.CloseAll() } -//Close terminates all the registered servers and panic if error occurs -func (s *Framework) Close() { - s.Must(s.CloseWithErr()) -} - // MustUse registers Handler middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains diff --git a/plugin_test.go b/plugin_test.go index b22daf02..67906e3e 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -74,7 +74,7 @@ func ExamplePlugins_Add() { fmt.Println(desc) ListenVirtual() - CloseWithErr() + Close() // Output: // GetName Struct From 871b43cae745ece2bb1d6a5b4970eaf369a0536e Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 7 Jul 2016 02:43:33 +0200 Subject: [PATCH 10/10] Update history --- HISTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 2689307b..699bd9cb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,6 +18,8 @@ - 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