apps.Switch(apps.Hosts...) example

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-18 04:17:53 +03:00
parent 589c8c6242
commit 5481b9a6c1
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
19 changed files with 164 additions and 114 deletions

View File

@ -359,6 +359,10 @@ Response:
Other Improvements: Other Improvements:
- New [apps](https://github.com/kataras/iris/tree/master/apps) subpackage. [Example of usage](https://github.com/kataras/iris/tree/master/_examples/routing/subdomains/redirect/multi-instances).
![apps image example](https://user-images.githubusercontent.com/22900943/90459288-8a54f400-e109-11ea-8dea-20631975c9fc.png)
- Fix `AutoTLS` when used with `iris.TLSNoRedirect` [*](https://github.com/kataras/iris/issues/1577). The `AutoTLS` runner can be customized through the new `iris.AutoTLSNoRedirect` instead, read its go documentation. Example of having both TLS and non-TLS versions of the same application without conflicts with letsencrypt `./well-known` path: - Fix `AutoTLS` when used with `iris.TLSNoRedirect` [*](https://github.com/kataras/iris/issues/1577). The `AutoTLS` runner can be customized through the new `iris.AutoTLSNoRedirect` instead, read its go documentation. Example of having both TLS and non-TLS versions of the same application without conflicts with letsencrypt `./well-known` path:
![](https://iris-go.com/images/github/autotls-1.png) ![](https://iris-go.com/images/github/autotls-1.png)

View File

@ -4,5 +4,5 @@ go 1.15
require ( require (
github.com/betacraft/yaag v1.0.1-0.20200719063524-47d781406108 github.com/betacraft/yaag v1.0.1-0.20200719063524-47d781406108
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
) )

View File

@ -4,6 +4,6 @@ go 1.15
require ( require (
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
go.mongodb.org/mongo-driver v1.3.4 go.mongodb.org/mongo-driver v1.3.4
) )

View File

@ -4,6 +4,6 @@ go 1.15
require ( require (
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
github.com/mailgun/groupcache/v2 v2.1.0 github.com/mailgun/groupcache/v2 v2.1.0
) )

View File

@ -4,5 +4,5 @@ go 1.15
require ( require (
github.com/Shopify/sarama v1.26.4 github.com/Shopify/sarama v1.26.4
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
) )

View File

@ -3,6 +3,6 @@ module github.com/kataras/iris/examples/logging/rollbar
go 1.15 go 1.15
require ( require (
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
github.com/rollbar/rollbar-go v1.2.0 github.com/rollbar/rollbar-go v1.2.0
) )

View File

@ -2,4 +2,4 @@ module app
go 1.15 go 1.15
require github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd require github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce

View File

@ -1,4 +1,5 @@
127.0.0.1 mydomain.com 127.0.0.1 mydomain.com
127.0.0.1 www.mydomain.com 127.0.0.1 www.mydomain.com
127.0.0.1 otherdomain.com
# Windows: Drive:/Windows/system32/drivers/etc/hosts, on Linux: /etc/hosts # Windows: Drive:/Windows/system32/drivers/etc/hosts, on Linux: /etc/hosts

View File

@ -1,30 +0,0 @@
package main
import (
"fmt"
"strings"
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestSubdomainRedirectWWW(t *testing.T) {
app := newApp()
root := strings.TrimSuffix(addr, ":80")
e := httptest.New(t, app)
tests := []struct {
path string
response string
}{
{"/", fmt.Sprintf("This is the www.%s endpoint.", root)},
{"/users", fmt.Sprintf("This is the www.%s/users endpoint.", root)},
{"/users/login", fmt.Sprintf("This is the www.%s/users/login endpoint.", root)},
}
for _, test := range tests {
e.GET(test.path).Expect().Status(httptest.StatusOK).Body().Equal(test.response)
e.GET(test.path).WithURL("www.mydomain.com").Expect().Status(httptest.StatusOK).Body().Equal(test.response)
}
}

View File

@ -0,0 +1,7 @@
module github.com/kataras/iris/_examples/routing/subdomains/redirect/multi-instances
go 1.15
require github.com/kataras/iris/v12 v12.1.9-0.20200817185317-589c8c6242ce
replace github.com/kataras/iris/v12 => ../../../../../

View File

@ -1,82 +1,70 @@
package main package main
import ( import (
"net/http" _ "github.com/kataras/iris/_examples/routing/subdomains/redirect/multi-instances/other"
_ "github.com/kataras/iris/_examples/routing/subdomains/redirect/multi-instances/root"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/apps"
) )
// In this example, you wanna use three different applications exposed as one.
// The first one is the "other" package, the second is the "root",
// the third is the switcher application which will expose the above.
// Unlike the previous example, on this one we will NOT redirect,
// the Hosts switcher will just pass the request to the matched Application to handle.
// This is NOT an alternative of your favorite load balancer.
// Read the comments carefully, if you need more information
// you can head over to the "apps" package's godocs and tests.
func main() { func main() {
app := iris.New() // The `apps.Hosts` switch provider:
// The pattern. A regexp for matching the host part of incoming requests.
hosts := map[string]*iris.Application{ // The target. An iris.Application instance (created by iris.New())
"mydomain.com": createRoot("www.mydomain.com"), // redirects to www. // OR
"www.mydomain.com": createWWW(), // You can use the Application's name (app.SetName("myapp")).
"test.mydomain.com": createTest(), // Example:
} // package rootdomain
for _, r := range hosts { // func init() {
r.Build() // app := iris.New().SetName("root app")
} // ...
// }
app.Downgrade(func(w http.ResponseWriter, r *http.Request) { // On the main package add an empty import statement: ` _ import "rootdomain"`
host := r.Host // And set the "root app" as the key to reference that application (of the same program).
if host == "" { // Thats the target we wanna use now ^ (see ../hosts file).
host = r.URL.Host // OR
} // An external host or a local running in the same machine but different port or host behind proxy.
switcher := apps.Switch(apps.Hosts{
if router, ok := hosts[host]; ok { {"^(www.)?mydomain.com$", "root app"},
router.ServeHTTP(w, r) {"^otherdomain.com$", "other app"},
return
}
http.NotFound(w, r)
}) })
// The registration order matters, so we can register a fallback server (when host no match)
// using "*". However, you have alternatives by using the Switch Iris Application value
// (let's call it "switcher"):
// 1. Handle the not founds, e.g. switcher.OnErrorCode(404, ...)
// 2. Use the switcher.WrapRouter, e.g. to log the flow of a request of all hosts exposed.
// 3. Just register routes to match, e.g. switcher.Get("/", ...)
switcher.Get("/", fallback)
// OR
// Change the response code to 502
// instead of 404 and write a message:
// switcher.OnErrorCode(iris.StatusNotFound, fallback)
app.Listen(":80") // The switcher is a common Iris Application, so you have access to the Iris features.
// And it should be listening to a host:port in order to match and serve its apps.
//
// http://mydomain.com (OK)
// http://www.mydomain.com (OK)
// http://mydomain.com/dsa (404)
// http://no.mydomain.com (502 Bad Host)
//
// http://otherdomain.com (OK)
// http://www.otherdomain.com (502 Bad Host)
// http://otherdomain.com/dsa (404 JSON)
// ...
switcher.Listen(":80")
} }
func createRoot(redirectTo string) *iris.Application { func fallback(ctx iris.Context) {
app := iris.New() ctx.StatusCode(iris.StatusBadGateway)
app.Downgrade(func(w http.ResponseWriter, r *http.Request) { ctx.Writef("Bad Host %s", ctx.Host())
fullScheme := "http://"
if r.TLS != nil {
fullScheme = "https://"
}
http.Redirect(w, r, fullScheme+redirectTo+r.URL.RequestURI(), iris.StatusMovedPermanently)
})
return app
}
func createWWW() *iris.Application {
app := iris.New()
app.Get("/", index)
users := app.Party("/users")
users.Get("/", usersIndex)
users.Get("/login", getLogin)
return app
}
func createTest() *iris.Application {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.WriteString("Test Index")
})
return app
}
func index(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com endpoint.")
}
func usersIndex(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users endpoint.")
}
func getLogin(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users/login endpoint.")
} }

View File

@ -0,0 +1,30 @@
package other
import (
"time"
"github.com/kataras/iris/v12"
)
func init() {
app := iris.New()
app.SetName("other app")
app.OnAnyErrorCode(handleErrors)
app.Get("/", index)
}
func index(ctx iris.Context) {
ctx.HTML("Other Index (App Name: <b>%s</b> | Host: <b>%s</b>)",
ctx.Application().String(), ctx.Host())
}
func handleErrors(ctx iris.Context) {
errCode := ctx.GetStatusCode()
ctx.JSON(iris.Map{
"Server": ctx.Application().String(),
"Code": errCode,
"Message": iris.StatusText(errCode),
"Timestamp": time.Now().Unix(),
})
}

View File

@ -0,0 +1,15 @@
package root
import "github.com/kataras/iris/v12"
func init() {
app := iris.New()
app.SetName("root app")
app.Get("/", index)
}
func index(ctx iris.Context) {
ctx.HTML("Main Root Index (App Name: <b>%s</b> | Host: <b>%s</b>)",
ctx.Application().String(), ctx.Host())
}

View File

@ -2,5 +2,5 @@ module app
go 1.15 go 1.15
require github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd require github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce

View File

@ -4,5 +4,5 @@ go 1.15
require ( require (
github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0 github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0
github.com/kataras/iris/v12 v12.1.9-0.20200812051831-0edf0affb0bd github.com/kataras/iris/v12 vv12.1.9-0.20200817185317-589c8c6242ce
) )

View File

@ -1,6 +1,8 @@
package apps package apps
import ( import (
"strings"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
) )
@ -33,11 +35,18 @@ func Switch(providers ...SwitchProvider) *iris.Application {
panic("iris: switch: empty providers") panic("iris: switch: empty providers")
} }
var friendlyAddrs []string
var cases []SwitchCase var cases []SwitchCase
for _, p := range providers { for _, p := range providers {
for _, c := range p.GetSwitchCases() { for _, c := range p.GetSwitchCases() {
cases = append(cases, c) cases = append(cases, c)
} }
if fp, ok := p.(FriendlyNameProvider); ok {
if friendlyName := fp.GetFriendlyName(); friendlyName != "" {
friendlyAddrs = append(friendlyAddrs, friendlyName)
}
}
} }
if len(cases) == 0 { if len(cases) == 0 {
@ -61,7 +70,7 @@ func Switch(providers ...SwitchProvider) *iris.Application {
app.UseRouter(func(ctx iris.Context) { app.UseRouter(func(ctx iris.Context) {
for _, c := range cases { for _, c := range cases {
if c.Filter(ctx) { if c.Filter(ctx) {
c.App.ServeHTTP(ctx.ResponseWriter().Naive(), ctx.Request()) c.App.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
// if c.App.Downgraded() { // if c.App.Downgraded() {
// c.App.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) // c.App.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
@ -81,6 +90,12 @@ func Switch(providers ...SwitchProvider) *iris.Application {
ctx.Next() ctx.Next()
}) })
// Configure the switcher's supervisor.
app.ConfigureHost(func(su *iris.Supervisor) {
if len(friendlyAddrs) > 0 {
su.FriendlyAddr = strings.Join(friendlyAddrs, ", ")
}
})
return app return app
} }
@ -88,8 +103,8 @@ type (
// SwitchCase contains the filter // SwitchCase contains the filter
// and the matched Application instance. // and the matched Application instance.
SwitchCase struct { SwitchCase struct {
Filter iris.Filter Filter iris.Filter // Filter runs against the Switcher.
App *iris.Application App *iris.Application // App is the main target application responsible to handle the request.
} }
// A SwitchProvider should return the switch cases. // A SwitchProvider should return the switch cases.
@ -100,6 +115,12 @@ type (
GetSwitchCases() []SwitchCase GetSwitchCases() []SwitchCase
} }
// FriendlyNameProvider can be optionally implemented by providers
// to customize the Switcher's Supervisor.FriendlyAddr field (Startup log).
FriendlyNameProvider interface {
GetFriendlyName() string
}
// Join returns a new slice which joins different type of switch cases. // Join returns a new slice which joins different type of switch cases.
Join []SwitchProvider Join []SwitchProvider
) )

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
@ -53,6 +54,18 @@ func (hosts Hosts) GetSwitchCases() []SwitchCase {
return cases return cases
} }
// GetFriendlyName implements the FriendlyNameProvider.
func (hosts Hosts) GetFriendlyName() string {
var patterns []string
for _, host := range hosts {
if strings.TrimSpace(host.Pattern) != "" {
patterns = append(patterns, host.Pattern)
}
}
return strings.Join(patterns, ", ")
}
func hostApp(host Host) *iris.Application { func hostApp(host Host) *iris.Application {
if host.Target == nil { if host.Target == nil {
return nil return nil

View File

@ -30,8 +30,9 @@ type Configurator func(su *Supervisor)
// //
// Interfaces are separated to return relative functionality to them. // Interfaces are separated to return relative functionality to them.
type Supervisor struct { type Supervisor struct {
Server *http.Server Server *http.Server
friendlyAddr string // e.g mydomain.com instead of :443 when AutoTLS is used, see `WriteStartupLogOnServe` task. // FriendlyAddr can be set to customize the "Now Listening on: {FriendlyAddr}".
FriendlyAddr string // e.g mydomain.com instead of :443 when AutoTLS is used, see `WriteStartupLogOnServe` task.
disableHTTP1ToHTTP2Redirection bool disableHTTP1ToHTTP2Redirection bool
closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown) closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown)
closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose. closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose.
@ -350,7 +351,7 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
if strings.TrimSpace(domain) != "" { if strings.TrimSpace(domain) != "" {
domains := strings.Split(domain, " ") domains := strings.Split(domain, " ")
su.friendlyAddr = domains[0] su.FriendlyAddr = strings.Join(domains, ", ")
hostPolicy = autocert.HostWhitelist(domains...) hostPolicy = autocert.HostWhitelist(domains...)
} }

View File

@ -23,7 +23,7 @@ import (
func WriteStartupLogOnServe(w io.Writer) func(TaskHost) { func WriteStartupLogOnServe(w io.Writer) func(TaskHost) {
return func(h TaskHost) { return func(h TaskHost) {
guessScheme := netutil.ResolveScheme(h.Supervisor.manuallyTLS || h.Supervisor.Fallback != nil) guessScheme := netutil.ResolveScheme(h.Supervisor.manuallyTLS || h.Supervisor.Fallback != nil)
addr := h.Supervisor.friendlyAddr addr := h.Supervisor.FriendlyAddr
if addr == "" { if addr == "" {
addr = h.Supervisor.Server.Addr addr = h.Supervisor.Server.Addr
} }