Various improvements; Remove Hugo import API; Add feeds for photos

This commit is contained in:
Jan-Lukas Else 2021-02-16 16:26:21 +01:00
parent 82ace66544
commit 99789efcd3
19 changed files with 212 additions and 337 deletions

View File

@ -84,8 +84,7 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
serveError(w, r, "Blog not found", http.StatusNotFound) serveError(w, r, "Blog not found", http.StatusNotFound)
return return
} }
w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix) b, _ := json.Marshal(map[string]interface{}{
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"subject": "acct:" + name + "@" + appConfig.Server.publicHostname, "subject": "acct:" + name + "@" + appConfig.Server.publicHostname,
"links": []map[string]string{ "links": []map[string]string{
{ {
@ -95,6 +94,8 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
}, },
}, },
}) })
w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix)
_, _ = writeMinified(w, contentTypeJSON, b)
} }
func apHandleInbox(w http.ResponseWriter, r *http.Request) { func apHandleInbox(w http.ResponseWriter, r *http.Request) {

View File

@ -14,9 +14,11 @@ var asContext = []string{"https://www.w3.org/ns/activitystreams"}
func manipulateAsPath(next http.Handler) http.Handler { func manipulateAsPath(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) { if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
// Is ActivityStream, add ".as" to differentiate cache and also trigger as function if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) {
r.URL.Path += ".as" // Is ActivityStream, add ".as" to differentiate cache and also trigger as function
r.URL.Path += ".as"
}
} }
next.ServeHTTP(rw, r) next.ServeHTTP(rw, r)
}) })
@ -68,9 +70,9 @@ type asEndpoints struct {
} }
func (p *post) serveActivityStreams(w http.ResponseWriter) { func (p *post) serveActivityStreams(w http.ResponseWriter) {
// Send JSON b, _ := json.Marshal(p.toASNote())
w.Header().Add(contentType, contentTypeASUTF8) w.Header().Set(contentType, contentTypeASUTF8)
_ = json.NewEncoder(w).Encode(p.toASNote()) _, _ = writeMinified(w, contentTypeAS, b)
} }
func (p *post) toASNote() *asNote { func (p *post) toASNote() *asNote {
@ -126,8 +128,6 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r
serveError(w, r, "Failed to marshal public key", http.StatusInternalServerError) serveError(w, r, "Failed to marshal public key", http.StatusInternalServerError)
return return
} }
// Send JSON
w.Header().Add(contentType, contentTypeASUTF8)
asBlog := &asPerson{ asBlog := &asPerson{
Context: asContext, Context: asContext,
Type: "Person", Type: "Person",
@ -154,5 +154,7 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r
URL: appConfig.User.Picture, URL: appConfig.User.Picture,
} }
} }
_ = json.NewEncoder(w).Encode(asBlog) jb, _ := json.Marshal(asBlog)
w.Header().Set(contentType, contentTypeASUTF8)
_, _ = writeMinified(w, contentTypeAS, jb)
} }

61
api.go
View File

@ -1,61 +0,0 @@
package main
import (
"io/ioutil"
"net/http"
"strings"
)
// Not tested anymore
func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
blog := r.URL.Query().Get("blog")
path := r.URL.Query().Get("path")
section := r.URL.Query().Get("section")
slug := r.URL.Query().Get("slug")
alias := r.URL.Query().Get("alias")
defer func() {
_ = r.Body.Close()
}()
bodyContent, err := ioutil.ReadAll(r.Body)
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
p, aliases, err := parseHugoFile(string(bodyContent))
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
p.Blog = blog
p.Path = path
p.Section = section
p.Slug = slug
err = p.create()
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
aliases = append(aliases, alias)
for i, alias := range aliases {
// Fix relativ paths
if !strings.HasPrefix(alias, "/") {
splittedPostPath := strings.Split(p.Path, "/")
alias = strings.TrimSuffix(p.Path, splittedPostPath[len(splittedPostPath)-1]) + alias
}
alias = strings.TrimSuffix(alias, "/")
if alias == p.Path {
alias = ""
}
aliases[i] = alias
}
if len(aliases) > 0 {
p.Parameters["aliases"] = aliases
err = p.replace(p.Path, p.Status)
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
}
http.Redirect(w, r, p.fullURL(), http.StatusCreated)
}

View File

@ -16,7 +16,6 @@ type config struct {
Blogs map[string]*configBlog `mapstructure:"blogs"` Blogs map[string]*configBlog `mapstructure:"blogs"`
User *configUser `mapstructure:"user"` User *configUser `mapstructure:"user"`
Hooks *configHooks `mapstructure:"hooks"` Hooks *configHooks `mapstructure:"hooks"`
Hugo *configHugo `mapstructure:"hugo"`
Micropub *configMicropub `mapstructure:"micropub"` Micropub *configMicropub `mapstructure:"micropub"`
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"` PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
ActivityPub *configActivityPub `mapstructure:"activityPub"` ActivityPub *configActivityPub `mapstructure:"activityPub"`
@ -151,15 +150,6 @@ type configHooks struct {
PostDelete []string `mapstructure:"postdelete"` PostDelete []string `mapstructure:"postdelete"`
} }
type configHugo struct {
Frontmatter []*frontmatter `mapstructure:"frontmatter"`
}
type frontmatter struct {
Meta string `mapstructure:"meta"`
Parameter string `mapstructure:"parameter"`
}
type configMicropub struct { type configMicropub struct {
CategoryParam string `mapstructure:"categoryParam"` CategoryParam string `mapstructure:"categoryParam"`
ReplyParam string `mapstructure:"replyParam"` ReplyParam string `mapstructure:"replyParam"`
@ -223,7 +213,6 @@ func initConfig() error {
viper.SetDefault("user.nick", "admin") viper.SetDefault("user.nick", "admin")
viper.SetDefault("user.password", "secret") viper.SetDefault("user.password", "secret")
viper.SetDefault("hooks.shell", "/bin/bash") viper.SetDefault("hooks.shell", "/bin/bash")
viper.SetDefault("hugo.frontmatter", []*frontmatter{{Meta: "title", Parameter: "title"}, {Meta: "tags", Parameter: "tags"}})
viper.SetDefault("micropub.categoryParam", "tags") viper.SetDefault("micropub.categoryParam", "tags")
viper.SetDefault("micropub.replyParam", "replylink") viper.SetDefault("micropub.replyParam", "replylink")
viper.SetDefault("micropub.likeParam", "likelink") viper.SetDefault("micropub.likeParam", "likelink")

View File

@ -3,7 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io/ioutil" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -103,6 +103,6 @@ func editorMicropubPost(w http.ResponseWriter, r *http.Request, media bool) {
return return
} }
w.WriteHeader(result.StatusCode) w.WriteHeader(result.StatusCode)
body, _ := ioutil.ReadAll(result.Body) _, _ = io.Copy(w, result.Body)
_, _ = w.Write(body) _ = result.Body.Close()
} }

View File

@ -66,16 +66,17 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
}) })
} }
var err error var err error
var feedString, feedMediaType string
switch f { switch f {
case rssFeed: case rssFeed:
w.Header().Set(contentType, "application/rss+xml; charset=utf-8") feedMediaType = contentTypeRSS
err = feed.WriteRss(w) feedString, err = feed.ToRss()
case atomFeed: case atomFeed:
w.Header().Set(contentType, "application/atom+xml; charset=utf-8") feedMediaType = contentTypeATOM
err = feed.WriteAtom(w) feedString, err = feed.ToAtom()
case jsonFeed: case jsonFeed:
w.Header().Set(contentType, "application/feed+json; charset=utf-8") feedMediaType = contentTypeJSONFeed
err = feed.WriteJSON(w) feedString, err = feed.ToJSON()
default: default:
return return
} }
@ -84,4 +85,6 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
serveError(w, r, err.Error(), http.StatusInternalServerError) serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
w.Header().Set(contentType, feedMediaType+charsetUtf8Suffix)
_, _ = writeMinified(w, feedMediaType, []byte(feedString))
} }

4
go.mod
View File

@ -20,7 +20,6 @@ require (
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/jeremywohl/flatten v1.0.1
github.com/jonboulle/clockwork v0.2.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5 github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
github.com/klauspost/cpuid v1.3.1 // indirect github.com/klauspost/cpuid v1.3.1 // indirect
@ -43,8 +42,7 @@ require (
github.com/spf13/cast v1.3.1 github.com/spf13/cast v1.3.1
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1 github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.12 github.com/tdewolff/minify/v2 v2.9.13
github.com/tdewolff/parse/v2 v2.5.10 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2 github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.2 github.com/yuin/goldmark v1.3.2

8
go.sum
View File

@ -142,8 +142,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -284,10 +282,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.12 h1:BKnbg3kcAuTLAhDeF3vSUxjvyj5lBRxaBvgxGCVLRRY= github.com/tdewolff/minify/v2 v2.9.13 h1:RrwQhgGoYBhKN/ezStGB+crU64wPK1ZE5Jmkl63lif0=
github.com/tdewolff/minify/v2 v2.9.12/go.mod h1:yuntVVAFuGyi9VmiRoUqAYEQnFCGO929ytj2ITMZuB8= github.com/tdewolff/minify/v2 v2.9.13/go.mod h1:faNOp+awAoo+fhFHD+NAkBOaXBAvJI2X2SDERGKnARo=
github.com/tdewolff/parse/v2 v2.5.9 h1:9wCXRT3OcgYDNatgU+HUTOoGhE9WcnY5UxLNoJUe1yw=
github.com/tdewolff/parse/v2 v2.5.9/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/parse/v2 v2.5.10 h1:vj35n+ljq8LuYUx436s4qB18wuwP7thrLv+t1syE39M= github.com/tdewolff/parse/v2 v2.5.10 h1:vj35n+ljq8LuYUx436s4qB18wuwP7thrLv+t1syE39M=
github.com/tdewolff/parse/v2 v2.5.10/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/parse/v2 v2.5.10/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=

253
http.go
View File

@ -23,6 +23,9 @@ const (
contentTypeWWWForm = "application/x-www-form-urlencoded" contentTypeWWWForm = "application/x-www-form-urlencoded"
contentTypeMultipartForm = "multipart/form-data" contentTypeMultipartForm = "multipart/form-data"
contentTypeAS = "application/activity+json" contentTypeAS = "application/activity+json"
contentTypeRSS = "application/rss+xml"
contentTypeATOM = "application/atom+xml"
contentTypeJSONFeed = "application/feed+json"
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
@ -99,15 +102,9 @@ func buildHandler() (http.Handler, error) {
r.Mount("/debug", middleware.Profiler()) r.Mount("/debug", middleware.Profiler())
} }
// API
r.Route("/api", func(apiRouter chi.Router) {
apiRouter.Use(middleware.NoCache, authMiddleware)
apiRouter.Post("/hugo", apiPostCreateHugo)
})
// Micropub // Micropub
r.Route(micropubPath, func(mpRouter chi.Router) { r.Route(micropubPath, func(mpRouter chi.Router) {
mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware) mpRouter.Use(checkIndieAuth)
mpRouter.Get("/", serveMicropubQuery) mpRouter.Get("/", serveMicropubQuery)
mpRouter.Post("/", serveMicropubPost) mpRouter.Post("/", serveMicropubPost)
mpRouter.Post(micropubMediaSubPath, serveMicropubMedia) mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
@ -115,7 +112,6 @@ func buildHandler() (http.Handler, error) {
// IndieAuth // IndieAuth
r.Route("/indieauth", func(indieauthRouter chi.Router) { r.Route("/indieauth", func(indieauthRouter chi.Router) {
indieauthRouter.Use(middleware.NoCache, minifier.Middleware)
indieauthRouter.Get("/", indieAuthRequest) indieauthRouter.Get("/", indieAuthRequest)
indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept) indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept)
indieauthRouter.Post("/", indieAuthVerification) indieauthRouter.Post("/", indieAuthVerification)
@ -124,23 +120,26 @@ func buildHandler() (http.Handler, error) {
}) })
// ActivityPub and stuff // ActivityPub and stuff
if appConfig.ActivityPub.Enabled { if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
r.Post("/activitypub/inbox/{blog}", apHandleInbox) r.Post("/activitypub/inbox/{blog}", apHandleInbox)
r.Post("/activitypub/{blog}/inbox", apHandleInbox) r.Post("/activitypub/{blog}/inbox", apHandleInbox)
r.Get("/.well-known/webfinger", apHandleWebfinger) r.With(cacheMiddleware).Get("/.well-known/webfinger", apHandleWebfinger)
r.With(cacheMiddleware).Get("/.well-known/host-meta", handleWellKnownHostMeta) r.With(cacheMiddleware).Get("/.well-known/host-meta", handleWellKnownHostMeta)
r.With(cacheMiddleware, minifier.Middleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover) r.With(cacheMiddleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover)
r.With(cacheMiddleware, minifier.Middleware).Get("/nodeinfo", serveNodeInfo) r.With(cacheMiddleware).Get("/nodeinfo", serveNodeInfo)
} }
// Webmentions // Webmentions
r.Route(webmentionPath, func(webmentionRouter chi.Router) { r.Route(webmentionPath, func(webmentionRouter chi.Router) {
webmentionRouter.Use(middleware.NoCache)
webmentionRouter.Post("/", handleWebmention) webmentionRouter.Post("/", handleWebmention)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin) webmentionRouter.Group(func(r chi.Router) {
webmentionRouter.With(minifier.Middleware, authMiddleware).Get(paginationPath, webmentionAdmin) // Authenticated routes
webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete) r.Use(authMiddleware)
webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove) r.Get("/", webmentionAdmin)
r.Get(paginationPath, webmentionAdmin)
r.Post("/delete", webmentionAdminDelete)
r.Post("/approve", webmentionAdminApprove)
})
}) })
// Posts // Posts
@ -148,39 +147,36 @@ func buildHandler() (http.Handler, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var postMW []func(http.Handler) http.Handler r.Group(func(r chi.Router) {
if appConfig.ActivityPub.Enabled { r.Use(manipulateAsPath, cacheMiddleware)
postMW = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware} for _, path := range pp {
} else { r.Get(path, servePost)
postMW = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
}
for _, path := range pp {
if path != "" {
r.With(postMW...).Get(path, servePost)
} }
} })
// Drafts // Drafts
dp, err := allPostPaths(statusDraft) dp, err := allPostPaths(statusDraft)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, path := range dp { r.Group(func(r chi.Router) {
if path != "" { r.Use(authMiddleware, cacheMiddleware)
r.With(middleware.NoCache, minifier.Middleware, authMiddleware).Get(path, servePost) for _, path := range dp {
r.Get(path, servePost)
} }
} })
// Post aliases // Post aliases
allPostAliases, err := allPostAliases() allPostAliases, err := allPostAliases()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, path := range allPostAliases { r.Group(func(r chi.Router) {
if path != "" { r.Use(cacheMiddleware)
r.With(cacheMiddleware).Get(path, servePostAlias) for _, path := range allPostAliases {
r.Get(path, servePostAlias)
} }
} })
// Assets // Assets
for _, path := range allAssetPaths() { for _, path := range allAssetPaths() {
@ -209,60 +205,79 @@ func buildHandler() (http.Handler, error) {
blogPath = "" blogPath = ""
} }
// Indexes, Feeds r.Group(func(r chi.Router) {
for _, section := range blogConfig.Sections {
if section.Name != "" {
path := blogPath + "/" + section.Name
handler := serveSection(blog, path, section)
r.With(cacheMiddleware, minifier.Middleware).Get(path, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, handler)
}
}
})
// Sections
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
for _, section := range blogConfig.Sections {
if section.Name != "" {
secPath := blogPath + "/" + section.Name
handler := serveSection(blog, secPath, section)
r.Get(secPath, handler)
r.Get(secPath+feedPath, handler)
r.Get(secPath+paginationPath, handler)
}
}
})
// Taxonomies
for _, taxonomy := range blogConfig.Taxonomies { for _, taxonomy := range blogConfig.Taxonomies {
if taxonomy.Name != "" { if taxonomy.Name != "" {
path := blogPath + "/" + taxonomy.Name taxPath := blogPath + "/" + taxonomy.Name
r.With(cacheMiddleware, minifier.Middleware).Get(path, serveTaxonomy(blog, taxonomy)) taxValues, err := allTaxonomyValues(blog, taxonomy.Name)
values, err := allTaxonomyValues(blog, taxonomy.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, tv := range values { r.Group(func(r chi.Router) {
vPath := path + "/" + urlize(tv) r.Use(cacheMiddleware)
handler := serveTaxonomyValue(blog, vPath, taxonomy, tv) r.Get(taxPath, serveTaxonomy(blog, taxonomy))
r.With(cacheMiddleware, minifier.Middleware).Get(vPath, handler) for _, tv := range taxValues {
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, handler) vPath := taxPath + "/" + urlize(tv)
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, handler) handler := serveTaxonomyValue(blog, vPath, taxonomy, tv)
} r.Get(vPath, handler)
r.Get(vPath+feedPath, handler)
r.Get(vPath+paginationPath, handler)
}
})
} }
} }
// Photos // Photos
if blogConfig.Photos != nil && blogConfig.Photos.Enabled { if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
photoPath := blogPath + blogConfig.Photos.Path r.Group(func(r chi.Router) {
handler := servePhotos(blog, photoPath) r.Use(cacheMiddleware)
r.With(cacheMiddleware, minifier.Middleware).Get(photoPath, handler) photoPath := blogPath + blogConfig.Photos.Path
r.With(cacheMiddleware, minifier.Middleware).Get(photoPath+paginationPath, handler) handler := servePhotos(blog, photoPath)
r.Get(photoPath, handler)
r.Get(photoPath+feedPath, handler)
r.Get(photoPath+paginationPath, handler)
})
} }
// Search // Search
if blogConfig.Search != nil && blogConfig.Search.Enabled { if blogConfig.Search != nil && blogConfig.Search.Enabled {
searchPath := blogPath + blogConfig.Search.Path r.Group(func(r chi.Router) {
handler := serveSearch(blog, searchPath) r.Use(cacheMiddleware)
r.With(cacheMiddleware, minifier.Middleware).Get(searchPath, handler) searchPath := blogPath + blogConfig.Search.Path
r.With(cacheMiddleware, minifier.Middleware).Post(searchPath, handler) handler := serveSearch(blog, searchPath)
searchResultPath := searchPath + "/" + searchPlaceholder r.Get(searchPath, handler)
resultHandler := serveSearchResults(blog, searchResultPath) r.Post(searchPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath, resultHandler) searchResultPath := searchPath + "/" + searchPlaceholder
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+feedPath, resultHandler) resultHandler := serveSearchResults(blog, searchResultPath)
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+paginationPath, resultHandler) r.Get(searchResultPath, resultHandler)
r.Get(searchResultPath+feedPath, resultHandler)
r.Get(searchResultPath+paginationPath, resultHandler)
})
} }
// Stats // Stats
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled { if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
statsPath := blogPath + blogConfig.BlogStats.Path statsPath := blogPath + blogConfig.BlogStats.Path
r.With(cacheMiddleware, minifier.Middleware).Get(statsPath, serveBlogStats(blog, statsPath)) r.With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath))
} }
// Year / month archives // Year / month archives
@ -270,60 +285,57 @@ func buildHandler() (http.Handler, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, d := range dates { r.Group(func(r chi.Router) {
// Year r.Use(cacheMiddleware)
yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year) for _, d := range dates {
yearHandler := serveDate(blog, yearPath, d.year, 0, 0) // Year
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath, yearHandler) yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year)
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+feedPath, yearHandler) yearHandler := serveDate(blog, yearPath, d.year, 0, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+paginationPath, yearHandler) r.Get(yearPath, yearHandler)
// Specific month r.Get(yearPath+feedPath, yearHandler)
monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month) r.Get(yearPath+paginationPath, yearHandler)
monthHandler := serveDate(blog, monthPath, d.year, d.month, 0) // Specific month
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath, monthHandler) monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month)
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+feedPath, monthHandler) monthHandler := serveDate(blog, monthPath, d.year, d.month, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+paginationPath, monthHandler) r.Get(monthPath, monthHandler)
// Specific day r.Get(monthPath+feedPath, monthHandler)
dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day) r.Get(monthPath+paginationPath, monthHandler)
dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day) // Specific day
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath, dayHandler) dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+feedPath, dayHandler) dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+paginationPath, dayHandler) r.Get(dayPath, dayHandler)
// Generic month r.Get(dayPath+feedPath, dayHandler)
genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month) r.Get(dayPath+paginationPath, dayHandler)
genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0) // Generic month
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath, genericMonthHandler) genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+feedPath, genericMonthHandler) genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+paginationPath, genericMonthHandler) r.Get(genericMonthPath, genericMonthHandler)
// Specific day r.Get(genericMonthPath+feedPath, genericMonthHandler)
genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day) r.Get(genericMonthPath+paginationPath, genericMonthHandler)
genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day) // Specific day
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath, genericMonthDayHandler) genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+feedPath, genericMonthDayHandler) genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+paginationPath, genericMonthDayHandler) r.Get(genericMonthDayPath, genericMonthDayHandler)
} r.Get(genericMonthDayPath+feedPath, genericMonthDayHandler)
r.Get(genericMonthDayPath+paginationPath, genericMonthDayHandler)
}
})
// Blog // Blog
if !blogConfig.PostAsHome { if !blogConfig.PostAsHome {
var mw []func(http.Handler) http.Handler
if appConfig.ActivityPub.Enabled {
mw = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware}
} else {
mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
}
handler := serveHome(blog, blogPath) handler := serveHome(blog, blogPath)
r.With(mw...).Get(fullBlogPath, handler) r.With(manipulateAsPath, cacheMiddleware).Get(fullBlogPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, handler) r.With(cacheMiddleware).Get(fullBlogPath+feedPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, handler) r.With(cacheMiddleware).Get(blogPath+paginationPath, handler)
} }
// Custom pages // Custom pages
for _, cp := range blogConfig.CustomPages { for _, cp := range blogConfig.CustomPages {
handler := serveCustomPage(blogConfig, cp) handler := serveCustomPage(blogConfig, cp)
if cp.Cache { if cp.Cache {
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, handler) r.With(cacheMiddleware).Get(cp.Path, handler)
} else { } else {
r.With(minifier.Middleware).Get(cp.Path, handler) r.Get(cp.Path, handler)
} }
} }
@ -338,7 +350,7 @@ func buildHandler() (http.Handler, error) {
// Editor // Editor
r.Route(blogPath+"/editor", func(mpRouter chi.Router) { r.Route(blogPath+"/editor", func(mpRouter chi.Router) {
mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware) mpRouter.Use(authMiddleware)
mpRouter.Get("/", serveEditor(blog)) mpRouter.Get("/", serveEditor(blog))
mpRouter.Post("/", serveEditorPost(blog)) mpRouter.Post("/", serveEditorPost(blog))
}) })
@ -347,25 +359,28 @@ func buildHandler() (http.Handler, error) {
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled { if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
commentsPath := blogPath + "/comment" commentsPath := blogPath + "/comment"
r.Route(commentsPath, func(cr chi.Router) { r.Route(commentsPath, func(cr chi.Router) {
cr.With(cacheMiddleware, minifier.Middleware).Get("/{id:[0-9]+}", serveComment(blog)) cr.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment(blog))
cr.With(captchaMiddleware).Post("/", createComment(blog, commentsPath)) cr.With(captchaMiddleware).Post("/", createComment(blog, commentsPath))
// Admin // Admin
cr.With(minifier.Middleware, authMiddleware).Get("/", commentsAdmin) cr.Group(func(r chi.Router) {
cr.With(authMiddleware).Post("/delete", commentsAdminDelete) r.Use(authMiddleware)
r.Get("/", commentsAdmin)
r.Post("/delete", commentsAdminDelete)
})
}) })
} }
} }
// Sitemap // Sitemap
r.With(cacheMiddleware, minifier.Middleware).Get(sitemapPath, serveSitemap) r.With(cacheMiddleware).Get(sitemapPath, serveSitemap)
// Robots.txt - doesn't need cache, because it's too simple // Robots.txt - doesn't need cache, because it's too simple
r.Get("/robots.txt", serveRobotsTXT) r.Get("/robots.txt", serveRobotsTXT)
// Check redirects, then serve 404 // Check redirects, then serve 404
r.With(cacheMiddleware, checkRegexRedirects, minifier.Middleware).NotFound(serve404) r.With(cacheMiddleware, checkRegexRedirects).NotFound(serve404)
r.With(minifier.Middleware).MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) { r.MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) {
serveError(rw, r, "", http.StatusMethodNotAllowed) serveError(rw, r, "", http.StatusMethodNotAllowed)
}) })

71
hugo.go
View File

@ -1,71 +0,0 @@
package main
import (
"strconv"
"strings"
"github.com/jeremywohl/flatten"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
)
func parseHugoFile(fileContent string) (p *post, aliases []string, e error) {
frontmatterSep := "---\n"
frontmatter := ""
if split := strings.Split(fileContent, frontmatterSep); len(split) > 2 {
frontmatter = split[1]
}
p = &post{
Content: strings.TrimPrefix(fileContent, frontmatterSep+frontmatter+frontmatterSep),
Parameters: map[string][]string{},
}
// Parse frontmatter
meta := map[string]interface{}{}
err := yaml.Unmarshal([]byte(frontmatter), &meta)
if err != nil {
return nil, nil, err
}
flat, err := flatten.Flatten(meta, "", flatten.DotStyle)
if err != nil {
return nil, nil, err
}
// Read dates
p.Published = cast.ToString(flat["date"])
p.Updated = cast.ToString(flat["lastmod"])
// Read parameters
for _, fm := range appConfig.Hugo.Frontmatter {
var values []string
for fk, value := range flat {
if strings.HasPrefix(fk, fm.Meta) {
trimmed := strings.TrimPrefix(fk, fm.Meta)
if len(trimmed) == 0 {
values = append(values, cast.ToString(value))
} else {
trimmed = strings.TrimPrefix(trimmed, ".")
if _, e := strconv.Atoi(trimmed); e == nil {
values = append(values, cast.ToString(value))
}
}
}
}
if len(values) > 0 {
p.Parameters[fm.Parameter] = values
}
}
// Parse redirects
for fk, value := range flat {
if strings.HasPrefix(fk, "aliases") {
trimmed := strings.TrimPrefix(fk, "aliases")
if len(trimmed) == 0 {
aliases = append(aliases, cast.ToString(value))
} else {
trimmed = strings.TrimPrefix(trimmed, ".")
if _, e := strconv.Atoi(trimmed); e == nil {
aliases = append(aliases, cast.ToString(value))
}
}
}
}
// Return post
return p, aliases, nil
}

View File

@ -134,16 +134,11 @@ func indieAuthVerification(w http.ResponseWriter, r *http.Request) {
serveError(w, r, "Authentication not valid", http.StatusForbidden) serveError(w, r, "Authentication not valid", http.StatusForbidden)
return return
} }
res := &tokenResponse{ b, _ := json.Marshal(tokenResponse{
Me: appConfig.Server.PublicAddress, Me: appConfig.Server.PublicAddress,
} })
w.Header().Add(contentType, contentTypeJSONUTF8) w.Header().Set(contentType, contentTypeJSONUTF8)
err = json.NewEncoder(w).Encode(res) _, _ = writeMinified(w, contentTypeJSON, b)
if err != nil {
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
} }
func indieAuthToken(w http.ResponseWriter, r *http.Request) { func indieAuthToken(w http.ResponseWriter, r *http.Request) {
@ -159,13 +154,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) {
Me: appConfig.Server.PublicAddress, Me: appConfig.Server.PublicAddress,
ClientID: data.ClientID, ClientID: data.ClientID,
} }
w.Header().Add(contentType, contentTypeJSONUTF8) b, _ := json.Marshal(res)
err = json.NewEncoder(w).Encode(res) w.Header().Set(contentType, contentTypeJSONUTF8)
if err != nil { _, _ = writeMinified(w, contentTypeJSON, b)
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
return return
} else if r.Method == http.MethodPost { } else if r.Method == http.MethodPost {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
@ -216,13 +207,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) {
Scope: strings.Join(data.Scopes, " "), Scope: strings.Join(data.Scopes, " "),
Me: appConfig.Server.PublicAddress, Me: appConfig.Server.PublicAddress,
} }
w.Header().Add(contentType, contentTypeJSONUTF8) b, _ := json.Marshal(res)
err = json.NewEncoder(w).Encode(res) w.Header().Set(contentType, contentTypeJSONUTF8)
if err != nil { _, _ = writeMinified(w, contentTypeJSON, b)
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
return return
} }
serveError(w, r, "", http.StatusBadRequest) serveError(w, r, "", http.StatusBadRequest)

View File

@ -25,11 +25,12 @@ type micropubConfig struct {
func serveMicropubQuery(w http.ResponseWriter, r *http.Request) { func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
switch r.URL.Query().Get("q") { switch r.URL.Query().Get("q") {
case "config": case "config":
w.Header().Add(contentType, contentTypeJSONUTF8) w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(&micropubConfig{ b, _ := json.Marshal(&micropubConfig{
MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath, MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath,
}) })
_, _ = writeMinified(w, contentTypeJSON, b)
case "source": case "source":
var mf interface{} var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" { if urlString := r.URL.Query().Get("url"); urlString != "" {
@ -61,9 +62,10 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
} }
mf = list mf = list
} }
w.Header().Add(contentType, contentTypeJSONUTF8) w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(mf) b, _ := json.Marshal(mf)
_, _ = writeMinified(w, contentTypeJSON, b)
case "category": case "category":
allCategories := []string{} allCategories := []string{}
for blog := range appConfig.Blogs { for blog := range appConfig.Blogs {
@ -74,11 +76,12 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
} }
allCategories = append(allCategories, values...) allCategories = append(allCategories, values...)
} }
w.Header().Add(contentType, contentTypeJSONUTF8) w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]interface{}{ b, _ := json.Marshal(map[string]interface{}{
"categories": allCategories, "categories": allCategories,
}) })
_, _ = writeMinified(w, contentTypeJSON, b)
default: default:
serve404(w, r) serve404(w, r)
} }

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"io"
"github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2"
mCss "github.com/tdewolff/minify/v2/css" mCss "github.com/tdewolff/minify/v2/css"
mHtml "github.com/tdewolff/minify/v2/html" mHtml "github.com/tdewolff/minify/v2/html"
@ -17,7 +19,14 @@ func initMinify() {
minifier.AddFunc("text/css", mCss.Minify) minifier.AddFunc("text/css", mCss.Minify)
minifier.AddFunc("text/xml", mXml.Minify) minifier.AddFunc("text/xml", mXml.Minify)
minifier.AddFunc("application/javascript", mJs.Minify) minifier.AddFunc("application/javascript", mJs.Minify)
minifier.AddFunc("application/rss+xml", mXml.Minify) minifier.AddFunc(contentTypeRSS, mXml.Minify)
minifier.AddFunc("application/atom+xml", mXml.Minify) minifier.AddFunc(contentTypeATOM, mXml.Minify)
minifier.AddFunc("application/feed+json", mJson.Minify) minifier.AddFunc(contentTypeJSONFeed, mJson.Minify)
minifier.AddFunc(contentTypeAS, mJson.Minify)
}
func writeMinified(w io.Writer, mediatype string, b []byte) (int, error) {
mw := minifier.Writer(mediatype, w)
defer func() { mw.Close() }()
return mw.Write(b)
} }

View File

@ -6,24 +6,23 @@ import (
) )
func serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) { func serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentType, contentTypeJSONUTF8) b, _ := json.Marshal(map[string]interface{}{
nid := map[string]interface{}{
"links": []map[string]interface{}{ "links": []map[string]interface{}{
{ {
"href": appConfig.Server.PublicAddress + "/nodeinfo", "href": appConfig.Server.PublicAddress + "/nodeinfo",
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1",
}, },
}, },
} })
_ = json.NewEncoder(w).Encode(&nid) w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
} }
func serveNodeInfo(w http.ResponseWriter, r *http.Request) { func serveNodeInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentType, contentTypeJSONUTF8)
localPosts, _ := countPosts(&postsRequestConfig{ localPosts, _ := countPosts(&postsRequestConfig{
status: statusPublished, status: statusPublished,
}) })
nid := map[string]interface{}{ b, _ := json.Marshal(map[string]interface{}{
"version": "2.1", "version": "2.1",
"software": map[string]interface{}{ "software": map[string]interface{}{
"name": "goblog", "name": "goblog",
@ -41,6 +40,7 @@ func serveNodeInfo(w http.ResponseWriter, r *http.Request) {
"webmention", "webmention",
}, },
"metadata": map[string]interface{}{}, "metadata": map[string]interface{}{},
} })
_ = json.NewEncoder(w).Encode(&nid) w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
} }

View File

@ -14,7 +14,9 @@ func allPostAliases() ([]string, error) {
for rows.Next() { for rows.Next() {
var path string var path string
_ = rows.Scan(&path) _ = rows.Scan(&path)
aliases = append(aliases, path) if path != "" {
aliases = append(aliases, path)
}
} }
return aliases, nil return aliases, nil
} }

View File

@ -396,7 +396,9 @@ func allPostPaths(status postStatus) ([]string, error) {
for rows.Next() { for rows.Next() {
var path string var path string
_ = rows.Scan(&path) _ = rows.Scan(&path)
postPaths = append(postPaths, path) if path != "" {
postPaths = append(postPaths, path)
}
} }
return postPaths, nil return postPaths, nil
} }

View File

@ -264,8 +264,8 @@ func render(w http.ResponseWriter, template string, data *renderData) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// Set content type (needed for minification middleware // Set content type
w.Header().Set(contentType, contentTypeHTMLUTF8) w.Header().Set(contentType, contentTypeHTMLUTF8)
// Write buffered response // Write buffered response
_, _ = w.Write(buffer.Bytes()) _, _ = writeMinified(w, contentTypeHTML, buffer.Bytes())
} }

View File

@ -35,5 +35,5 @@ func serveSitemap(w http.ResponseWriter, r *http.Request) {
} }
sm.Add(item) sm.Add(item)
} }
_, _ = sm.WriteTo(w) _, _ = sm.WriteTo(w) // Already minified
} }

View File

@ -98,6 +98,6 @@ func serveAsset(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Header().Set("Cache-Control", "public,max-age=31536000,immutable") w.Header().Set("Cache-Control", "public,max-age=31536000,immutable")
w.Header().Set(contentType, af.contentType) w.Header().Set(contentType, af.contentType+charsetUtf8Suffix)
_, _ = w.Write(af.body) _, _ = w.Write(af.body)
} }