mirror of https://github.com/jlelse/GoBlog
Add option to set extra address for media
This commit is contained in:
parent
55fa3421f9
commit
ec9ef528c4
3
app.go
3
app.go
|
@ -71,5 +71,6 @@ type goBlog struct {
|
|||
// Template strings
|
||||
ts *ts.TemplateStrings
|
||||
// Tor
|
||||
torAddress string
|
||||
torAddress string
|
||||
torHostname string
|
||||
}
|
||||
|
|
13
config.go
13
config.go
|
@ -32,6 +32,7 @@ type configServer struct {
|
|||
Port int `mapstructure:"port"`
|
||||
PublicAddress string `mapstructure:"publicAddress"`
|
||||
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
||||
MediaAddress string `mapstructure:"mediaAddress"`
|
||||
PublicHTTPS bool `mapstructure:"publicHttps"`
|
||||
Tor bool `mapstructure:"tor"`
|
||||
SecurityHeaders bool `mapstructure:"securityHeaders"`
|
||||
|
@ -39,6 +40,7 @@ type configServer struct {
|
|||
JWTSecret string `mapstructure:"jwtSecret"`
|
||||
publicHostname string
|
||||
shortPublicHostname string
|
||||
mediaHostname string
|
||||
}
|
||||
|
||||
type configDb struct {
|
||||
|
@ -304,13 +306,20 @@ func (a *goBlog) initConfig() error {
|
|||
return err
|
||||
}
|
||||
a.cfg.Server.publicHostname = publicURL.Hostname()
|
||||
if a.cfg.Server.ShortPublicAddress != "" {
|
||||
shortPublicURL, err := url.Parse(a.cfg.Server.ShortPublicAddress)
|
||||
if sa := a.cfg.Server.ShortPublicAddress; sa != "" {
|
||||
shortPublicURL, err := url.Parse(sa)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.cfg.Server.shortPublicHostname = shortPublicURL.Hostname()
|
||||
}
|
||||
if ma := a.cfg.Server.MediaAddress; ma != "" {
|
||||
mediaUrl, err := url.Parse(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.cfg.Server.mediaHostname = mediaUrl.Hostname()
|
||||
}
|
||||
if a.cfg.Server.JWTSecret == "" {
|
||||
return errors.New("no JWT secret configured")
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ server:
|
|||
port: 8080
|
||||
publicAddress: https://example.com # Public address to use for the blog
|
||||
shortPublicAddress: https://short.example.com # Optional short address, will redirect to main address
|
||||
mediaAddress: https://media.example.com # Optional domain to use for serving media files
|
||||
# Security
|
||||
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
|
||||
securityHeaders: true # Set security HTTP headers (to always use HTTPS etc.)
|
||||
|
|
33
http.go
33
http.go
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/justinas/alice"
|
||||
"go.goblog.app/app/pkgs/maprouter"
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -78,8 +79,11 @@ func (a *goBlog) startServer() (err error) {
|
|||
// Start HTTPS
|
||||
s.Addr = ":https"
|
||||
hosts := []string{a.cfg.Server.publicHostname}
|
||||
if a.cfg.Server.shortPublicHostname != "" {
|
||||
hosts = append(hosts, a.cfg.Server.shortPublicHostname)
|
||||
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
||||
hosts = append(hosts, shn)
|
||||
}
|
||||
if mhn := a.cfg.Server.mediaHostname; mhn != "" {
|
||||
hosts = append(hosts, mhn)
|
||||
}
|
||||
acmeDir := acme.LetsEncryptURL
|
||||
// acmeDir := "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
@ -127,11 +131,29 @@ const (
|
|||
)
|
||||
|
||||
func (a *goBlog) buildRouter() (http.Handler, error) {
|
||||
mapRouter := &maprouter.MapRouter{
|
||||
Handlers: map[string]http.Handler{},
|
||||
}
|
||||
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
||||
mapRouter.Handlers[shn] = http.HandlerFunc(a.redirectShortDomain)
|
||||
}
|
||||
if mhn := a.cfg.Server.mediaHostname; mhn != "" && !a.isPrivate() {
|
||||
mr := chi.NewMux()
|
||||
|
||||
mr.Use(middleware.RedirectSlashes)
|
||||
mr.Use(middleware.CleanPath)
|
||||
mr.Use(middleware.GetHead)
|
||||
|
||||
mr.Group(a.mediaFilesRouter)
|
||||
|
||||
mapRouter.Handlers[mhn] = mr
|
||||
}
|
||||
|
||||
// Default router
|
||||
r := chi.NewMux()
|
||||
|
||||
// Basic middleware
|
||||
r.Use(fixHTTPHandler)
|
||||
r.Use(a.redirectShortDomain)
|
||||
r.Use(middleware.RedirectSlashes)
|
||||
r.Use(middleware.CleanPath)
|
||||
r.Use(middleware.GetHead)
|
||||
|
@ -173,7 +195,7 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
|
|||
r.Group(a.staticFilesRouter)
|
||||
|
||||
// Media files
|
||||
r.With(a.privateModeHandler).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, a.serveMediaFile)
|
||||
r.Route("/m", a.mediaFilesRouter)
|
||||
|
||||
// Captcha
|
||||
r.Handle("/captcha/*", captcha.Server(500, 250))
|
||||
|
@ -196,7 +218,8 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
|
|||
|
||||
r.MethodNotAllowed(a.serveNotAllowed)
|
||||
|
||||
return r, nil
|
||||
mapRouter.DefaultHandler = r
|
||||
return mapRouter, nil
|
||||
}
|
||||
|
||||
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -43,8 +42,8 @@ func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
|
|||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
||||
if a.cfg.Server.Tor && a.torAddress != "" {
|
||||
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
|
||||
if a.torAddress != "" {
|
||||
w.Header().Set("Onion-Location", a.torAddress+r.RequestURI)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
|
|
@ -92,6 +92,12 @@ func (a *goBlog) staticFilesRouter(r chi.Router) {
|
|||
}
|
||||
}
|
||||
|
||||
// Media files
|
||||
func (a *goBlog) mediaFilesRouter(r chi.Router) {
|
||||
r.Use(a.privateModeHandler)
|
||||
r.Get(mediaFileRoute, a.serveMediaFile)
|
||||
}
|
||||
|
||||
// Blog
|
||||
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
|
|
8
media.go
8
media.go
|
@ -8,13 +8,17 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const mediaFilePath = "data/media"
|
||||
const (
|
||||
mediaFilePath = "data/media"
|
||||
mediaFileRoute = `/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`
|
||||
)
|
||||
|
||||
func (a *goBlog) serveMediaFile(w http.ResponseWriter, r *http.Request) {
|
||||
f := filepath.Join(mediaFilePath, chi.URLParam(r, "file"))
|
||||
_, err := os.Stat(f)
|
||||
if err != nil {
|
||||
a.serve404(w, r)
|
||||
// Serve 404, but don't use normal serve404 method because of media domain
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable")
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package maprouter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Make sure interface is satisfied
|
||||
var _ http.Handler = &MapRouter{}
|
||||
|
||||
// Routes requests based on a map with routers
|
||||
type MapRouter struct {
|
||||
// Default http.Handler
|
||||
DefaultHandler http.Handler
|
||||
// Handlers mapped by prefix
|
||||
Handlers map[string]http.Handler
|
||||
// Optional function to find key for handler, default uses hostname
|
||||
KeyFunc func(r *http.Request) string
|
||||
}
|
||||
|
||||
// Serve the HTTP request
|
||||
func (ar *MapRouter) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
if len(ar.Handlers) > 0 {
|
||||
var key string
|
||||
if ar.KeyFunc != nil {
|
||||
key = ar.KeyFunc(r)
|
||||
} else {
|
||||
key = defaultKey(r)
|
||||
}
|
||||
if h, ok := ar.Handlers[key]; ok {
|
||||
h.ServeHTTP(rw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
ar.DefaultHandler.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
// Gets the default key for the router
|
||||
func defaultKey(r *http.Request) string {
|
||||
return r.Host
|
||||
}
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -174,7 +173,7 @@ func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
|
|||
}
|
||||
// Tor
|
||||
if a.cfg.Server.Tor && a.torAddress != "" {
|
||||
data.TorAddress = fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI)
|
||||
data.TorAddress = a.torAddress + r.RequestURI
|
||||
}
|
||||
if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed {
|
||||
data.TorUsed = true
|
||||
|
|
|
@ -4,12 +4,6 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
func (a *goBlog) redirectShortDomain(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if a.cfg.Server.shortPublicHostname != "" && r.Host == a.cfg.Server.shortPublicHostname {
|
||||
http.Redirect(rw, r, a.getFullAddress(r.RequestURI), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
func (a *goBlog) redirectShortDomain(rw http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(rw, r, a.getFullAddress(r.RequestURI), http.StatusMovedPermanently)
|
||||
}
|
||||
|
|
7
tor.go
7
tor.go
|
@ -8,6 +8,7 @@ import (
|
|||
"encoding/pem"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
@ -72,8 +73,10 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
|||
return err
|
||||
}
|
||||
defer onion.Close()
|
||||
a.torAddress = onion.String()
|
||||
log.Println("Onion service published on http://" + a.torAddress)
|
||||
a.torAddress = "http://" + onion.String()
|
||||
torUrl, _ := url.Parse(a.torAddress)
|
||||
a.torHostname = torUrl.Hostname()
|
||||
log.Println("Onion service published on " + a.torAddress)
|
||||
// Clear cache
|
||||
a.cache.purge()
|
||||
// Serve handler
|
||||
|
|
Loading…
Reference in New Issue