diff --git a/apps/switch.go b/apps/switch.go index 73aa8cfc..5ffefbf0 100644 --- a/apps/switch.go +++ b/apps/switch.go @@ -30,27 +30,29 @@ import ( // e.g. to inject the 404 on not application found. // It can also be wrapped with its `WrapRouter` method, // which is really useful for logging and statistics. -func Switch(providers ...SwitchProvider) *iris.Application { - if len(providers) == 0 { - panic("iris: switch: empty providers") +// +// Wrap with the `Join` slice to pass +// 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 cases []SwitchCase - for _, p := range providers { - for _, c := range p.GetSwitchCases() { - cases = append(cases, c) - } - - if fp, ok := p.(FriendlyNameProvider); ok { - if friendlyName := fp.GetFriendlyName(); friendlyName != "" { - friendlyAddrs = append(friendlyAddrs, friendlyName) - } + if fp, ok := provider.(FriendlyNameProvider); ok { + if friendlyName := fp.GetFriendlyName(); friendlyName != "" { + friendlyAddrs = append(friendlyAddrs, friendlyName) } } - if len(cases) == 0 { - panic("iris: switch: empty cases") + opts := DefaultSwitchOptions() + for _, opt := range options { + if opt == nil { + continue + } + + opt.Apply(&opts) } app := iris.New() @@ -70,10 +72,17 @@ func Switch(providers ...SwitchProvider) *iris.Application { app.UseRouter(func(ctx iris.Context) { for _, c := range cases { 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() { - // c.App.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) + // c.App.ServeHTTP(w, r) // } else { // Note(@kataras): don't ever try something like that; // the context pool is the switcher's one. diff --git a/apps/switch_hosts.go b/apps/switch_hosts.go index 36bfd858..0e4c4ba9 100644 --- a/apps/switch_hosts.go +++ b/apps/switch_hosts.go @@ -119,13 +119,12 @@ func newHostRedirectApp(targetHost string, code int) *iris.Application { // carefully checks if the expression already matched the "redirectTo" // to avoid the redirect loops at all. // 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 } r.Host = targetHost r.URL.Host = targetHost - // r.URL.User = nil http.Redirect(w, r, r.URL.String(), code) }) diff --git a/apps/switch_options.go b/apps/switch_options.go new file mode 100644 index 00000000..5b38cbdc --- /dev/null +++ b/apps/switch_options.go @@ -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) + } +} diff --git a/apps/switch_options_test.go b/apps/switch_options_test.go new file mode 100644 index 00000000..d398a2a6 --- /dev/null +++ b/apps/switch_options_test.go @@ -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) + } +}