]> Sergey Matveev's repositories - sgblog.git/blobdiff - cmd/sgblog/http.go
More compact Base64 ETag
[sgblog.git] / cmd / sgblog / http.go
index 527f51eacec80cfc7aad4e1286603b4153691bda..000037ab210e666decf8880593d58aaab31be7af 100644 (file)
@@ -1,28 +1,25 @@
-/*
-SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
-Copyright (C) 2020-2022 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+// SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
+// Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 package main
 
 import (
        "bytes"
-       "compress/gzip"
        "crypto/sha1"
        _ "embed"
-       "encoding/hex"
+       "encoding/base64"
        "encoding/xml"
        "errors"
        "fmt"
@@ -41,9 +38,11 @@ import (
        "github.com/go-git/go-git/v5"
        "github.com/go-git/go-git/v5/plumbing"
        "github.com/go-git/go-git/v5/plumbing/object"
+       "github.com/klauspost/compress/zstd"
+       "github.com/vorlif/spreak"
        "go.stargrave.org/sgblog"
        "go.stargrave.org/sgblog/cmd/sgblog/atom"
-       "golang.org/x/crypto/blake2b"
+       "lukechampine.com/blake3"
 )
 
 const (
@@ -84,6 +83,7 @@ type TableEntry struct {
        Title       string
        LinesNum    int
        CommentsNum int
+       ImagesNum   int
        DomainURLs  []string
        Topics      []string
 }
@@ -98,7 +98,7 @@ func makeA(href, text string) string {
 }
 
 func etagString(etag hash.Hash) string {
-       return `"` + hex.EncodeToString(etag.Sum(nil)) + `"`
+       return `"` + base64.RawURLEncoding.EncodeToString(etag.Sum(nil)) + `"`
 }
 
 func urlParse(what string) *url.URL {
@@ -126,13 +126,13 @@ func lineURLizeInTemplate(urlPrefix, line interface{}) string {
        return lineURLize(urlPrefix.(string), line.(string))
 }
 
-func startHeader(etag hash.Hash, gziped bool) string {
+func startHeader(etag hash.Hash, zstded bool) string {
        lines := []string{
                "Content-Type: text/html; charset=utf-8",
                "ETag: " + etagString(etag),
        }
-       if gziped {
-               lines = append(lines, "Content-Encoding: gzip")
+       if zstded {
+               lines = append(lines, "Content-Encoding: zstd")
        }
        lines = append(lines, "")
        lines = append(lines, "")
@@ -174,6 +174,7 @@ func serveHTTP() {
        if err != nil {
                log.Fatalln(err)
        }
+       initLocalizer(cfg.Lang)
 
        pathInfo := os.Getenv("PATH_INFO")
        if len(pathInfo) == 0 {
@@ -184,14 +185,10 @@ func serveHTTP() {
                makeErr(err, http.StatusBadRequest)
        }
 
-       etagHash, err := blake2b.New256(nil)
-       if err != nil {
-               panic(err)
-       }
+       etagHash := blake3.New(32, nil)
        for _, s := range []string{
                "SGBLOG",
                sgblog.Version,
-               cfg.GitPath,
                cfg.Branch,
                cfg.Title,
                cfg.URLPrefix,
@@ -210,9 +207,7 @@ func serveHTTP() {
                cfg.CommentsNotesRef,
                cfg.CommentsEmail,
        }
-       for _, gitURL := range cfg.GitURLs {
-               etagHashForWeb = append(etagHashForWeb, gitURL)
-       }
+       etagHashForWeb = append(etagHashForWeb, cfg.GitURLs...)
 
        headHash, err := initRepo(cfg)
        if err != nil {
@@ -246,12 +241,15 @@ func serveHTTP() {
        var outBuf bytes.Buffer
        var out io.Writer
        out = &outBuf
-       var gzipWriter *gzip.Writer
+       var zstdWriter *zstd.Encoder
        acceptEncoding := os.Getenv("HTTP_ACCEPT_ENCODING")
        for _, encoding := range strings.Split(acceptEncoding, ", ") {
-               if encoding == "gzip" {
-                       gzipWriter = gzip.NewWriter(&outBuf)
-                       out = gzipWriter
+               if encoding == "zstd" {
+                       zstdWriter, err = zstd.NewWriter(&outBuf, zstd.WithEncoderLevel(zstd.SpeedDefault))
+                       if err != nil {
+                               panic(err)
+                       }
+                       out = zstdWriter
                }
        }
 
@@ -340,6 +338,7 @@ func serveHTTP() {
                                entry.DomainURLs = append(entry.DomainURLs, makeA(line, u.Host))
                        }
                        entry.CommentsNum = len(sgblog.ParseComments(entry.CommentsRaw))
+                       entry.ImagesNum = len(listImgs(cfg, entry.Commit.Hash))
                        entry.Topics = sgblog.ParseTopics(entry.TopicsRaw)
                        entries[i] = entry
                }
@@ -347,8 +346,9 @@ func serveHTTP() {
                if offsetPrev < 0 {
                        offsetPrev = 0
                }
-               os.Stdout.Write([]byte(startHeader(etagHash, gzipWriter != nil)))
+               os.Stdout.Write([]byte(startHeader(etagHash, zstdWriter != nil)))
                err = TmplHTMLIndex.Execute(out, struct {
+                       T                *spreak.Localizer
                        Version          string
                        Cfg              *Cfg
                        Topic            string
@@ -363,6 +363,7 @@ func serveHTTP() {
                        LogEnded         bool
                        Entries          []TableEntry
                }{
+                       T:                localizer,
                        Version:          sgblog.Version,
                        Cfg:              cfg,
                        Topic:            topic,
@@ -405,9 +406,9 @@ func serveHTTP() {
                }
                os.Stdout.WriteString("Content-Type: text/plain; charset=utf-8\n")
                os.Stdout.WriteString("ETag: " + etagString(etagHash) + "\n")
-               if gzipWriter != nil {
-                       os.Stdout.WriteString("Content-Encoding: gzip\n")
-                       gzipWriter.Close()
+               if zstdWriter != nil {
+                       os.Stdout.WriteString("Content-Encoding: zstd\n")
+                       zstdWriter.Close()
                }
                os.Stdout.WriteString("\n")
                os.Stdout.Write(outBuf.Bytes())
@@ -433,10 +434,7 @@ func serveHTTP() {
                } else {
                        title = fmt.Sprintf("%s (topic: %s)", cfg.Title, topic)
                }
-               idHasher, err := blake2b.New256(nil)
-               if err != nil {
-                       panic(err)
-               }
+               idHasher := blake3.New(32, nil)
                idHasher.Write([]byte("ATOM POSTS"))
                idHasher.Write([]byte(cfg.AtomId))
                idHasher.Write([]byte(topic))
@@ -488,13 +486,22 @@ func serveHTTP() {
                                htmlized = append(htmlized, lineURLize(cfg.AtomBaseURL+cfg.URLPrefix, l))
                        }
                        htmlized = append(htmlized, "</pre>")
+                       links := []atom.Link{{
+                               Rel:  "alternate",
+                               Href: cfg.AtomBaseURL + cfg.URLPrefix + "/" + commit.Hash.String(),
+                       }}
+                       for _, img := range listImgs(cfg, commit.Hash) {
+                               links = append(links, atom.Link{
+                                       Rel:    "enclosure",
+                                       Href:   "http://" + cfg.ImgDomain + "/" + img.Path,
+                                       Type:   img.Typ,
+                                       Length: uint(img.Size),
+                               })
+                       }
                        feed.Entry = append(feed.Entry, &atom.Entry{
-                               Title: lines[0],
-                               ID:    "urn:uuid:" + bytes2uuid(commit.Hash[:]),
-                               Link: []atom.Link{{
-                                       Rel:  "alternate",
-                                       Href: cfg.AtomBaseURL + cfg.URLPrefix + "/" + commit.Hash.String(),
-                               }},
+                               Title:     lines[0],
+                               ID:        "urn:uuid:" + bytes2uuid(commit.Hash[:]),
+                               Link:      links,
                                Published: atom.Time(commit.Author.When),
                                Updated:   atom.Time(commit.Author.When),
                                Summary:   &atom.Text{Type: "text", Body: lines[0]},
@@ -520,10 +527,7 @@ func serveHTTP() {
                etagHash.Write([]byte("ATOM COMMENTS"))
                etagHash.Write(commit.Hash[:])
                checkETag(etagHash)
-               idHasher, err := blake2b.New256(nil)
-               if err != nil {
-                       panic(err)
-               }
+               idHasher := blake3.New(32, nil)
                idHasher.Write([]byte("ATOM COMMENTS"))
                idHasher.Write([]byte(cfg.AtomId))
                feed := atom.Feed{
@@ -651,10 +655,7 @@ func serveHTTP() {
                                        body: lines[3:],
                                })
                        }
-                       idHasher, err := blake2b.New256(nil)
-                       if err != nil {
-                               panic(err)
-                       }
+                       idHasher := blake3.New(32, nil)
                        idHasher.Write([]byte("ATOM COMMENTS"))
                        idHasher.Write(commit.Hash[:])
                        feed := atom.Feed{
@@ -737,8 +738,9 @@ func serveHTTP() {
                        notesLines = strings.Split(string(notesRaw), "\n")
                }
 
-               os.Stdout.Write([]byte(startHeader(etagHash, gzipWriter != nil)))
+               os.Stdout.Write([]byte(startHeader(etagHash, zstdWriter != nil)))
                err = TmplHTMLEntry.Execute(out, struct {
+                       T               *spreak.Localizer
                        Version         string
                        Cfg             *Cfg
                        Title           string
@@ -751,7 +753,9 @@ func serveHTTP() {
                        NoteLines       []string
                        Comments        []CommentEntry
                        Topics          []string
+                       Imgs            []Img
                }{
+                       T:               localizer,
                        Version:         sgblog.Version,
                        Cfg:             cfg,
                        Title:           title,
@@ -764,6 +768,7 @@ func serveHTTP() {
                        NoteLines:       notesLines,
                        Comments:        comments,
                        Topics:          sgblog.ParseTopics(topicsRaw),
+                       Imgs:            listImgs(cfg, commit.Hash),
                })
                if err != nil {
                        makeErr(err, http.StatusInternalServerError)
@@ -772,8 +777,8 @@ func serveHTTP() {
                makeErr(errors.New("unknown URL action"), http.StatusNotFound)
        }
        out.Write([]byte("</body></html>\n"))
-       if gzipWriter != nil {
-               gzipWriter.Close()
+       if zstdWriter != nil {
+               zstdWriter.Close()
        }
        os.Stdout.Write(outBuf.Bytes())
        return
@@ -781,9 +786,9 @@ func serveHTTP() {
 AtomFinish:
        os.Stdout.WriteString("Content-Type: application/atom+xml; charset=utf-8\n")
        os.Stdout.WriteString("ETag: " + etagString(etagHash) + "\n")
-       if gzipWriter != nil {
-               os.Stdout.WriteString("Content-Encoding: gzip\n")
-               gzipWriter.Close()
+       if zstdWriter != nil {
+               os.Stdout.WriteString("Content-Encoding: zstd\n")
+               zstdWriter.Close()
        }
        os.Stdout.WriteString("\n")
        os.Stdout.Write(outBuf.Bytes())