1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

Add configurable base path for reverse proxy use (#169)

* ui: Refactor routing functions into Router record
* ui: Store base URI in AppConfig
* ui: Use basePath in Router functions
* backend: Add Web.BasePath config option and update routes
* Tweaks to get SPA to bootstrap basePath configured
* ui: basePath support for apis/serve
* ui: basePath support for message monitor
* web: Redirect requests to / when basePath configured
* doc: add basepath to config.md
* Closes #107
This commit is contained in:
James Hillyerd
2020-08-09 15:53:15 -07:00
committed by GitHub
parent 316a732e7f
commit 289b38f016
20 changed files with 381 additions and 143 deletions

View File

@@ -96,6 +96,7 @@ type POP3 struct {
// Web contains the HTTP server configuration.
type Web struct {
Addr string `required:"true" default:"0.0.0.0:9000" desc:"Web server IP4 host:port"`
BasePath string `default:"" desc:"Base path prefix for UI and API URLs"`
UIDir string `required:"true" default:"ui/dist" desc:"User interface dir"`
GreetingFile string `required:"true" default:"ui/greeting.html" desc:"Home page greeting HTML"`
MonitorVisible bool `required:"true" default:"true" desc:"Show monitor tab in UI?"`

View File

@@ -1,5 +1,6 @@
package web
type jsonAppConfig struct {
MonitorVisible bool `json:"monitor-visible"`
BasePath string `json:"base-path"`
MonitorVisible bool `json:"monitor-visible"`
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/inbucket/inbucket/pkg/config"
"github.com/inbucket/inbucket/pkg/message"
"github.com/inbucket/inbucket/pkg/msghub"
"github.com/inbucket/inbucket/pkg/stringutil"
"github.com/rs/zerolog/log"
)
@@ -56,33 +57,42 @@ func Initialize(
msgHub = mh
manager = mm
// Redirect requests to / if there is a base path configured.
prefix := stringutil.MakePathPrefixer(conf.Web.BasePath)
redirectBase := prefix("/")
if redirectBase != "/" {
log.Info().Str("module", "web").Str("phase", "startup").Str("path", redirectBase).
Msg("Base path configured")
Router.Path("/").Handler(http.RedirectHandler(redirectBase, http.StatusFound))
}
// Dynamic paths.
log.Info().Str("module", "web").Str("phase", "startup").Str("path", conf.Web.UIDir).
Msg("Web UI content mapped")
Router.Handle("/debug/vars", expvar.Handler())
Router.Handle(prefix("/debug/vars"), expvar.Handler())
if conf.Web.PProf {
Router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
Router.HandleFunc("/debug/pprof/profile", pprof.Profile)
Router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
Router.HandleFunc("/debug/pprof/trace", pprof.Trace)
Router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
Router.HandleFunc(prefix("/debug/pprof/cmdline"), pprof.Cmdline)
Router.HandleFunc(prefix("/debug/pprof/profile"), pprof.Profile)
Router.HandleFunc(prefix("/debug/pprof/symbol"), pprof.Symbol)
Router.HandleFunc(prefix("/debug/pprof/trace"), pprof.Trace)
Router.PathPrefix(prefix("/debug/pprof/")).HandlerFunc(pprof.Index)
log.Warn().Str("module", "web").Str("phase", "startup").
Msg("Go pprof tools installed to /debug/pprof")
Msg("Go pprof tools installed to " + prefix("/debug/pprof"))
}
// Static paths.
Router.PathPrefix("/static").Handler(
http.StripPrefix("/", http.FileServer(http.Dir(conf.Web.UIDir))))
Router.Path("/favicon.png").Handler(
Router.PathPrefix(prefix("/static")).Handler(
http.StripPrefix(prefix("/"), http.FileServer(http.Dir(conf.Web.UIDir))))
Router.Path(prefix("/favicon.png")).Handler(
fileHandler(filepath.Join(conf.Web.UIDir, "favicon.png")))
// SPA managed paths.
spaHandler := cookieHandler(appConfigCookie(conf.Web),
fileHandler(filepath.Join(conf.Web.UIDir, "index.html")))
Router.Path("/").Handler(spaHandler)
Router.Path("/monitor").Handler(spaHandler)
Router.Path("/status").Handler(spaHandler)
Router.PathPrefix("/m/").Handler(spaHandler)
Router.Path(prefix("/")).Handler(spaHandler)
Router.Path(prefix("/monitor")).Handler(spaHandler)
Router.Path(prefix("/status")).Handler(spaHandler)
Router.PathPrefix(prefix("/m/")).Handler(spaHandler)
// Error handlers.
Router.NotFoundHandler = noMatchHandler(
@@ -131,6 +141,7 @@ func Start(ctx context.Context) {
func appConfigCookie(webConfig config.Web) *http.Cookie {
o := &jsonAppConfig{
BasePath: webConfig.BasePath,
MonitorVisible: webConfig.MonitorVisible,
}
b, err := json.Marshal(o)

View File

@@ -61,3 +61,16 @@ func SliceToLower(slice []string) {
slice[i] = strings.ToLower(s)
}
}
// MakePathPrefixer returns a function that will add the specified prefix (base) to URI strings.
// The returned prefixer expects all provided paths to start with /.
func MakePathPrefixer(prefix string) func(string) string {
prefix = strings.Trim(prefix, "/")
if prefix != "" {
prefix = "/" + prefix
}
return func(path string) string {
return prefix + path
}
}

View File

@@ -1,6 +1,7 @@
package stringutil_test
import (
"fmt"
"net/mail"
"testing"
@@ -35,3 +36,43 @@ func TestStringAddressList(t *testing.T) {
}
}
}
func TestMakePathPrefixer(t *testing.T) {
testCases := []struct {
prefix, path, want string
}{
{prefix: "", path: "", want: ""},
{prefix: "", path: "relative", want: "relative"},
{prefix: "", path: "/qualified", want: "/qualified"},
{prefix: "", path: "/many/path/segments", want: "/many/path/segments"},
{prefix: "pfx", path: "", want: "/pfx"},
{prefix: "pfx", path: "/", want: "/pfx/"},
{prefix: "pfx", path: "relative", want: "/pfxrelative"},
{prefix: "pfx", path: "/qualified", want: "/pfx/qualified"},
{prefix: "pfx", path: "/many/path/segments", want: "/pfx/many/path/segments"},
{prefix: "/pfx/", path: "", want: "/pfx"},
{prefix: "/pfx/", path: "/", want: "/pfx/"},
{prefix: "/pfx/", path: "relative", want: "/pfxrelative"},
{prefix: "/pfx/", path: "/qualified", want: "/pfx/qualified"},
{prefix: "/pfx/", path: "/many/path/segments", want: "/pfx/many/path/segments"},
{prefix: "a/b/c", path: "", want: "/a/b/c"},
{prefix: "a/b/c", path: "/", want: "/a/b/c/"},
{prefix: "a/b/c", path: "relative", want: "/a/b/crelative"},
{prefix: "a/b/c", path: "/qualified", want: "/a/b/c/qualified"},
{prefix: "a/b/c", path: "/many/path/segments", want: "/a/b/c/many/path/segments"},
{prefix: "/a/b/c/", path: "", want: "/a/b/c"},
{prefix: "/a/b/c/", path: "/", want: "/a/b/c/"},
{prefix: "/a/b/c/", path: "relative", want: "/a/b/crelative"},
{prefix: "/a/b/c/", path: "/qualified", want: "/a/b/c/qualified"},
{prefix: "/a/b/c/", path: "/many/path/segments", want: "/a/b/c/many/path/segments"},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("prefix %s for path %s", tc.prefix, tc.path), func(t *testing.T) {
prefixer := stringutil.MakePathPrefixer(tc.prefix)
got := prefixer(tc.path)
if got != tc.want {
t.Errorf("Got: %q, want: %q", got, tc.want)
}
})
}
}