From 4f6e9e5c7c52b2daadbedff764419efed5a63254 Mon Sep 17 00:00:00 2001 From: Nazar Kanaev Date: Thu, 20 Aug 2020 00:10:30 +0100 Subject: [PATCH] bundle assets --- .gitignore | 1 + bundle.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ server/handlers.go | 53 ++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 bundle.go diff --git a/.gitignore b/.gitignore index 28a7e2e..200906b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ yarr *.db +server/assets_bundle.go diff --git a/bundle.go b/bundle.go new file mode 100644 index 0000000..f29b828 --- /dev/null +++ b/bundle.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/base64" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + "text/template" +) + +var code_template = `// autogenerated. do not edit! + +package server + +var assets_bundle = map[string]asset{ + {{- range .}} + "{{.Name}}": {etag: "{{.Etag}}", body: "{{.Body}}"}, + {{- end }} +} + +func init() { + assets = assets_bundle +} +` + +type asset struct { + Name, Etag, Body string +} + +func shasum(b []byte) string { + h := sha256.New() + h.Write(b) + return fmt.Sprintf("%x", h.Sum(nil))[:16] +} + +func encode(b []byte) string { + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + zw.Write(b) + zw.Close() + return base64.StdEncoding.EncodeToString(buf.Bytes()) +} + +func main() { + outfile := "server/assets_bundle.go" + assets := make([]asset, 0) + filepatterns := []string{ + "assets/index.html", + "assets/graphicarts/*.svg", + "assets/graphicarts/*.png", + "assets/javascripts/*.js", + "assets/stylesheets/*.css", + "assets/stylesheets/*.map", + } + fmt.Printf("%8s %8s %s\n", "original", "encoded", "filename") + for _, pattern := range filepatterns { + filenames, _ := filepath.Glob(pattern) + for _, filename := range filenames { + content, _ := ioutil.ReadFile(filename) + assets = append(assets, asset{ + Name: strings.TrimPrefix(filename, "assets/"), + Etag: shasum(content), + Body: encode(content), + }) + fmt.Printf( + "%8d %8d %s\n", + len(content), + len(assets[len(assets)-1].Body), + filename, + ) + } + } + var buf bytes.Buffer + template := template.Must(template.New("code").Parse(code_template)) + template.Execute(&buf, assets) + ioutil.WriteFile(outfile, buf.Bytes(), 0644) +} diff --git a/server/handlers.go b/server/handlers.go index 11ee3be..26dcbf2 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -1,7 +1,10 @@ package server import ( + "bytes" "encoding/json" + "encoding/base64" + "compress/gzip" "fmt" "github.com/nkanaev/yarr/storage" "html" @@ -37,6 +40,36 @@ var routes []Route = []Route{ p("/page", PageCrawlHandler), } +type asset struct { + etag string + body string // base64(gzip(content)) + gzipped *[]byte + decoded *string +} + +func (a *asset) gzip() *[]byte { + if a.gzipped == nil { + gzipped, _ := base64.StdEncoding.DecodeString(a.body) + a.gzipped = &gzipped + } + return a.gzipped +} + +func (a *asset) text() *string { + if a.decoded == nil { + gzipped, _ := base64.StdEncoding.DecodeString(a.body) + reader, _ := gzip.NewReader(bytes.NewBuffer(gzipped)) + decoded, _ := ioutil.ReadAll(reader) + reader.Close() + + decoded_string := string(decoded) + a.decoded = &decoded_string + } + return a.decoded +} + +var assets map[string]asset + type FolderCreateForm struct { Title string `json:"title"` } @@ -58,6 +91,9 @@ type ItemUpdateForm struct { func IndexHandler(rw http.ResponseWriter, req *http.Request) { t := template.Must(template.New("index.html").Delims("{%", "%}").Funcs(template.FuncMap{ "inline": func(svg string) template.HTML { + if asset, ok := assets["graphicarts/" + svg]; ok { + return template.HTML(*asset.text()) + } content, _ := ioutil.ReadFile("assets/graphicarts/" + svg) return template.HTML(content) }, @@ -67,14 +103,25 @@ func IndexHandler(rw http.ResponseWriter, req *http.Request) { } func StaticHandler(rw http.ResponseWriter, req *http.Request) { - path := "assets/" + Vars(req)["path"] - f, err := os.Open(path) + path := Vars(req)["path"] + ctype := mime.TypeByExtension(filepath.Ext(path)) + + if assets != nil { + if asset, ok := assets[path]; ok { + rw.Header().Set("Content-Type", ctype) + rw.Header().Set("Content-Encoding", "gzip") + rw.Header().Set("Etag", asset.etag) + rw.Write(*asset.gzip()) + } + } + + f, err := os.Open("assets/" + path) if err != nil { rw.WriteHeader(http.StatusNotFound) return } defer f.Close() - rw.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(path))) + rw.Header().Set("Content-Type", ctype) io.Copy(rw, f) }