From 2a4997cadf502ccfd04fbfee32701182a22f479c Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 17 Feb 2017 02:55:26 +0200 Subject: [PATCH] Add listen and proxy tests Former-commit-id: 7c89556942af8e9c6966022323cb9746db446cfc --- HISTORY.md | 17 ++-- addr.go | 4 +- addr_test.go | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++ iris.go | 84 ++++++++++++----- 4 files changed, 324 insertions(+), 31 deletions(-) create mode 100644 addr_test.go diff --git a/HISTORY.md b/HISTORY.md index 5ddf0a2f..2964da80 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -746,23 +746,24 @@ This pattern allows us to be very pluggable and add features that the *Framework it knows only the main policies which implement but their features are our(as users) business. -We have 7 policies,so far, and some of them have 'subpolicies' (the RouterReversionPolicy for example). +We have 8 policies, so far, and some of them have 'subpolicies' (the RouterReversionPolicy for example). -- LoggerPolicy -- EventPolicy +- LoggerPolicy +- EventPolicy - Boot - Build - Interrupted - Recover -- RouterReversionPolicy +- RouterReversionPolicy - StaticPath - WildcardPath - URLPath - RouteContextLinker -- RouterBuilderPolicy -- RouterWrapperPolicy -- RenderPolicy -- TemplateFuncsPolicy +- RouterBuilderPolicy +- RouterWrapperPolicy +- RenderPolicy +- TemplateFuncsPolicy +- SessionsPolicy **Details** of these can be found at [policy.go](https://github.com/kataras/iris/blob/master/policy.go). diff --git a/addr.go b/addr.go index dc2361e7..8432a6af 100644 --- a/addr.go +++ b/addr.go @@ -288,12 +288,14 @@ func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error { // override the handler and redirect all requests to this addr h := ProxyHandler(redirectSchemeAndHost) prx := New(OptionDisableBanner(true)) + prx.Adapt(DevLogger()) + prx.Adapt(RouterBuilderPolicy(func(RouteRepository, ContextPool) http.Handler { return h })) go prx.Listen(proxyAddr) - time.Sleep(300 * time.Millisecond) + time.Sleep(150 * time.Millisecond) return func() error { return prx.Close() } } diff --git a/addr_test.go b/addr_test.go new file mode 100644 index 00000000..a542e25b --- /dev/null +++ b/addr_test.go @@ -0,0 +1,250 @@ +// Black-box Testing +package iris_test + +import ( + "io/ioutil" + "math/rand" + "net/http" + "os" + "strconv" + "testing" + "time" + + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/httptest" +) + +func TestParseAddr(t *testing.T) { + + // test hosts + expectedHost1 := "mydomain.com:1993" + expectedHost2 := "mydomain.com" + expectedHost3 := iris.DefaultServerHostname + ":9090" + expectedHost4 := "mydomain.com:443" + + host1 := iris.ParseHost(expectedHost1) + host2 := iris.ParseHost(expectedHost2) + host3 := iris.ParseHost(":9090") + host4 := iris.ParseHost(expectedHost4) + + if host1 != expectedHost1 { + t.Fatalf("Expecting server 1's host to be %s but we got %s", expectedHost1, host1) + } + if host2 != expectedHost2 { + t.Fatalf("Expecting server 2's host to be %s but we got %s", expectedHost2, host2) + } + if host3 != expectedHost3 { + t.Fatalf("Expecting server 3's host to be %s but we got %s", expectedHost3, host3) + } + if host4 != expectedHost4 { + t.Fatalf("Expecting server 4's host to be %s but we got %s", expectedHost4, host4) + } + + // test hostname + expectedHostname1 := "mydomain.com" + expectedHostname2 := "mydomain.com" + expectedHostname3 := iris.DefaultServerHostname + expectedHostname4 := "mydomain.com" + + hostname1 := iris.ParseHostname(host1) + hostname2 := iris.ParseHostname(host2) + hostname3 := iris.ParseHostname(host3) + hostname4 := iris.ParseHostname(host4) + if hostname1 != expectedHostname1 { + t.Fatalf("Expecting server 1's hostname to be %s but we got %s", expectedHostname1, hostname1) + } + + if hostname2 != expectedHostname2 { + t.Fatalf("Expecting server 2's hostname to be %s but we got %s", expectedHostname2, hostname2) + } + + if hostname3 != expectedHostname3 { + t.Fatalf("Expecting server 3's hostname to be %s but we got %s", expectedHostname3, hostname3) + } + + if hostname4 != expectedHostname4 { + t.Fatalf("Expecting server 4's hostname to be %s but we got %s", expectedHostname4, hostname4) + } + + // test scheme, no need to test fullhost(scheme+host) + expectedScheme1 := iris.SchemeHTTP + expectedScheme2 := iris.SchemeHTTP + expectedScheme3 := iris.SchemeHTTP + expectedScheme4 := iris.SchemeHTTPS + scheme1 := iris.ParseScheme(host1) + scheme2 := iris.ParseScheme(host2) + scheme3 := iris.ParseScheme(host3) + scheme4 := iris.ParseScheme(host4) + if scheme1 != expectedScheme1 { + t.Fatalf("Expecting server 1's hostname to be %s but we got %s", expectedScheme1, scheme1) + } + + if scheme2 != expectedScheme2 { + t.Fatalf("Expecting server 2's hostname to be %s but we got %s", expectedScheme2, scheme2) + } + + if scheme3 != expectedScheme3 { + t.Fatalf("Expecting server 3's hostname to be %s but we got %s", expectedScheme3, scheme3) + } + + if scheme4 != expectedScheme4 { + t.Fatalf("Expecting server 4's hostname to be %s but we got %s", expectedScheme4, scheme4) + } +} + +const ( + testTLSCert = `-----BEGIN CERTIFICATE----- +MIIDAzCCAeugAwIBAgIJAP0pWSuIYyQCMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV +BAMMDWxvY2FsaG9zdDozMzEwHhcNMTYxMjI1MDk1OTI3WhcNMjYxMjIzMDk1OTI3 +WjAYMRYwFAYDVQQDDA1sb2NhbGhvc3Q6MzMxMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm +mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe +tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz +3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD +sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu +PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABo1AwTjAdBgNVHQ4EFgQU +MXrBvbILQmiwjUj19aecF2N+6IkwHwYDVR0jBBgwFoAUMXrBvbILQmiwjUj19aec +F2N+6IkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA4zbFml1t9KXJ +OijAV8gALePR8v04DQwJP+jsRxXw5zzhc8Wqzdd2hjUd07mfRWAvmyywrmhCV6zq +OHznR+aqIqHtm0vV8OpKxLoIQXavfBd6axEXt3859RDM4xJNwIlxs3+LWGPgINud +wjJqjyzSlhJpQpx4YZ5Da+VMiqAp8N1UeaZ5lBvmSDvoGh6HLODSqtPlWMrldRW9 +AfsXVxenq81MIMeKW2fSOoPnWZ4Vjf1+dSlbJE/DD4zzcfbyfgY6Ep/RrUltJ3ag +FQbuNTQlgKabe21dSL9zJ2PengVKXl4Trl+4t/Kina9N9Jw535IRCSwinD6a/2Ca +m7DnVXFiVA== +-----END CERTIFICATE----- +` + + testTLSKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm +mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe +tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz +3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD +sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu +PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABAoIBAQCTLE0eHpPevtg0 ++FaRUMd5diVA5asoF3aBIjZXaU47bY0G+SO02x6wSMmDFK83a4Vpy/7B3Bp0jhF5 +DLCUyKaLdmE/EjLwSUq37ty+JHFizd7QtNBCGSN6URfpmSabHpCjX3uVQqblHIhF +mki3BQCdJ5CoXPemxUCHjDgYSZb6JVNIPJExjekc0+4A2MYWMXV6Wr86C7AY3659 +KmveZpC3gOkLA/g/IqDQL/QgTq7/3eloHaO+uPBihdF56do4eaOO0jgFYpl8V7ek +PZhHfhuPZV3oq15+8Vt77ngtjUWVI6qX0E3ilh+V5cof+03q0FzHPVe3zBUNXcm0 +OGz19u/FAoGBAPSm4Aa4xs/ybyjQakMNix9rak66ehzGkmlfeK5yuQ/fHmTg8Ac+ +ahGs6A3lFWQiyU6hqm6Qp0iKuxuDh35DJGCWAw5OUS/7WLJtu8fNFch6iIG29rFs +s+Uz2YLxJPebpBsKymZUp7NyDRgEElkiqsREmbYjLrc8uNKkDy+k14YnAoGBAPGn +ZlN0Mo5iNgQStulYEP5pI7WOOax9KOYVnBNguqgY9c7fXVXBxChoxt5ebQJWG45y +KPG0hB0bkA4YPu4bTRf5acIMpjFwcxNlmwdc4oCkT4xqAFs9B/AKYZgkf4IfKHqW +P9PD7TbUpkaxv25bPYwUSEB7lPa+hBtRyN9Wo6qfAoGAPBkeISiU1hJE0i7YW55h +FZfKZoqSYq043B+ywo+1/Dsf+UH0VKM1ZSAnZPpoVc/hyaoW9tAb98r0iZ620wJl +VkCjgYklknbY5APmw/8SIcxP6iVq1kzQqDYjcXIRVa3rEyWEcLzM8VzL8KFXbIQC +lPIRHFfqKuMEt+HLRTXmJ7MCgYAHGvv4QjdmVl7uObqlG9DMGj1RjlAF0VxNf58q +NrLmVG2N2qV86wigg4wtZ6te4TdINfUcPkmQLYpLz8yx5Z2bsdq5OPP+CidoD5nC +WqnSTIKGR2uhQycjmLqL5a7WHaJsEFTqHh2wego1k+5kCUzC/KmvM7MKmkl6ICp+ +3qZLUwKBgQCDOhKDwYo1hdiXoOOQqg/LZmpWOqjO3b4p99B9iJqhmXN0GKXIPSBh +5nqqmGsG8asSQhchs7EPMh8B80KbrDTeidWskZuUoQV27Al1UEmL6Zcl83qXD6sf +k9X9TwWyZtp5IL1CAEd/Il9ZTXFzr3lNaN8LCFnU+EIsz1YgUW8LTg== +-----END RSA PRIVATE KEY----- +` +) + +func getRandomNumber(min int, max int) int { + rand.Seed(time.Now().Unix()) + return rand.Intn(max-min) + min +} + +// works as +// defer listenTLS(iris.Default, hostTLS)() +func listenTLS(app *iris.Framework, hostTLS string) func() { + app.Close() // close any prev listener + app.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 { + panic(ferr) + } + + keyFile, ferr := ioutil.TempFile("", "key") + if ferr != nil { + panic(ferr) + } + + certFile.WriteString(testTLSCert) + keyFile.WriteString(testTLSKey) + + go app.ListenTLS(hostTLS, certFile.Name(), keyFile.Name()) + time.Sleep(200 * time.Millisecond) + + return func() { + certFile.Close() + time.Sleep(50 * time.Millisecond) + os.Remove(certFile.Name()) + + keyFile.Close() + time.Sleep(50 * time.Millisecond) + os.Remove(keyFile.Name()) + } +} + +// Contains the server test for multi running servers +func TestMultiRunningServers_v1_PROXY(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + host := "localhost" + hostTLS := host + ":" + strconv.Itoa(getRandomNumber(1919, 2021)) + app.Get("/", func(ctx *iris.Context) { + ctx.Writef("Hello from %s", hostTLS) + }) + + defer listenTLS(app, hostTLS)() + + e := httptest.New(app, t, httptest.ExplicitURL(true)) + e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS) + + // proxy http to https + proxyHost := host + ":" + strconv.Itoa(getRandomNumber(3300, 3340)) + // println("running proxy on: " + proxyHost) + + iris.Proxy(proxyHost, "https://"+hostTLS) + + // proxySrv := &http.Server{Addr: proxyHost, Handler: iris.ProxyHandler("https://" + hostTLS)} + // go proxySrv.ListenAndServe() + // time.Sleep(3 * time.Second) + + eproxy := httptest.NewInsecure("http://"+proxyHost, t, httptest.ExplicitURL(true)) + eproxy.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS) +} + +// Contains the server test for multi running servers +func TestMultiRunningServers_v2(t *testing.T) { + app := iris.New() + app.Adapt(newTestNativeRouter()) + + domain := "localhost" + hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(2222, 2229)) + srv1Host := domain + ":" + strconv.Itoa(getRandomNumber(4446, 5444)) + srv2Host := domain + ":" + strconv.Itoa(getRandomNumber(7778, 8887)) + + app.Get("/", func(ctx *iris.Context) { + ctx.Writef("Hello from %s", hostTLS) + }) + + defer listenTLS(app, hostTLS)() + + // using the same iris' handler but not as proxy, just the same handler + srv2 := &http.Server{Handler: app.Router, Addr: srv2Host} + go srv2.ListenAndServe() + + // using the proxy handler + srv1 := &http.Server{Handler: iris.ProxyHandler("https://" + hostTLS), Addr: srv1Host} + go srv1.ListenAndServe() + time.Sleep(200 * time.Millisecond) // wait a little for the http servers + + e := httptest.New(app, t, httptest.ExplicitURL(true)) + e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS) + + eproxy1 := httptest.NewInsecure("http://"+srv1Host, t, httptest.ExplicitURL(true)) + eproxy1.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS) + + eproxy2 := httptest.NewInsecure("http://"+srv2Host, t) + eproxy2.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS) + +} diff --git a/iris.go b/iris.go index bd77347c..11f80d30 100644 --- a/iris.go +++ b/iris.go @@ -62,12 +62,43 @@ func init() { // Framework is our God |\| Google.Search('Greek mythology Iris'). type Framework struct { + // Router is the Router API, REST Routing, Static files & favicon, + // Grouping, Custom HTTP Errors, Subdomains and more. + // + // This field is available before 'Boot' but the routes are actually registered after 'Boot' + // if no RouterBuilderPolicy was .Adapt(ed) by user then + // it throws a panic with detailed information of how-to-fix it. *Router + + // Config contains the configuration fields + // all fields defaults to something that is working, developers don't have to set it. + // + // can be setted via .New, .Set and .New(.YAML) + Config *Configuration + + // policies contains the necessary information about the application's components. + // - LoggerPolicy + // - EventPolicy + // - Boot + // - Build + // - Interrupted + // - Recover + // - RouterReversionPolicy + // - StaticPath + // - WildcardPath + // - URLPath + // - RouteContextLinker + // - RouterBuilderPolicy + // - RouterWrapperPolicy + // - RenderPolicy + // - TemplateFuncsPolicy + // - SessionsPolicy + // + // These are setted by user's call to .Adapt policies Policies - // HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers - // note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX - ln net.Listener + ln net.Listener // setted on Listten/Serve funcions, available after 'Boot' + // TLSNextProto optionally specifies a function to take over // ownership of the provided TLS connection when an NPN/ALPN // protocol upgrade has occurred. The map key is the protocol @@ -76,16 +107,16 @@ type Framework struct { // and RemoteAddr if not already set. The connection is // automatically closed when the function returns. // If TLSNextProto is nil, HTTP/2 support is enabled automatically. - TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler) + TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler) // same as http.Server.TLSNextProto + // ConnState specifies an optional callback function that is // called when a client connection changes state. See the // ConnState type and associated constants for details. - ConnState func(net.Conn, http.ConnState) + ConnState func(net.Conn, http.ConnState) // same as http.Server.ConnState - closedManually bool + closedManually bool // true if closed via .Close, used to not throw an error when closing the app's server - once sync.Once - Config *Configuration + once sync.Once // used to 'Boot' once } var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags) @@ -269,27 +300,36 @@ func New(setters ...OptionSetter) *Framework { } s.Adapt(EventPolicy{Build: func(*Framework) { - // user has registered routes - if s.Router.repository.Len() > 0 { - // first check if it's not setted already by any Boot event. - if s.Router.handler == nil { - // and most importantly, check if the user has provided a router - // adaptor, if not then it should panic here, iris can't run without a router attached to it - // and default router not any more, user should select one from ./adaptors or - // any other third-party adaptor may done by community. - // I was coding the new iris version for more than 20 days(~200+ hours of code) - // and I hope that once per application the addition of +1 line users have to put, - // is not a big deal. - if s.policies.RouterBuilderPolicy == nil { + // Author's notes: + // Proxy for example has 0 routes registered but still uses the RouterBuilderPolicy + // so we can't check only for it, we can check if it's nil and it has more than one registered + // routes, then panic, if has no registered routes the user don't want to get errors about the router. + + // first check if it's not setted already by any Boot event. + if s.Router.handler == nil { + hasRoutes := s.Router.repository.Len() > 0 + routerBuilder := s.policies.RouterBuilderPolicy + // and most importantly, check if the user has provided a router adaptor + // at the same time has registered at least one route, + // if not then it should panic here, iris can't run without a router attached to it + // and default router not any more, user should select one from ./adaptors or + // any other third-party adaptor may done by community. + // I was coding the new iris version for more than 20 days(~200+ hours of code) + // and I hope that once per application the addition of +1 line users have to put, + // is not a big deal. + if hasRoutes { + if routerBuilder == nil { // this is important panic and app can't continue as we said. s.handlePanic(errRouterIsMissing.Format(s.Config.VHost)) // don't trace anything else, // the detailed errRouterIsMissing message will tell the user what to do to fix that. os.Exit(0) - } + } + + if routerBuilder != nil { // buid the router using user's selection build policy - s.Router.build(s.policies.RouterBuilderPolicy) + s.Router.build(routerBuilder) } } }})