More optimizations

This commit is contained in:
Jan-Lukas Else 2022-02-12 00:48:59 +01:00
parent 6e767e3612
commit d8caf1e6f5
11 changed files with 95 additions and 93 deletions

View File

@ -6,7 +6,6 @@ import (
"encoding/gob" "encoding/gob"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@ -120,8 +119,7 @@ func (a *goBlog) apSendSigned(blogIri, to string, activity []byte) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
if !apRequestIsSuccess(resp.StatusCode) { if !apRequestIsSuccess(resp.StatusCode) {
body, _ := io.ReadAll(resp.Body) return fmt.Errorf("signed request failed with status %d", resp.StatusCode)
return fmt.Errorf("signed request failed with status %d: %s", resp.StatusCode, string(body))
} }
return nil return nil
} }

View File

@ -55,7 +55,7 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
} }
// Encode original request // Encode original request
h, _ := json.Marshal(r.Header) h, _ := json.Marshal(r.Header)
b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB b, _ := io.ReadAll(io.LimitReader(r.Body, 20000000)) // Only allow 20 MB
_ = r.Body.Close() _ = r.Body.Close()
if len(b) == 0 { if len(b) == 0 {
// Maybe it's a form // Maybe it's a form

View File

@ -36,7 +36,7 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) {
defer cancel() defer cancel()
for { for {
// Retrieve content // Retrieve content
mt, message, err := c.Read(ctx) mt, message, err := c.Reader(ctx)
if err != nil { if err != nil {
break break
} }
@ -48,21 +48,24 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
break break
} }
a.createMarkdownPreview(w, blog, string(message)) a.createMarkdownPreview(w, blog, message)
err = w.Close() if err = w.Close(); err != nil {
if err != nil {
break break
} }
} }
} }
func (a *goBlog) createMarkdownPreview(w io.Writer, blog string, markdown string) { func (a *goBlog) createMarkdownPreview(w io.Writer, blog string, markdown io.Reader) {
md, err := io.ReadAll(markdown)
if err != nil {
_, _ = io.WriteString(w, err.Error())
return
}
p := &post{ p := &post{
Blog: blog, Blog: blog,
Content: markdown, Content: string(md),
} }
err := a.computeExtraPostParameters(p) if err = a.computeExtraPostParameters(p); err != nil {
if err != nil {
_, _ = io.WriteString(w, err.Error()) _, _ = io.WriteString(w, err.Error())
return return
} }

View File

@ -15,7 +15,6 @@ import (
"github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
"go.goblog.app/app/pkgs/bufferpool"
) )
func (a *goBlog) initMarkdown() { func (a *goBlog) initMarkdown() {
@ -76,14 +75,6 @@ func (a *goBlog) initMarkdown() {
) )
} }
func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) {
buffer := bufferpool.Get()
err = a.renderMarkdownToWriter(buffer, source, absoluteLinks)
rendered = buffer.Bytes()
bufferpool.Put(buffer)
return
}
func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) { func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) {
if absoluteLinks { if absoluteLinks {
err = a.absoluteMd.Convert([]byte(source), w) err = a.absoluteMd.Convert([]byte(source), w)
@ -97,24 +88,34 @@ func (a *goBlog) renderText(s string) string {
if s == "" { if s == "" {
return "" return ""
} }
h, err := a.renderMarkdown(s, false) pipeReader, pipeWriter := io.Pipe()
var err error
go func() {
err = a.renderMarkdownToWriter(pipeWriter, s, false)
_ = pipeWriter.Close()
}()
text := htmlTextFromReader(pipeReader)
if err != nil { if err != nil {
return "" return ""
} }
return htmlText(string(h)) return text
} }
func (a *goBlog) renderMdTitle(s string) string { func (a *goBlog) renderMdTitle(s string) string {
if s == "" { if s == "" {
return "" return ""
} }
buffer := bufferpool.Get() pipeReader, pipeWriter := io.Pipe()
defer bufferpool.Put(buffer) var err error
err := a.titleMd.Convert([]byte(s), buffer) go func() {
err = a.titleMd.Convert([]byte(s), pipeWriter)
_ = pipeWriter.Close()
}()
text := htmlTextFromReader(pipeReader)
if err != nil { if err != nil {
return "" return ""
} }
return htmlText(buffer.String()) return text
} }
// Extensions etc... // Extensions etc...
@ -177,27 +178,24 @@ func (c *customRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
n := node.(*ast.Image) n := node.(*ast.Image)
// Make URL absolute if it's relative dest := string(n.Destination)
destination := util.URLEscape(n.Destination, true) // Make destination absolute if it's relative
if c.absoluteLinks && c.publicAddress != "" && bytes.HasPrefix(destination, []byte("/")) { if c.absoluteLinks && c.publicAddress != "" {
destination = util.EscapeHTML(append([]byte(c.publicAddress), destination...)) resolved, err := resolveURLReferences(c.publicAddress, dest)
} else { if err != nil {
destination = util.EscapeHTML(destination) return ast.WalkStop, err
}
if len(resolved) > 0 {
dest = resolved[0]
}
} }
_, _ = w.WriteString("<a href=\"") hb := newHtmlBuilder(w)
_, _ = w.Write(destination) hb.writeElementOpen("a", "href", dest)
_, _ = w.WriteString("\">") imgEls := []interface{}{"src", dest, "alt", string(n.Text(source)), "loading", "lazy"}
_, _ = w.WriteString("<img src=\"") if len(n.Title) > 0 {
_, _ = w.Write(destination) imgEls = append(imgEls, "title", string(n.Title))
_, _ = w.WriteString("\" alt=\"")
_, _ = w.Write(util.EscapeHTML(n.Text(source)))
_ = w.WriteByte('"')
_, _ = w.WriteString(" loading=\"lazy\"")
if n.Title != nil {
_, _ = w.WriteString(" title=\"")
_, _ = w.Write(n.Title)
_ = w.WriteByte('"')
} }
_, _ = w.WriteString("></a>") hb.writeElementOpen("img", imgEls...)
hb.writeElementClose("a")
return ast.WalkSkipChildren, nil return ast.WalkSkipChildren, nil
} }

View File

@ -6,8 +6,17 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.goblog.app/app/pkgs/bufferpool"
) )
func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) {
buffer := bufferpool.Get()
err = a.renderMarkdownToWriter(buffer, source, absoluteLinks)
rendered = buffer.Bytes()
bufferpool.Put(buffer)
return
}
func Test_markdown(t *testing.T) { func Test_markdown(t *testing.T) {
t.Run("Basic Markdown tests", func(t *testing.T) { t.Run("Basic Markdown tests", func(t *testing.T) {
app := &goBlog{ app := &goBlog{

View File

@ -15,7 +15,7 @@ import (
func Test_compress(t *testing.T) { func Test_compress(t *testing.T) {
fakeFileContent := "Test" fakeFileContent := "Test"
hash := sha256.New() hash := sha256.New()
io.WriteString(hash, fakeFileContent) _, _ = io.WriteString(hash, fakeFileContent)
fakeSha256 := fmt.Sprintf("%x", hash.Sum(nil)) fakeSha256 := fmt.Sprintf("%x", hash.Sum(nil))
var uf mediaStorageSaveFunc = func(filename string, f io.Reader) (location string, err error) { var uf mediaStorageSaveFunc = func(filename string, f io.Reader) (location string, err error) {

View File

@ -113,8 +113,8 @@ func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
a.micropubCreatePostFromForm(w, r) a.micropubCreatePostFromForm(w, r)
case contenttype.JSON: case contenttype.JSON:
parsedMfItem := &microformatItem{} parsedMfItem := &microformatItem{}
b, _ := io.ReadAll(io.LimitReader(r.Body, 10000000)) // 10 MB err := json.NewDecoder(io.LimitReader(r.Body, 10000000)).Decode(parsedMfItem)
if err := json.Unmarshal(b, parsedMfItem); err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }

View File

@ -93,13 +93,14 @@ func (a *goBlog) postSummary(p *post) (summary string) {
if summary != "" { if summary != "" {
return return
} }
if splitted := strings.Split(p.Content, summaryDivider); len(splitted) > 1 { splitted := strings.Split(p.Content, summaryDivider)
rendered, _ := a.renderMarkdown(splitted[0], false) hasDivider := len(splitted) > 1
summary = htmlText(string(rendered)) markdown := splitted[0]
} else { summary = a.renderText(markdown)
rendered, _ := a.renderMarkdown(splitted[0], false) if !hasDivider {
summary = strings.Split(htmlText(string(rendered)), "\n\n")[0] summary = strings.Split(summary, "\n\n")[0]
} }
summary = strings.TrimSpace(strings.ReplaceAll(summary, "\n\n", " "))
return return
} }

View File

@ -1,9 +1,9 @@
package main package main
import ( import (
"io"
"net/http" "net/http"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
) )
@ -39,13 +39,14 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
// Write status code // Write status code
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
// Render // Render
buf := bufferpool.Get() pipeReader, pipeWriter := io.Pipe()
mw := a.min.Writer(contenttype.HTML, buf) go func() {
hb := newHtmlBuilder(mw) mw := a.min.Writer(contenttype.HTML, pipeWriter)
f(hb, data) f(newHtmlBuilder(mw), data)
_ = mw.Close() _ = mw.Close()
_, _ = buf.WriteTo(w) _ = pipeWriter.Close()
bufferpool.Put(buf) }()
_, _ = io.Copy(w, pipeReader)
} }
func (a *goBlog) checkRenderData(r *http.Request, data *renderData) { func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io" "io"
@ -34,15 +33,11 @@ func (a *goBlog) initTemplateAssets() error {
return err return err
} }
// Compile asset and close file // Compile asset and close file
compiled, err := a.compileAsset(path, file) err = a.compileAsset(strings.TrimPrefix(path, assetsFolder+"/"), file)
_ = file.Close() _ = file.Close()
if err != nil { if err != nil {
return err return err
} }
// Add to map
if compiled != "" {
a.assetFileNames[strings.TrimPrefix(path, assetsFolder+"/")] = compiled
}
} }
return nil return nil
}); err != nil { }); err != nil {
@ -55,7 +50,7 @@ func (a *goBlog) initTemplateAssets() error {
return nil return nil
} }
func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) { func (a *goBlog) compileAsset(name string, read io.Reader) error {
ext := path.Ext(name) ext := path.Ext(name)
switch ext { switch ext {
case ".js": case ".js":
@ -69,16 +64,18 @@ func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) {
hash := sha256.New() hash := sha256.New()
body, err := io.ReadAll(io.TeeReader(read, hash)) body, err := io.ReadAll(io.TeeReader(read, hash))
if err != nil { if err != nil {
return "", err return err
} }
// File name // File name
compiledFileName := fmt.Sprintf("%x%s", hash.Sum(nil), ext) compiledFileName := fmt.Sprintf("%x%s", hash.Sum(nil), ext)
// Create struct // Save file
a.assetFiles[compiledFileName] = &assetFile{ a.assetFiles[compiledFileName] = &assetFile{
contentType: mime.TypeByExtension(ext), contentType: mime.TypeByExtension(ext),
body: body, body: body,
} }
return compiledFileName, err // Save mapping of original file name to compiled file name
a.assetFileNames[name] = compiledFileName
return err
} }
// Function for templates // Function for templates
@ -113,22 +110,15 @@ func (a *goBlog) initChromaCSS() error {
return nil return nil
} }
// Initialize the style // Initialize the style
chromaStyleBuilder := chromaGoBlogStyle.Builder() chromaStyle, err := chromaGoBlogStyle.Builder().Build()
chromaStyle, err := chromaStyleBuilder.Build()
if err != nil { if err != nil {
return err return err
} }
// Write the CSS to a buffer // Generate and minify CSS
var cssBuffer bytes.Buffer pipeReader, pipeWriter := io.Pipe()
if err = chromahtml.New(chromahtml.ClassPrefix("c-")).WriteCSS(&cssBuffer, chromaStyle); err != nil { go func() {
return err _ = chromahtml.New(chromahtml.ClassPrefix("c-")).WriteCSS(pipeWriter, chromaStyle)
} _ = pipeWriter.Close()
// Compile asset }()
compiled, err := a.compileAsset(chromaPath, &cssBuffer) return a.compileAsset(chromaPath, pipeReader)
if err != nil {
return err
}
// Add to map
a.assetFileNames[chromaPath] = compiled
return nil
} }

View File

@ -221,16 +221,18 @@ func mBytesString(size int64) string {
} }
func htmlText(s string) string { func htmlText(s string) string {
return htmlTextFromReader(strings.NewReader(s))
}
func htmlTextFromReader(r io.Reader) string {
// Build policy to only allow a subset of HTML tags // Build policy to only allow a subset of HTML tags
textPolicy := bluemonday.StrictPolicy() textPolicy := bluemonday.StrictPolicy()
textPolicy.AllowElements("h1", "h2", "h3", "h4", "h5", "h6") // Headers textPolicy.AllowElements("h1", "h2", "h3", "h4", "h5", "h6") // Headers
textPolicy.AllowElements("p") // Paragraphs textPolicy.AllowElements("p") // Paragraphs
textPolicy.AllowElements("ol", "ul", "li") // Lists textPolicy.AllowElements("ol", "ul", "li") // Lists
textPolicy.AllowElements("blockquote") // Blockquotes textPolicy.AllowElements("blockquote") // Blockquotes
// Filter HTML tags // Read filtered HTML into document
htmlBuf := textPolicy.SanitizeReader(strings.NewReader(s)) doc, _ := goquery.NewDocumentFromReader(textPolicy.SanitizeReader(r))
// Read HTML into document
doc, _ := goquery.NewDocumentFromReader(htmlBuf)
var text strings.Builder var text strings.Builder
if bodyChild := doc.Find("body").Children(); bodyChild.Length() > 0 { if bodyChild := doc.Find("body").Children(); bodyChild.Length() > 0 {
// Input was real HTML, so build the text from the body // Input was real HTML, so build the text from the body