mirror of https://github.com/jlelse/GoBlog
More optimizations
This commit is contained in:
parent
6e767e3612
commit
d8caf1e6f5
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
19
editor.go
19
editor.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
68
markdown.go
68
markdown.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 := µformatItem{}
|
parsedMfItem := µformatItem{}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
render.go
17
render.go
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
10
utils.go
10
utils.go
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue