Add support for base URLs with subfolders

This commit is contained in:
Frédéric Guillot 2018-02-03 15:33:17 -08:00
parent 78385a351e
commit 9c42997209
10 changed files with 105 additions and 23 deletions

View file

@ -5,6 +5,7 @@
package config
import (
"net/url"
"os"
"strconv"
)
@ -26,7 +27,10 @@ const (
// Config manages configuration parameters.
type Config struct {
IsHTTPS bool
IsHTTPS bool
baseURL string
rootURL string
basePath string
}
func (c *Config) get(key, fallback string) string {
@ -53,13 +57,34 @@ func (c *Config) HasDebugMode() bool {
return c.get("DEBUG", "") != ""
}
// BaseURL returns the application base URL.
// BaseURL returns the application base URL with path.
func (c *Config) BaseURL() string {
baseURL := c.get("BASE_URL", defaultBaseURL)
if baseURL[len(baseURL)-1:] == "/" {
baseURL = baseURL[:len(baseURL)-1]
if c.baseURL == "" {
c.baseURL = c.get("BASE_URL", defaultBaseURL)
if c.baseURL[len(c.baseURL)-1:] == "/" {
c.baseURL = c.baseURL[:len(c.baseURL)-1]
}
}
return baseURL
return c.baseURL
}
// RootURL returns the base URL without path.
func (c *Config) RootURL() string {
if c.rootURL == "" {
u, _ := url.Parse(c.BaseURL())
u.Path = ""
c.rootURL = u.String()
}
return c.rootURL
}
// BasePath returns the application base path according to the base URL.
func (c *Config) BasePath() string {
if c.basePath == "" {
u, _ := url.Parse(c.BaseURL())
c.basePath = u.Path
}
return c.basePath
}
// DatabaseURL returns the database URL.

View file

@ -9,7 +9,26 @@ import (
"testing"
)
func TestGetCustomBaseURL(t *testing.T) {
func TestDebugModeOn(t *testing.T) {
os.Clearenv()
os.Setenv("DEBUG", "1")
cfg := NewConfig()
if !cfg.HasDebugMode() {
t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode())
}
}
func TestDebugModeOff(t *testing.T) {
os.Clearenv()
cfg := NewConfig()
if cfg.HasDebugMode() {
t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode())
}
}
func TestCustomBaseURL(t *testing.T) {
os.Clearenv()
os.Setenv("BASE_URL", "http://example.org")
cfg := NewConfig()
@ -17,9 +36,17 @@ func TestGetCustomBaseURL(t *testing.T) {
if cfg.BaseURL() != "http://example.org" {
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
}
if cfg.RootURL() != "http://example.org" {
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL())
}
if cfg.BasePath() != "" {
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
}
}
func TestGetCustomBaseURLWithTrailingSlash(t *testing.T) {
func TestCustomBaseURLWithTrailingSlash(t *testing.T) {
os.Clearenv()
os.Setenv("BASE_URL", "http://example.org/folder/")
cfg := NewConfig()
@ -27,13 +54,29 @@ func TestGetCustomBaseURLWithTrailingSlash(t *testing.T) {
if cfg.BaseURL() != "http://example.org/folder" {
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
}
if cfg.RootURL() != "http://example.org" {
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.BaseURL())
}
if cfg.BasePath() != "/folder" {
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
}
}
func TestGetDefaultBaseURL(t *testing.T) {
func TestDefaultBaseURL(t *testing.T) {
os.Clearenv()
cfg := NewConfig()
if cfg.BaseURL() != "http://localhost" {
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
}
if cfg.RootURL() != "http://localhost" {
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL())
}
if cfg.BasePath() != "" {
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
}
}

View file

@ -45,6 +45,10 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
middleware.NewSessionMiddleware(cfg, store).Handler,
))
if cfg.BasePath() != "" {
router = router.PathPrefix(cfg.BasePath()).Subrouter()
}
router.Handle("/fever/", feverHandler.Use(feverController.Handler)).Name("feverEndpoint")
router.Handle("/v1/users", apiHandler.Use(apiController.CreateUser)).Methods("POST")

View file

@ -19,11 +19,11 @@ const (
)
// New creates a new cookie.
func New(name, value string, isHTTPS bool) *http.Cookie {
func New(name, value string, isHTTPS bool, path string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
Path: "/",
Path: basePath(path),
Secure: isHTTPS,
HttpOnly: true,
Expires: time.Now().Add(cookieDuration * 24 * time.Hour),
@ -31,14 +31,21 @@ func New(name, value string, isHTTPS bool) *http.Cookie {
}
// Expired returns an expired cookie.
func Expired(name string, isHTTPS bool) *http.Cookie {
func Expired(name string, isHTTPS bool, path string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: "",
Path: "/",
Path: basePath(path),
Secure: isHTTPS,
HttpOnly: true,
MaxAge: -1,
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
}
}
func basePath(path string) string {
if path == "" {
return "/"
}
return path
}

View file

@ -36,7 +36,7 @@ func (s *SessionMiddleware) Handler(next http.Handler) http.Handler {
return
}
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, s.cfg.IsHTTPS))
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, s.cfg.IsHTTPS, s.cfg.BasePath()))
} else {
logger.Debug("[Middleware:Session] %s", session)
}

View file

@ -40,7 +40,7 @@
<label for="form-fever-password">{{ t "Fever Password" }}</label>
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
<p>{{ t "Fever API endpoint:" }} <strong>{{ baseURL }}{{ route "feverEndpoint" }}</strong></p>
<p>{{ t "Fever API endpoint:" }} <strong>{{ rootURL }}{{ route "feverEndpoint" }}</strong></p>
</div>
<h3>Pinboard</h3>
@ -120,7 +120,7 @@
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
<div class="bookmarklet">
<a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
<a href="javascript:location.href='{{ rootURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
</div>
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>

View file

@ -38,6 +38,9 @@ func (e *Engine) parseAll() {
"baseURL": func() string {
return e.cfg.BaseURL()
},
"rootURL": func() string {
return e.cfg.RootURL()
},
"hasOAuth2Provider": func(provider string) bool {
return e.cfg.OAuth2Provider() == provider
},

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2018-01-31 22:01:40.010173412 -0800 PST m=+0.035694895
// 2018-02-03 15:28:45.540437885 -0800 PST m=+0.032377624
package template
@ -815,7 +815,7 @@ var templateViewsMap = map[string]string{
<label for="form-fever-password">{{ t "Fever Password" }}</label>
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
<p>{{ t "Fever API endpoint:" }} <strong>{{ baseURL }}{{ route "feverEndpoint" }}</strong></p>
<p>{{ t "Fever API endpoint:" }} <strong>{{ rootURL }}{{ route "feverEndpoint" }}</strong></p>
</div>
<h3>Pinboard</h3>
@ -895,7 +895,7 @@ var templateViewsMap = map[string]string{
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
<div class="bookmarklet">
<a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
<a href="javascript:location.href='{{ rootURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
</div>
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>
@ -1225,7 +1225,7 @@ var templateViewsMapChecksums = map[string]string{
"feeds": "65b0a47c4438810b9d51c60f3f3b2519690e56ff74029e6296c68626b83a470b",
"history": "d2476fd727e4f53428b5ed1f3f9423063583337ec8cfe1dd9c931fcb03852a20",
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
"integrations": "a677434e9a8be1f80cfbc1d04828dacc7abcec2a22b45c80594d49cc2ba7c0e5",
"integrations": "958b73d632a3e2a79368bb1582efb8aabc438cef4fa6e8dc1aa4932494916aca",
"login": "7d83c3067c02f1f6aafdd8816c7f97a4eb5a5a4bdaaaa4cc1e2fbb9c17ea65e8",
"sessions": "d8ef5900d8ea8395804b320002e5f45ed0ab8b790e43f674f61f8b9787041cbd",
"settings": "ea2505b9d0a6d6bb594dba87a92079de19baa6d494f0651693a7685489fb7de9",

View file

@ -59,7 +59,7 @@ func (c *Controller) CheckLogin(ctx *handler.Context, request *handler.Request,
logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username)
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS))
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS, c.cfg.BasePath()))
response.Redirect(ctx.Route("unread"))
}
@ -75,6 +75,6 @@ func (c *Controller) Logout(ctx *handler.Context, request *handler.Request, resp
logger.Error("[Controller:Logout] %v", err)
}
response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, c.cfg.IsHTTPS))
response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, c.cfg.IsHTTPS, c.cfg.BasePath()))
response.Redirect(ctx.Route("login"))
}

View file

@ -132,7 +132,7 @@ func (c *Controller) OAuth2Callback(ctx *handler.Context, request *handler.Reque
logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username)
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS))
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS, c.cfg.BasePath()))
response.Redirect(ctx.Route("unread"))
}