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
|
// Template strings
|
||||||
ts *ts.TemplateStrings
|
ts *ts.TemplateStrings
|
||||||
// Tor
|
// Tor
|
||||||
torAddress string
|
torAddress string
|
||||||
|
torHostname string
|
||||||
}
|
}
|
||||||
|
|
13
config.go
13
config.go
|
@ -32,6 +32,7 @@ type configServer struct {
|
||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
PublicAddress string `mapstructure:"publicAddress"`
|
PublicAddress string `mapstructure:"publicAddress"`
|
||||||
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
||||||
|
MediaAddress string `mapstructure:"mediaAddress"`
|
||||||
PublicHTTPS bool `mapstructure:"publicHttps"`
|
PublicHTTPS bool `mapstructure:"publicHttps"`
|
||||||
Tor bool `mapstructure:"tor"`
|
Tor bool `mapstructure:"tor"`
|
||||||
SecurityHeaders bool `mapstructure:"securityHeaders"`
|
SecurityHeaders bool `mapstructure:"securityHeaders"`
|
||||||
|
@ -39,6 +40,7 @@ type configServer struct {
|
||||||
JWTSecret string `mapstructure:"jwtSecret"`
|
JWTSecret string `mapstructure:"jwtSecret"`
|
||||||
publicHostname string
|
publicHostname string
|
||||||
shortPublicHostname string
|
shortPublicHostname string
|
||||||
|
mediaHostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
type configDb struct {
|
type configDb struct {
|
||||||
|
@ -304,13 +306,20 @@ func (a *goBlog) initConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.cfg.Server.publicHostname = publicURL.Hostname()
|
a.cfg.Server.publicHostname = publicURL.Hostname()
|
||||||
if a.cfg.Server.ShortPublicAddress != "" {
|
if sa := a.cfg.Server.ShortPublicAddress; sa != "" {
|
||||||
shortPublicURL, err := url.Parse(a.cfg.Server.ShortPublicAddress)
|
shortPublicURL, err := url.Parse(sa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.cfg.Server.shortPublicHostname = shortPublicURL.Hostname()
|
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 == "" {
|
if a.cfg.Server.JWTSecret == "" {
|
||||||
return errors.New("no JWT secret configured")
|
return errors.New("no JWT secret configured")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ server:
|
||||||
port: 8080
|
port: 8080
|
||||||
publicAddress: https://example.com # Public address to use for the blog
|
publicAddress: https://example.com # Public address to use for the blog
|
||||||
shortPublicAddress: https://short.example.com # Optional short address, will redirect to main address
|
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
|
# Security
|
||||||
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
|
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
|
||||||
securityHeaders: true # Set security HTTP headers (to always use HTTPS etc.)
|
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"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/justinas/alice"
|
"github.com/justinas/alice"
|
||||||
|
"go.goblog.app/app/pkgs/maprouter"
|
||||||
"golang.org/x/crypto/acme"
|
"golang.org/x/crypto/acme"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -78,8 +79,11 @@ func (a *goBlog) startServer() (err error) {
|
||||||
// Start HTTPS
|
// Start HTTPS
|
||||||
s.Addr = ":https"
|
s.Addr = ":https"
|
||||||
hosts := []string{a.cfg.Server.publicHostname}
|
hosts := []string{a.cfg.Server.publicHostname}
|
||||||
if a.cfg.Server.shortPublicHostname != "" {
|
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
||||||
hosts = append(hosts, a.cfg.Server.shortPublicHostname)
|
hosts = append(hosts, shn)
|
||||||
|
}
|
||||||
|
if mhn := a.cfg.Server.mediaHostname; mhn != "" {
|
||||||
|
hosts = append(hosts, mhn)
|
||||||
}
|
}
|
||||||
acmeDir := acme.LetsEncryptURL
|
acmeDir := acme.LetsEncryptURL
|
||||||
// acmeDir := "https://acme-staging-v02.api.letsencrypt.org/directory"
|
// acmeDir := "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
@ -127,11 +131,29 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) buildRouter() (http.Handler, error) {
|
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()
|
r := chi.NewMux()
|
||||||
|
|
||||||
// Basic middleware
|
// Basic middleware
|
||||||
r.Use(fixHTTPHandler)
|
r.Use(fixHTTPHandler)
|
||||||
r.Use(a.redirectShortDomain)
|
|
||||||
r.Use(middleware.RedirectSlashes)
|
r.Use(middleware.RedirectSlashes)
|
||||||
r.Use(middleware.CleanPath)
|
r.Use(middleware.CleanPath)
|
||||||
r.Use(middleware.GetHead)
|
r.Use(middleware.GetHead)
|
||||||
|
@ -173,7 +195,7 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
|
||||||
r.Group(a.staticFilesRouter)
|
r.Group(a.staticFilesRouter)
|
||||||
|
|
||||||
// Media files
|
// 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
|
// Captcha
|
||||||
r.Handle("/captcha/*", captcha.Server(500, 250))
|
r.Handle("/captcha/*", captcha.Server(500, 250))
|
||||||
|
@ -196,7 +218,8 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
|
||||||
|
|
||||||
r.MethodNotAllowed(a.serveNotAllowed)
|
r.MethodNotAllowed(a.serveNotAllowed)
|
||||||
|
|
||||||
return r, nil
|
mapRouter.DefaultHandler = r
|
||||||
|
return mapRouter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"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-Frame-Options", "SAMEORIGIN")
|
||||||
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
||||||
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
||||||
if a.cfg.Server.Tor && a.torAddress != "" {
|
if a.torAddress != "" {
|
||||||
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
|
w.Header().Set("Onion-Location", a.torAddress+r.RequestURI)
|
||||||
}
|
}
|
||||||
next.ServeHTTP(w, r)
|
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
|
// Blog
|
||||||
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
||||||
return 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"
|
"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) {
|
func (a *goBlog) serveMediaFile(w http.ResponseWriter, r *http.Request) {
|
||||||
f := filepath.Join(mediaFilePath, chi.URLParam(r, "file"))
|
f := filepath.Join(mediaFilePath, chi.URLParam(r, "file"))
|
||||||
_, err := os.Stat(f)
|
_, err := os.Stat(f)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable")
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -174,7 +173,7 @@ func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
|
||||||
}
|
}
|
||||||
// Tor
|
// Tor
|
||||||
if a.cfg.Server.Tor && a.torAddress != "" {
|
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 {
|
if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed {
|
||||||
data.TorUsed = true
|
data.TorUsed = true
|
||||||
|
|
|
@ -4,12 +4,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) redirectShortDomain(next http.Handler) http.Handler {
|
func (a *goBlog) redirectShortDomain(rw http.ResponseWriter, r *http.Request) {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
http.Redirect(rw, r, a.getFullAddress(r.RequestURI), http.StatusMovedPermanently)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
7
tor.go
7
tor.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
@ -72,8 +73,10 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer onion.Close()
|
defer onion.Close()
|
||||||
a.torAddress = onion.String()
|
a.torAddress = "http://" + onion.String()
|
||||||
log.Println("Onion service published on http://" + a.torAddress)
|
torUrl, _ := url.Parse(a.torAddress)
|
||||||
|
a.torHostname = torUrl.Hostname()
|
||||||
|
log.Println("Onion service published on " + a.torAddress)
|
||||||
// Clear cache
|
// Clear cache
|
||||||
a.cache.purge()
|
a.cache.purge()
|
||||||
// Serve handler
|
// Serve handler
|
||||||
|
|
Loading…
Reference in New Issue