mirror of https://github.com/jlelse/GoBlog
More improvements (less buffers, some fixes)
parent
51eaf24ff2
commit
222c792908
|
@ -7,8 +7,10 @@ import (
|
|||
"database/sql"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -242,7 +244,8 @@ func (a *goBlog) apVerifySignature(r *http.Request) (*asPerson, string, int, err
|
|||
|
||||
func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(contentType, "application/xrd+xml"+contenttype.CharsetUtf8Suffix)
|
||||
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="https://` + r.Host + `/.well-known/webfinger?resource={uri}"/></XRD>`))
|
||||
_, _ = io.WriteString(w, xml.Header)
|
||||
_, _ = io.WriteString(w, `<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="https://`+r.Host+`/.well-known/webfinger?resource={uri}"/></XRD>`)
|
||||
}
|
||||
|
||||
func (a *goBlog) apGetRemoteActor(iri string) (*asPerson, int, error) {
|
||||
|
|
14
cache.go
14
cache.go
|
@ -179,21 +179,23 @@ func (c *cache) getCache(key string, next http.Handler, r *http.Request) (item *
|
|||
// Record request
|
||||
recorder := httptest.NewRecorder()
|
||||
next.ServeHTTP(recorder, cr)
|
||||
// Cache values from recorder
|
||||
recorder.Flush()
|
||||
// Cache result
|
||||
result := recorder.Result()
|
||||
eTag := sha256.New()
|
||||
body, _ := io.ReadAll(io.TeeReader(result.Body, eTag))
|
||||
headers := result.Header.Clone()
|
||||
_ = result.Body.Close()
|
||||
lastMod := time.Now()
|
||||
if lm := result.Header.Get(lastModified); lm != "" {
|
||||
if lm := headers.Get(lastModified); lm != "" {
|
||||
if parsedTime, te := dateparse.ParseLocal(lm); te == nil {
|
||||
lastMod = parsedTime
|
||||
}
|
||||
}
|
||||
// Remove problematic headers
|
||||
result.Header.Del("Accept-Ranges")
|
||||
result.Header.Del("ETag")
|
||||
result.Header.Del(lastModified)
|
||||
headers.Del("Accept-Ranges")
|
||||
headers.Del("ETag")
|
||||
headers.Del(lastModified)
|
||||
// Create cache item
|
||||
exp, _ := cr.Context().Value(cacheExpirationKey).(int)
|
||||
item = &cacheItem{
|
||||
|
@ -201,7 +203,7 @@ func (c *cache) getCache(key string, next http.Handler, r *http.Request) (item *
|
|||
creationTime: lastMod,
|
||||
eTag: fmt.Sprintf("%x", eTag.Sum(nil)),
|
||||
code: result.StatusCode,
|
||||
header: result.Header,
|
||||
header: headers,
|
||||
body: body,
|
||||
}
|
||||
// Save cache
|
||||
|
|
30
editor.go
30
editor.go
|
@ -11,7 +11,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
"go.goblog.app/app/pkgs/contenttype"
|
||||
"gopkg.in/yaml.v3"
|
||||
ws "nhooyr.io/websocket"
|
||||
|
@ -93,22 +92,21 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
|||
},
|
||||
})
|
||||
case "updatepost":
|
||||
jsonBuf := bufferpool.Get()
|
||||
defer bufferpool.Put(jsonBuf)
|
||||
err := json.NewEncoder(jsonBuf).Encode(map[string]interface{}{
|
||||
"action": actionUpdate,
|
||||
"url": r.FormValue("url"),
|
||||
"replace": map[string][]string{
|
||||
"content": {
|
||||
r.FormValue("content"),
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
defer pipeReader.Close()
|
||||
go func() {
|
||||
writeErr := json.NewEncoder(pipeWriter).Encode(map[string]interface{}{
|
||||
"action": actionUpdate,
|
||||
"url": r.FormValue("url"),
|
||||
"replace": map[string][]string{
|
||||
"content": {
|
||||
r.FormValue("content"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, "", jsonBuf)
|
||||
})
|
||||
_ = pipeWriter.CloseWithError(writeErr)
|
||||
}()
|
||||
req, err := http.NewRequest(http.MethodPost, "", pipeReader)
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
23
feeds.go
23
feeds.go
|
@ -1,7 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -52,27 +52,28 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
|
|||
})
|
||||
bufferpool.Put(buf)
|
||||
}
|
||||
var err error
|
||||
var feedBuffer bytes.Buffer
|
||||
var feedWriteFunc func(w io.Writer) error
|
||||
var feedMediaType string
|
||||
switch f {
|
||||
case rssFeed:
|
||||
feedMediaType = contenttype.RSS
|
||||
err = feed.WriteRss(&feedBuffer)
|
||||
feedWriteFunc = feed.WriteRss
|
||||
case atomFeed:
|
||||
feedMediaType = contenttype.ATOM
|
||||
err = feed.WriteAtom(&feedBuffer)
|
||||
feedWriteFunc = feed.WriteAtom
|
||||
case jsonFeed:
|
||||
feedMediaType = contenttype.JSONFeed
|
||||
err = feed.WriteJSON(&feedBuffer)
|
||||
feedWriteFunc = feed.WriteJSON
|
||||
default:
|
||||
a.serve404(w, r)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
go func() {
|
||||
writeErr := feedWriteFunc(pipeWriter)
|
||||
_ = pipeWriter.CloseWithError(writeErr)
|
||||
}()
|
||||
w.Header().Set(contentType, feedMediaType+contenttype.CharsetUtf8Suffix)
|
||||
_ = a.min.Minify(feedMediaType, w, &feedBuffer)
|
||||
minifyErr := a.min.Minify(feedMediaType, w, pipeReader)
|
||||
_ = pipeReader.CloseWithError(minifyErr)
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -52,7 +52,7 @@ require (
|
|||
github.com/tkrajina/gpxgo v1.2.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
||||
github.com/yuin/goldmark v1.4.5
|
||||
github.com/yuin/goldmark v1.4.6
|
||||
// master
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||
|
|
3
go.sum
3
go.sum
|
@ -447,8 +447,9 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.5 h1:4OEQwtW2uLXjEdgnGM3Vg652Pq37X7NOIRzFWb3BzIc=
|
||||
github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.6 h1:EQ1OkiNq/eMbQxs/2O/A8VDIHERXGH14s19ednd4XIw=
|
||||
github.com/yuin/goldmark v1.4.6/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38 h1:XZjLcLoTPNZuxppY3gwhRqo/T2XF6JMGFFdkAjX3w1w=
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg=
|
||||
|
|
|
@ -1,26 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.goblog.app/app/pkgs/contenttype"
|
||||
)
|
||||
|
||||
type openSearchDescription struct {
|
||||
XMLName xml.Name `xml:"http://a9.com/-/spec/opensearch/1.1/ OpenSearchDescription"`
|
||||
Text string `xml:",chardata"`
|
||||
ShortName string `xml:"ShortName"`
|
||||
Description string `xml:"Description"`
|
||||
URL *openSearchDescriptionUrl `xml:"Url"`
|
||||
SearchForm string `xml:"http://www.mozilla.org/2006/browser/search/ SearchForm"`
|
||||
}
|
||||
|
||||
type openSearchDescriptionUrl struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
Method string `xml:"method,attr"`
|
||||
Template string `xml:"template,attr"`
|
||||
Param *openSearchDescriptionUrlParam `xml:"Param"`
|
||||
}
|
||||
|
||||
type openSearchDescriptionUrlParam struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
|
||||
_, b := a.getBlog(r)
|
||||
title := a.renderMdTitle(b.Title)
|
||||
sURL := a.getFullAddress(b.getRelativePath(defaultIfEmpty(b.Search.Path, defaultSearchPath)))
|
||||
var buf bytes.Buffer
|
||||
_, _ = fmt.Fprintf(&buf, "<?xml version=\"1.0\"?><OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">"+
|
||||
"<ShortName>%s</ShortName><Description>%s</Description>"+
|
||||
"<Url type=\"text/html\" method=\"post\" template=\"%s\"><Param name=\"q\" value=\"{searchTerms}\" /></Url>"+
|
||||
"<moz:SearchForm>%s</moz:SearchForm>"+
|
||||
"</OpenSearchDescription>",
|
||||
title, title, sURL, sURL)
|
||||
w.Header().Set(contentType, "application/opensearchdescription+xml")
|
||||
_ = a.min.Minify(contenttype.XML, w, &buf)
|
||||
w.Header().Set(contentType, "application/opensearchdescription+xml"+contenttype.CharsetUtf8Suffix)
|
||||
openSearch := &openSearchDescription{
|
||||
ShortName: title,
|
||||
Description: title,
|
||||
URL: &openSearchDescriptionUrl{
|
||||
Type: "text/html",
|
||||
Method: "post",
|
||||
Template: sURL,
|
||||
Param: &openSearchDescriptionUrlParam{
|
||||
Name: "q",
|
||||
Value: "{searchTerms}",
|
||||
},
|
||||
},
|
||||
SearchForm: sURL,
|
||||
}
|
||||
mw := a.min.Writer(contenttype.XML, w)
|
||||
_, _ = io.WriteString(mw, xml.Header)
|
||||
_ = xml.NewEncoder(mw).Encode(openSearch)
|
||||
_ = mw.Close()
|
||||
}
|
||||
|
||||
func openSearchUrl(b *configBlog) string {
|
||||
|
|
|
@ -7,10 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func (a *goBlog) isPrivate() bool {
|
||||
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return a.cfg.PrivateMode != nil && a.cfg.PrivateMode.Enabled
|
||||
}
|
||||
|
||||
func (a *goBlog) privateModeHandler(next http.Handler) http.Handler {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
|
@ -42,11 +41,9 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
|
|||
// Render
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
go func() {
|
||||
bufferedPipeWriter := bufio.NewWriter(pipeWriter)
|
||||
minifyWriter := a.min.Writer(contenttype.HTML, bufferedPipeWriter)
|
||||
minifyWriter := a.min.Writer(contenttype.HTML, pipeWriter)
|
||||
f(newHtmlBuilder(minifyWriter), data)
|
||||
_ = minifyWriter.Close()
|
||||
_ = bufferedPipeWriter.Flush()
|
||||
_ = pipeWriter.Close()
|
||||
}()
|
||||
_, readErr := io.Copy(w, pipeReader)
|
||||
|
|
|
@ -8,9 +8,14 @@ import (
|
|||
const robotsTXTPath = "/robots.txt"
|
||||
|
||||
func (a *goBlog) serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprint(w, "User-agent: *\n")
|
||||
if a.isPrivate() {
|
||||
_, _ = w.Write([]byte("User-agent: *\nDisallow: /"))
|
||||
_, _ = fmt.Fprint(w, "Disallow: /\n")
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(fmt.Sprintf("User-agent: *\nSitemap: %v", a.getFullAddress(sitemapPath))))
|
||||
_, _ = fmt.Fprint(w, "Allow: /\n\n")
|
||||
_, _ = fmt.Fprintf(w, "Sitemap: %s\n", a.getFullAddress(sitemapPath))
|
||||
for _, bc := range a.cfg.Blogs {
|
||||
_, _ = fmt.Fprintf(w, "Sitemap: %s\n", a.getFullAddress(bc.getRelativePath(sitemapBlogPath)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -17,18 +18,21 @@ func Test_robotsTXT(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
h := http.HandlerFunc(app.serveRobotsTXT)
|
||||
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
||||
txt := assert.HTTPBody(h, http.MethodGet, "", nil)
|
||||
assert.Equal(t, "User-agent: *\nSitemap: https://example.com/sitemap.xml", txt)
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/robots.txt", nil)
|
||||
app.serveRobotsTXT(rec, req)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml\n", rec.Body.String())
|
||||
|
||||
app.cfg.PrivateMode = &configPrivateMode{
|
||||
Enabled: true,
|
||||
}
|
||||
assert.True(t, app.isPrivate())
|
||||
|
||||
h = http.HandlerFunc(app.serveRobotsTXT)
|
||||
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
||||
txt = assert.HTTPBody(h, http.MethodGet, "", nil)
|
||||
assert.Equal(t, "User-agent: *\nDisallow: /", txt)
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest("GET", "/robots.txt", nil)
|
||||
app.serveRobotsTXT(rec, req)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "User-agent: *\nDisallow: /\n", rec.Body.String())
|
||||
|
||||
}
|
||||
|
|
22
sitemap.go
22
sitemap.go
|
@ -1,9 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -174,13 +174,19 @@ func (a *goBlog) serveSitemapBlogPosts(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (a *goBlog) writeSitemapXML(w http.ResponseWriter, sm interface{}) {
|
||||
w.Header().Set(contentType, contenttype.XMLUTF8)
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xml.Header)
|
||||
buf.WriteString(`<?xml-stylesheet type="text/xsl" href="`)
|
||||
buf.WriteString(a.assetFileName("sitemap.xsl"))
|
||||
buf.WriteString(`" ?>`)
|
||||
_ = xml.NewEncoder(&buf).Encode(sm)
|
||||
_ = a.min.Minify(contenttype.XML, w, &buf)
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
go func() {
|
||||
mw := a.min.Writer(contenttype.XML, pipeWriter)
|
||||
_, _ = io.WriteString(mw, xml.Header)
|
||||
_, _ = io.WriteString(mw, `<?xml-stylesheet type="text/xsl" href="`)
|
||||
_, _ = io.WriteString(mw, a.assetFileName("sitemap.xsl"))
|
||||
_, _ = io.WriteString(mw, `" ?>`)
|
||||
writeErr := xml.NewEncoder(mw).Encode(sm)
|
||||
_ = mw.Close()
|
||||
_ = pipeWriter.CloseWithError(writeErr)
|
||||
}()
|
||||
_, copyErr := io.Copy(w, pipeReader)
|
||||
_ = pipeReader.CloseWithError(copyErr)
|
||||
}
|
||||
|
||||
const sitemapDatePathsSql = `
|
||||
|
|
Loading…
Reference in New Issue