add options feature on apps package and add a SetHost option to force host on multiple subdomains that should mean the same exact application, so any handler should run based on the same Host field

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-19 05:32:21 +03:00
parent bdb94bbae2
commit dddfeb9ca9
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
4 changed files with 141 additions and 19 deletions

View File

@ -30,27 +30,29 @@ import (
// e.g. to inject the 404 on not application found. // e.g. to inject the 404 on not application found.
// It can also be wrapped with its `WrapRouter` method, // It can also be wrapped with its `WrapRouter` method,
// which is really useful for logging and statistics. // which is really useful for logging and statistics.
func Switch(providers ...SwitchProvider) *iris.Application { //
if len(providers) == 0 { // Wrap with the `Join` slice to pass
panic("iris: switch: empty providers") // more than one provider at the same time.
func Switch(provider SwitchProvider, options ...SwitchOption) *iris.Application {
cases := provider.GetSwitchCases()
if len(cases) == 0 {
panic("iris: switch: empty cases")
} }
var friendlyAddrs []string var friendlyAddrs []string
var cases []SwitchCase if fp, ok := provider.(FriendlyNameProvider); ok {
for _, p := range providers { if friendlyName := fp.GetFriendlyName(); friendlyName != "" {
for _, c := range p.GetSwitchCases() { friendlyAddrs = append(friendlyAddrs, friendlyName)
cases = append(cases, c)
}
if fp, ok := p.(FriendlyNameProvider); ok {
if friendlyName := fp.GetFriendlyName(); friendlyName != "" {
friendlyAddrs = append(friendlyAddrs, friendlyName)
}
} }
} }
if len(cases) == 0 { opts := DefaultSwitchOptions()
panic("iris: switch: empty cases") for _, opt := range options {
if opt == nil {
continue
}
opt.Apply(&opts)
} }
app := iris.New() app := iris.New()
@ -70,10 +72,17 @@ 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(), ctx.Request()) w := ctx.ResponseWriter()
r := ctx.Request()
for _, reqMod := range opts.RequestModifiers {
reqMod(r)
}
c.App.ServeHTTP(w, r)
// if c.App.Downgraded() { // if c.App.Downgraded() {
// c.App.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) // c.App.ServeHTTP(w, r)
// } else { // } else {
// Note(@kataras): don't ever try something like that; // Note(@kataras): don't ever try something like that;
// the context pool is the switcher's one. // the context pool is the switcher's one.

View File

@ -119,13 +119,12 @@ func newHostRedirectApp(targetHost string, code int) *iris.Application {
// carefully checks if the expression already matched the "redirectTo" // carefully checks if the expression already matched the "redirectTo"
// to avoid the redirect loops at all. // to avoid the redirect loops at all.
// iris: switch: hosts redirect: loop detected between expression: "^my.*$" and target host: "mydomain.com" // iris: switch: hosts redirect: loop detected between expression: "^my.*$" and target host: "mydomain.com"
http.Error(w, http.StatusText(iris.StatusLoopDetected), iris.StatusLoopDetected) http.Error(w, iris.StatusText(iris.StatusTooManyRequests), iris.StatusTooManyRequests)
return return
} }
r.Host = targetHost r.Host = targetHost
r.URL.Host = targetHost r.URL.Host = targetHost
// r.URL.User = nil // r.URL.User = nil
http.Redirect(w, r, r.URL.String(), code) http.Redirect(w, r, r.URL.String(), code)
}) })

76
apps/switch_options.go Normal file
View File

@ -0,0 +1,76 @@
package apps
import "net/http"
type (
// SwitchOptions holds configuration
// for the switcher application.
SwitchOptions struct {
// RequestModifiers holds functions to run
// if and only if at least one Filter passed.
// They are used to modify the request object
// of the matched Application, e.g. modify the host.
//
// See `SetHost` option too.
RequestModifiers []func(*http.Request)
// Note(@kataras): I though a lot of API designs for that one and the current is the safest to use.
// I skipped the idea of returning a wrapped Application to have functions like app.UseFilter
// or the idea of accepting a chain of Iris Handlers here because the Context belongs
// to the switcher application and a new one is acquired on the matched Application level,
// so communication between them is not possible although
// we can make it possible but lets not complicate the code here, unless otherwise requested.
}
// SwitchOption should be implemented by all options
// passed to the `Switch` package-level last variadic input argument.
SwitchOption interface {
Apply(*SwitchOptions)
}
// SwitchOptionFunc provides a functional way to pass options
// to the `Switch` package-level function's last variadic input argument.
SwitchOptionFunc func(*SwitchOptions)
)
// Apply completes the `SwitchOption` interface.
func (f SwitchOptionFunc) Apply(opts *SwitchOptions) {
f(opts)
}
// DefaultSwitchOptions returns a fresh SwitchOptions
// struct value with its fields set to their defaults.
func DefaultSwitchOptions() SwitchOptions {
return SwitchOptions{
RequestModifiers: nil,
}
}
// Apply completes the `SwitchOption` interface.
// It does copies values from "o" to "opts" when necessary.
func (o SwitchOptions) Apply(opts *SwitchOptions) {
if v := o.RequestModifiers; len(v) > 0 {
opts.RequestModifiers = v // override, not append.
}
}
// SetHost is a SwitchOption.
// It force sets a Host field for the matched Application's request object.
// Extremely useful when used with Hosts SwitchProvider.
// Usecase: www. to root domain without redirection (SEO reasons)
// and keep the same internal request Host for both of them so
// the root app's handlers will always work with a single host no matter
// what the real request Host was.
func SetHost(hostField string) SwitchOptionFunc {
if hostField == "" {
return nil
}
setHost := func(r *http.Request) {
r.Host = hostField
r.URL.Host = hostField // note: the URL.String builds the uri based on that.
}
return func(opts *SwitchOptions) {
opts.RequestModifiers = append(opts.RequestModifiers, setHost)
}
}

View File

@ -0,0 +1,38 @@
package apps
import (
"testing"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/httptest"
)
func TestSetHost(t *testing.T) {
var (
index = func(ctx iris.Context) {
ctx.Header("Server", ctx.Application().String())
ctx.WriteString(ctx.Host())
}
forceHost = "www.mydomain.com"
)
rootApp := iris.New().SetName("My Server")
rootApp.Get("/", index)
switcher := Switch(Hosts{
{"^(www.)?mydomain.com$", rootApp},
}, SetHost(forceHost))
e := httptest.New(t, switcher)
tests := []*httptest.Request{
e.GET("/").WithURL("http://mydomain.com"),
e.GET("/").WithURL("http://www.mydomain.com"),
}
for _, tt := range tests {
ex := tt.Expect().Status(iris.StatusOK)
ex.Header("Server").Equal(rootApp.String())
ex.Body().Equal(forceHost)
}
}