Add --prefix flag for easy reverse proxying

This commit is contained in:
Seednode 2023-09-13 23:24:29 -05:00
parent d612144be1
commit fdd33376ce
12 changed files with 88 additions and 54 deletions

View File

@ -114,6 +114,7 @@ Flags:
--cache-file string path to optional persistent cache file --cache-file string path to optional persistent cache file
-f, --filter enable filtering -f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs) --flash enable support for shockwave flash files (via ruffle.rs)
--handlers display registered handlers (for debugging)
-h, --help help for roulette -h, --help help for roulette
--images enable support for image files --images enable support for image files
-i, --info expose informational endpoints -i, --info expose informational endpoints
@ -121,6 +122,7 @@ Flags:
--minimum-files uint32 skip directories with file counts below this value (default 1) --minimum-files uint32 skip directories with file counts below this value (default 1)
--page-length uint32 pagination length for statistics and debug pages --page-length uint32 pagination length for statistics and debug pages
-p, --port uint16 port to listen on (default 8080) -p, --port uint16 port to listen on (default 8080)
--prefix string path with which to prefix all listeners (for reverse proxying)
--profile register net/http/pprof handlers --profile register net/http/pprof handlers
-r, --recursive recurse into subdirectories -r, --recursive recurse into subdirectories
--refresh-interval string force refresh interval equal to this duration (minimum 500ms) --refresh-interval string force refresh interval equal to this duration (minimum 500ms)

View File

@ -152,5 +152,5 @@ func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCac
cache.generate(args, formats) cache.generate(args, formats)
} }
mux.GET("/clear_cache", serveCacheClear(args, cache, formats)) register(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats))
} }

View File

@ -65,7 +65,7 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
if Sorting { if Sorting {
shouldSort = "?sort=asc" shouldSort = "?sort=asc"
} }
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n", mediaPrefix, v, shouldSort, v)) htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s%s\">%s</a></td></tr>\n", Prefix, mediaPrefix, v, shouldSort, v))
} }
} }
if PageLength != 0 { if PageLength != 0 {
@ -274,19 +274,19 @@ func serveEnabledMimeTypes(formats *types.Types) httprouter.Handle {
func registerInfoHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) { func registerInfoHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) {
if Cache { if Cache {
mux.GET("/html/", serveIndexHtml(args, cache, false)) register(mux, Prefix+"/html/", serveIndexHtml(args, cache, false))
if PageLength != 0 { if PageLength != 0 {
mux.GET("/html/:page", serveIndexHtml(args, cache, true)) register(mux, Prefix+"/html/:page", serveIndexHtml(args, cache, true))
} }
mux.GET("/json", serveIndexJson(args, cache)) register(mux, Prefix+"/json", serveIndexJson(args, cache))
if PageLength != 0 { if PageLength != 0 {
mux.GET("/json/:page", serveIndexJson(args, cache)) register(mux, Prefix+"/json/:page", serveIndexJson(args, cache))
} }
} }
mux.GET("/available_extensions", serveAvailableExtensions()) register(mux, Prefix+"/available_extensions", serveAvailableExtensions())
mux.GET("/enabled_extensions", serveEnabledExtensions(formats)) register(mux, Prefix+"/enabled_extensions", serveEnabledExtensions(formats))
mux.GET("/available_mime_types", serveAvailableMimeTypes()) register(mux, Prefix+"/available_mime_types", serveAvailableMimeTypes())
mux.GET("/enabled_mime_types", serveEnabledMimeTypes(formats)) register(mux, Prefix+"/enabled_mime_types", serveEnabledMimeTypes(formats))
} }

View File

@ -11,9 +11,9 @@ import (
) )
func registerProfileHandlers(mux *httprouter.Router) { func registerProfileHandlers(mux *httprouter.Router) {
mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index) mux.HandlerFunc("GET", Prefix+"/debug/pprof/", pprof.Index)
mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) mux.HandlerFunc("GET", Prefix+"/debug/pprof/cmdline", pprof.Cmdline)
mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) mux.HandlerFunc("GET", Prefix+"/debug/pprof/profile", pprof.Profile)
mux.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) mux.HandlerFunc("GET", Prefix+"/debug/pprof/symbol", pprof.Symbol)
mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace) mux.HandlerFunc("GET", Prefix+"/debug/pprof/trace", pprof.Trace)
} }

View File

@ -12,7 +12,7 @@ import (
) )
const ( const (
ReleaseVersion string = "0.80.1" ReleaseVersion string = "0.81.0"
) )
var ( var (
@ -23,12 +23,14 @@ var (
CacheFile string CacheFile string
Filtering bool Filtering bool
Flash bool Flash bool
Handlers bool
Images bool Images bool
Info bool Info bool
MaximumFileCount uint32 MaximumFileCount uint32
MinimumFileCount uint32 MinimumFileCount uint32
PageLength uint32 PageLength uint32
Port uint16 Port uint16
Prefix string
Profile bool Profile bool
Recursive bool Recursive bool
RefreshInterval string RefreshInterval string
@ -79,12 +81,14 @@ func init() {
rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file") rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file")
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering") rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)") rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)")
rootCmd.Flags().BoolVar(&Handlers, "handlers", false, "display registered handlers (for debugging)")
rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files") rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files")
rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints") rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints")
rootCmd.Flags().Uint32Var(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value") rootCmd.Flags().Uint32Var(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value")
rootCmd.Flags().Uint32Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value") rootCmd.Flags().Uint32Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
rootCmd.Flags().Uint32Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages") rootCmd.Flags().Uint32Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages")
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on") rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
rootCmd.Flags().StringVar(&Prefix, "prefix", "", "path with which to prefix all listeners (for reverse proxying)")
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers") rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories") rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
rootCmd.Flags().StringVar(&RefreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)") rootCmd.Flags().StringVar(&RefreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)")

View File

@ -40,7 +40,9 @@ const (
func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle { func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
path := strings.TrimPrefix(r.URL.Path, sourcePrefix) prefix := Prefix + sourcePrefix
path := strings.TrimPrefix(r.URL.Path, prefix)
prefixedFilePath, err := stripQueryParams(path) prefixedFilePath, err := stripQueryParams(path)
if err != nil { if err != nil {
@ -51,7 +53,7 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
return return
} }
filePath, err := filepath.EvalSymlinks(strings.TrimPrefix(prefixedFilePath, sourcePrefix)) filePath, err := filepath.EvalSymlinks(strings.TrimPrefix(prefixedFilePath, prefix))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -134,7 +136,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
return return
} }
strippedRefererUri := strings.TrimPrefix(refererUri, mediaPrefix) strippedRefererUri := strings.TrimPrefix(refererUri, Prefix+mediaPrefix)
filters := &filters{ filters := &filters{
included: splitQueryParams(r.URL.Query().Get("include"), regexes), included: splitQueryParams(r.URL.Query().Get("include"), regexes),
@ -187,8 +189,9 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
queryParams := generateQueryParams(filters, sortOrder, refreshInterval) queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
newUrl := fmt.Sprintf("http://%s%s%s", newUrl := fmt.Sprintf("http://%s%s%s%s",
r.Host, r.Host,
Prefix,
preparePath(filePath), preparePath(filePath),
queryParams, queryParams,
) )
@ -205,7 +208,7 @@ func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprout
sortOrder := sortOrder(r) sortOrder := sortOrder(r)
path := strings.TrimPrefix(r.URL.Path, mediaPrefix) path := strings.TrimPrefix(strings.TrimPrefix(r.URL.Path, Prefix), mediaPrefix)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
path = strings.TrimPrefix(path, "/") path = strings.TrimPrefix(path, "/")
@ -240,7 +243,7 @@ func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprout
return return
} }
fileUri := generateFileUri(path) fileUri := Prefix + "/" + generateFileUri(path)
fileName := filepath.Base(path) fileName := filepath.Base(path)
@ -248,20 +251,20 @@ func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprout
refreshTimer, refreshInterval := refreshInterval(r) refreshTimer, refreshInterval := refreshInterval(r)
queryParams := generateQueryParams(filters, sortOrder, refreshInterval) rootUrl := Prefix + "/" + generateQueryParams(filters, sortOrder, refreshInterval)
var htmlBody strings.Builder var htmlBody strings.Builder
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`) htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
htmlBody.WriteString(faviconHtml) htmlBody.WriteString(faviconHtml)
htmlBody.WriteString(fmt.Sprintf(`<style>%s</style>`, fileType.Css())) htmlBody.WriteString(fmt.Sprintf(`<style>%s</style>`, fileType.Css()))
htmlBody.WriteString((fileType.Title(queryParams, fileUri, path, fileName, mimeType))) htmlBody.WriteString((fileType.Title(rootUrl, fileUri, path, fileName, Prefix, mimeType)))
htmlBody.WriteString(`</head><body>`) htmlBody.WriteString(`</head><body>`)
if refreshInterval != "0ms" { if refreshInterval != "0ms" {
htmlBody.WriteString(fmt.Sprintf("<script>window.onload = function(){setInterval(function(){window.location.href = '/%s';}, %d);};</script>", htmlBody.WriteString(fmt.Sprintf("<script>window.onload = function(){setInterval(function(){window.location.href = '%s';}, %d);};</script>",
queryParams, rootUrl,
refreshTimer)) refreshTimer))
} }
htmlBody.WriteString((fileType.Body(queryParams, fileUri, path, fileName, mimeType))) htmlBody.WriteString((fileType.Body(rootUrl, fileUri, path, fileName, Prefix, mimeType)))
htmlBody.WriteString(`</body></html>`) htmlBody.WriteString(`</body></html>`)
_, err = io.WriteString(w, gohtml.Format(htmlBody.String())) _, err = io.WriteString(w, gohtml.Format(htmlBody.String()))
@ -285,6 +288,25 @@ func serveVersion() httprouter.Handle {
} }
} }
func register(mux *httprouter.Router, path string, handle httprouter.Handle) {
mux.GET(path, handle)
if Handlers {
fmt.Printf("Registered handler for path %s\n", path)
}
}
func redirectRoot() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
newUrl := fmt.Sprintf("http://%s%s",
r.Host,
Prefix,
)
http.Redirect(w, r, newUrl, RedirectStatusCode)
}
}
func ServePage(args []string) error { func ServePage(args []string) error {
timeZone := os.Getenv("TZ") timeZone := os.Getenv("TZ")
if timeZone != "" { if timeZone != "" {
@ -363,17 +385,23 @@ func ServePage(args []string) error {
mux.PanicHandler = serverErrorHandler() mux.PanicHandler = serverErrorHandler()
mux.GET("/", serveRoot(paths, regexes, cache, formats)) Prefix = strings.TrimSuffix(Prefix, "/")
mux.GET("/favicons/*favicon", serveFavicons()) register(mux, Prefix+"/", serveRoot(paths, regexes, cache, formats))
mux.GET("/favicon.ico", serveFavicons()) if Prefix != "" {
register(mux, "/", redirectRoot())
}
mux.GET(mediaPrefix+"/*media", serveMedia(paths, regexes, formats)) register(mux, Prefix+"/favicons/*favicon", serveFavicons())
mux.GET(sourcePrefix+"/*static", serveStaticFile(paths, cache)) register(mux, Prefix+"/favicon.ico", serveFavicons())
mux.GET("/version", serveVersion()) register(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, regexes, formats))
register(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, cache))
register(mux, Prefix+"/version", serveVersion())
if Cache { if Cache {
registerCacheHandlers(mux, args, cache, formats) registerCacheHandlers(mux, args, cache, formats)

View File

@ -22,13 +22,13 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName) return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<a href="/%s"><audio controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a>`, return fmt.Sprintf(`<a href="%s"><audio controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a>`,
queryParams, rootUrl,
fileUri, fileUri,
mime, mime,
fileName) fileName)

View File

@ -22,15 +22,15 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName) return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
var html strings.Builder var html strings.Builder
html.WriteString(fmt.Sprintf(`<script src="https://unpkg.com/@ruffle-rs/ruffle"></script><script>window.RufflePlayer.config = {autoplay:"on"};</script><embed src="%s"></embed>`, fileUri)) html.WriteString(fmt.Sprintf(`<script src="https://unpkg.com/@ruffle-rs/ruffle"></script><script>window.RufflePlayer.config = {autoplay:"on"};</script><embed src="%s"></embed>`, fileUri))
html.WriteString(fmt.Sprintf(`<br /><button onclick="window.location.href = '/%s';">Next</button>`, queryParams)) html.WriteString(fmt.Sprintf(`<br /><button onclick="window.location.href = '%s';">Next</button>`, rootUrl))
return html.String() return html.String()
} }

View File

@ -37,7 +37,7 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
dimensions, err := ImageDimensions(filePath) dimensions, err := ImageDimensions(filePath)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -49,14 +49,14 @@ func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) str
dimensions.height) dimensions.height)
} }
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
dimensions, err := ImageDimensions(filePath) dimensions, err := ImageDimensions(filePath)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return fmt.Sprintf(`<a href="/%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a>`, return fmt.Sprintf(`<a href="%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a>`,
queryParams, rootUrl,
fileUri, fileUri,
dimensions.width, dimensions.width,
dimensions.height, dimensions.height,

View File

@ -27,18 +27,18 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName) return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
body, err := os.ReadFile(filePath) body, err := os.ReadFile(filePath)
if err != nil { if err != nil {
body = []byte{} body = []byte{}
} }
return fmt.Sprintf(`<a href="/%s"><textarea autofocus readonly>%s</textarea></a>`, return fmt.Sprintf(`<a href="%s"><textarea autofocus readonly>%s</textarea></a>`,
queryParams, rootUrl,
body) body)
} }

View File

@ -20,8 +20,8 @@ var SupportedFormats = &Types{
type Type interface { type Type interface {
Css() string Css() string
Title(queryParams, fileUri, filePath, fileName, mime string) string Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string
Body(queryParams, fileUri, filePath, fileName, mime string) string Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string
Extensions() map[string]string Extensions() map[string]string
MimeTypes() []string MimeTypes() []string
Validate(filePath string) bool Validate(filePath string) bool

View File

@ -24,13 +24,13 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName) return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<a href="/%s"><video controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a>`, return fmt.Sprintf(`<a href="%s"><video controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a>`,
queryParams, rootUrl,
fileUri, fileUri,
mime, mime,
fileName) fileName)