diff --git a/adaptors/httprouter/httprouter.go b/adaptors/httprouter/httprouter.go index bb030490..78117a87 100644 --- a/adaptors/httprouter/httprouter.go +++ b/adaptors/httprouter/httprouter.go @@ -625,26 +625,21 @@ func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler { } if mux.hosts && tree.subdomain != "" { - // context.VirtualHost() is a slow method because it makes - // string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost - requestHost := context.VirtualHostname() + + requestHost := context.Host() hostname := context.Framework().Config.VHost + // println("mux are true and tree.subdomain= " + tree.subdomain + "and hostname = " + hostname + " host = " + requestHost) if requestHost != hostname { - //println(requestHost + " != " + mux.hostname) // we have a subdomain if strings.Contains(tree.subdomain, iris.DynamicSubdomainIndicator) { } else { - //println(requestHost + " = " + mux.hostname) - // mux.host = iris-go.com:8080, the subdomain for example is api., - // so the host must be api.iris-go.com:8080 if tree.subdomain+hostname != requestHost { // go to the next tree, we have a subdomain but it is not the correct continue } - } } else { - //("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain) + //("it's subdomain but the request is not the same as the vhost) continue } } diff --git a/configuration_test.go b/configuration_test.go index 85593ec0..aa332bcf 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -11,7 +11,7 @@ import ( . "gopkg.in/kataras/iris.v6" ) -// go test -v -run TestConfiguration* +// $ go test -v -run TestConfiguration* func TestConfigurationStatic(t *testing.T) { def := DefaultConfiguration() diff --git a/context.go b/context.go index 99e3e97d..2cbc09b0 100644 --- a/context.go +++ b/context.go @@ -398,7 +398,8 @@ func (ctx *Context) Subdomain() (subdomain string) { return } -// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener +// VirtualHostname returns the hostname that user registers, +// host path maybe differs from the real which is the Host(), which taken from a net.listener func (ctx *Context) VirtualHostname() string { realhost := ctx.Host() hostname := realhost diff --git a/context_test.go b/context_test.go new file mode 100644 index 00000000..c2d8312a --- /dev/null +++ b/context_test.go @@ -0,0 +1,237 @@ +package iris_test + +import ( + "io/ioutil" + "testing" + + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/adaptors/httprouter" + "gopkg.in/kataras/iris.v6/httptest" +) + +// White-box testing * +func TestContextDoNextStop(t *testing.T) { + var context iris.Context + ok := false + afterStop := false + context.Middleware = iris.Middleware{iris.HandlerFunc(func(*iris.Context) { + ok = true + }), iris.HandlerFunc(func(*iris.Context) { + ok = true + }), iris.HandlerFunc(func(*iris.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 != 255 { + t.Fatalf("Context's StopExecution didn't worked, we expected to have position %d but we got %d", 255, 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") + } +} + +type pathParameter struct { + Key string + Value string +} +type pathParameters []pathParameter + +// White-box testing * +func TestContextParams(t *testing.T) { + context := &iris.Context{} + params := pathParameters{ + pathParameter{Key: "testkey", Value: "testvalue"}, + pathParameter{Key: "testkey2", Value: "testvalue2"}, + pathParameter{Key: "id", Value: "3"}, + pathParameter{Key: "bigint", Value: "548921854390354"}, + } + for _, p := range params { + context.Set(p.Key, p.Value) + } + + 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 context.ParamsLen() != len(params) { + t.Fatalf("Expecting to have %d parameters but we got %d", len(params), context.ParamsLen()) + } + + if vi, err := context.ParamInt(params[2].Key); err != nil { + t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value) + } else if vi != 3 { + t.Fatalf("Expecting to receive %d but we got %d", 3, vi) + } + + if vi, err := context.ParamInt64(params[3].Key); err != nil { + t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value) + } else if vi != 548921854390354 { + t.Fatalf("Expecting to receive %d but we got %d", 548921854390354, vi) + } + + // end-to-end test now, note that we will not test the whole mux here, this happens on http_test.go + + app := iris.New() + app.Adapt(httprouter.New()) + expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like" + app.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) { + paramsStr := ctx.ParamsSentence() + ctx.WriteString(paramsStr) + }) + + httptest.New(app, t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(iris.StatusOK).Body().Equal(expectedParamsStr) + +} + +func TestContextURLParams(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + passedParams := map[string]string{"param1": "value1", "param2": "value2"} + app.Get("/", func(ctx *iris.Context) { + params := ctx.URLParams() + ctx.JSON(iris.StatusOK, params) + }) + e := httptest.New(app, t) + + e.GET("/").WithQueryObject(passedParams).Expect().Status(iris.StatusOK).JSON().Equal(passedParams) +} + +// hoststring returns the full host, will return the HOST:IP +func TestContextHostString(t *testing.T) { + app := iris.New(iris.Configuration{VHost: "0.0.0.0:8080"}) + app.Adapt(newTestNativeRouter()) + + app.Get("/", func(ctx *iris.Context) { + ctx.WriteString(ctx.Host()) + }) + + app.Get("/wrong", func(ctx *iris.Context) { + ctx.WriteString(ctx.Host() + "w") + }) + + e := httptest.New(app, t) + e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(app.Config.VHost) + e.GET("/wrong").Expect().Body().NotEqual(app.Config.VHost) +} + +// 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) { + vhost := "mycustomvirtualname.com" + app := iris.New(iris.Configuration{VHost: vhost + ":8080"}) + app.Adapt(newTestNativeRouter()) + + app.Get("/", func(ctx *iris.Context) { + ctx.WriteString(ctx.VirtualHostname()) + }) + + app.Get("/wrong", func(ctx *iris.Context) { + ctx.WriteString(ctx.VirtualHostname() + "w") + }) + + e := httptest.New(app, t) + e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(vhost) + e.GET("/wrong").Expect().Body().NotEqual(vhost) +} + +func TestContextFormValueString(t *testing.T) { + app := iris.New() + app.Adapt(httprouter.New()) + var k, v string + k = "postkey" + v = "postvalue" + app.Post("/", func(ctx *iris.Context) { + ctx.WriteString(k + "=" + ctx.FormValue(k)) + }) + e := httptest.New(app, t) + + e.POST("/").WithFormField(k, v).Expect().Status(iris.StatusOK).Body().Equal(k + "=" + v) +} + +func TestContextSubdomain(t *testing.T) { + app := iris.New(iris.Configuration{VHost: "mydomain.com:9999"}) + app.Adapt(httprouter.New()) + + //Default.Config.Tester.ListeningAddr = "mydomain.com:9999" + // Default.Config.Tester.ExplicitURL = true + app.Party("mysubdomain.").Get("/mypath", func(ctx *iris.Context) { + ctx.WriteString(ctx.Subdomain()) + }) + + e := httptest.New(app, t) + + e.GET("/").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(iris.StatusNotFound) + e.GET("/mypath").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(iris.StatusOK).Body().Equal("mysubdomain") + + // e.GET("http://mysubdomain.mydomain.com:9999").Expect().Status(iris.StatusNotFound) + // e.GET("http://mysubdomain.mydomain.com:9999/mypath").Expect().Status(iris.StatusOK).Body().Equal("mysubdomain") +} + +func TestLimitRequestBodySizeMiddleware(t *testing.T) { + const maxBodySize = 1 << 20 + + app := iris.New() + app.Adapt(newTestNativeRouter()) + // or inside handler: ctx.SetMaxRequestBodySize(int64(maxBodySize)) + app.Use(iris.LimitRequestBodySize(maxBodySize)) + + app.Post("/", func(ctx *iris.Context) { + b, err := ioutil.ReadAll(ctx.Request.Body) + if len(b) > maxBodySize { + // this is a fatal error it should never happened. + t.Fatalf("body is larger (%d) than maxBodySize (%d) even if we add the LimitRequestBodySize middleware", len(b), maxBodySize) + } + // if is larger then send a bad request status + if err != nil { + ctx.WriteHeader(iris.StatusBadRequest) + ctx.Writef(err.Error()) + return + } + + ctx.Write(b) + }) + + // UseGlobal should be called at the end used to prepend handlers + // app.UseGlobal(iris.LimitRequestBodySize(int64(maxBodySize))) + + e := httptest.New(app, t) + + // test with small body + e.POST("/").WithBytes([]byte("ok")).Expect().Status(iris.StatusOK).Body().Equal("ok") + // test with equal to max body size limit + bsent := make([]byte, maxBodySize, maxBodySize) + e.POST("/").WithBytes(bsent).Expect().Status(iris.StatusOK).Body().Length().Equal(len(bsent)) + // test with larger body sent and wait for the custom response + largerBSent := make([]byte, maxBodySize+1, maxBodySize+1) + e.POST("/").WithBytes(largerBSent).Expect().Status(iris.StatusBadRequest).Body().Equal("http: request body too large") + +} diff --git a/router_policy_test.go b/policy_test.go similarity index 86% rename from router_policy_test.go rename to policy_test.go index 0f2d04df..9162a150 100644 --- a/router_policy_test.go +++ b/policy_test.go @@ -62,13 +62,7 @@ func newTestNativeRouter() Policies { RouterBuilderPolicy: func(repo RouteRepository, context ContextPool) http.Handler { servemux := http.NewServeMux() noIndexRegistered := true - servemux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if noIndexRegistered { - context.Run(w, r, func(ctx *Context) { - ctx.EmitError(StatusNotFound) - }) - } - }) + repo.Visit(func(route RouteInfo) { path := route.Path() if path == "/" { @@ -86,15 +80,6 @@ func newTestNativeRouter() Policies { recorder := ctx.Recorder() ctx.Do() - // ok, we can't bypass the net/http server.go's err handlers - // we have two options: - // - create the mux by ourselve, not an ideal because we already done two of them. - // - create a new response writer which will check once if user has registered error handler,if yes write that response instead. - // - on "/" path(which net/http fallbacks if no any registered route handler found) make if requested_path != "/" or "" - // and emit the 404 error, but for the rest of the custom errors...? - // - use our custom context's recorder to record the status code, this will be a bit slower solution(maybe not) - // but it covers all our scenarios. - statusCode := recorder.StatusCode() if statusCode >= 400 { // if we have an error status code try to find a custom error handler errorHandler := ctx.Framework().Errors.Get(statusCode) @@ -113,6 +98,21 @@ func newTestNativeRouter() Policies { }) }) + // ok, we can't bypass the net/http server.go's err handlers + // we have two options: + // - create the mux by ourselve, not an ideal because we already done two of them. + // - create a new response writer which will check once if user has registered error handler,if yes write that response instead. + // - on "/" path(which net/http fallbacks if no any registered route handler found) make if requested_path != "/" or "" + // and emit the 404 error, but for the rest of the custom errors...? + // - use our custom context's recorder to record the status code, this will be a bit slower solution(maybe not) + // but it covers all our scenarios. + if noIndexRegistered { + servemux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + context.Run(w, r, func(ctx *Context) { + ctx.EmitError(StatusNotFound) + }) + }) + } return servemux }, } diff --git a/response_writer_test.go b/response_writer_test.go new file mode 100644 index 00000000..e2a4cd85 --- /dev/null +++ b/response_writer_test.go @@ -0,0 +1,163 @@ +package iris_test + +import ( + "fmt" + "testing" + + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/httptest" +) + +// most tests lives inside context_test.go:Transactions, there lives the response writer's full and coblex tests +func TestResponseWriterBeforeFlush(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + body := "my body" + beforeFlushBody := "body appeneded or setted before callback" + + app.Get("/", func(ctx *iris.Context) { + w := ctx.ResponseWriter + + w.SetBeforeFlush(func() { + w.WriteString(beforeFlushBody) + }) + + w.WriteString(body) + }) + + // recorder can change the status code after write too + // it can also be changed everywhere inside the context's lifetime + app.Get("/recorder", func(ctx *iris.Context) { + w := ctx.Recorder() + + w.SetBeforeFlush(func() { + w.SetBodyString(beforeFlushBody) + w.WriteHeader(iris.StatusForbidden) + }) + + w.WriteHeader(iris.StatusOK) + w.WriteString(body) + }) + + e := httptest.New(app, t) + + e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(body + beforeFlushBody) + e.GET("/recorder").Expect().Status(iris.StatusForbidden).Body().Equal(beforeFlushBody) +} + +func TestResponseWriterToRecorderMiddleware(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + beforeFlushBody := "body appeneded or setted before callback" + app.UseGlobal(iris.Recorder) + + app.Get("/", func(ctx *iris.Context) { + w := ctx.Recorder() + + w.SetBeforeFlush(func() { + w.SetBodyString(beforeFlushBody) + w.WriteHeader(iris.StatusForbidden) + }) + + w.WriteHeader(iris.StatusOK) + w.WriteString("this will not be sent at all because of SetBodyString") + }) + + e := httptest.New(app, t) + + e.GET("/").Expect().Status(iris.StatusForbidden).Body().Equal(beforeFlushBody) +} + +func TestResponseRecorderStatusCodeContentTypeBody(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + firstStatusCode := iris.StatusOK + contentType := "text/html; charset=" + app.Config.Charset + firstBodyPart := "first" + secondBodyPart := "second" + prependedBody := "zero" + expectedBody := prependedBody + firstBodyPart + secondBodyPart + + app.Use(iris.Recorder) + // recorder's status code can change if needed by a middleware or the last handler. + app.UseFunc(func(ctx *iris.Context) { + ctx.SetStatusCode(firstStatusCode) + ctx.Next() + }) + + app.UseFunc(func(ctx *iris.Context) { + ctx.SetContentType(contentType) + ctx.Next() + }) + + app.UseFunc(func(ctx *iris.Context) { + // set a body ( we will append it later, only with response recorder we can set append or remove a body or a part of it*) + ctx.WriteString(firstBodyPart) + ctx.Next() + }) + + app.UseFunc(func(ctx *iris.Context) { + ctx.WriteString(secondBodyPart) + ctx.Next() + }) + + app.Get("/", func(ctx *iris.Context) { + previousStatusCode := ctx.StatusCode() + if previousStatusCode != firstStatusCode { + t.Fatalf("Previous status code should be %d but got %d", firstStatusCode, previousStatusCode) + } + + previousContentType := ctx.ContentType() + if previousContentType != contentType { + t.Fatalf("First content type should be %s but got %d", contentType, previousContentType) + } + // change the status code, this will tested later on (httptest) + ctx.SetStatusCode(iris.StatusForbidden) + prevBody := string(ctx.Recorder().Body()) + if prevBody != firstBodyPart+secondBodyPart { + t.Fatalf("Previous body (first handler + second handler's writes) expected to be: %s but got: %s", firstBodyPart+secondBodyPart, prevBody) + } + // test it on httptest later on + ctx.Recorder().SetBodyString(prependedBody + prevBody) + }) + + e := httptest.New(app, t) + + et := e.GET("/").Expect().Status(iris.StatusForbidden) + et.Header("Content-Type").Equal(contentType) + et.Body().Equal(expectedBody) +} + +func ExampleResponseWriter_WriteHeader() { + app := iris.New(iris.OptionDisableBanner(true)) + app.Adapt(newTestNativeRouter()) + + expectedOutput := "Hey" + app.Get("/", func(ctx *iris.Context) { + + // here + for i := 0; i < 10; i++ { + ctx.ResponseWriter.WriteHeader(iris.StatusOK) + } + + ctx.Writef(expectedOutput) + + // here + fmt.Println(expectedOutput) + + // here + for i := 0; i < 10; i++ { + ctx.SetStatusCode(iris.StatusOK) + } + }) + + e := httptest.New(app, nil) + e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedOutput) + // here it shouldn't log an error that status code write multiple times (by the net/http package.) + + // Output: + // Hey +} diff --git a/transaction_test.go b/transaction_test.go new file mode 100644 index 00000000..86fef282 --- /dev/null +++ b/transaction_test.go @@ -0,0 +1,122 @@ +package iris_test + +import ( + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/httptest" + + "testing" +) + +func TestTransaction(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + firstTransactionFailureMessage := "Error: Virtual failure!!!" + secondTransactionSuccessHTMLMessage := "