From ccdc998836038ba2fb8bb72d417a0aa09648ad89 Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Thu, 27 Jun 2019 15:34:16 +0200 Subject: Serve assets as own files, use caching strategies --- assets/style.css | 2 +- css/main.scss | 8 ------ gopherproxy.go | 78 +++++++++++++++++++++++++++++++++++++++----------------- template.go | 14 ++++++---- 4 files changed, 64 insertions(+), 38 deletions(-) diff --git a/assets/style.css b/assets/style.css index 0f4583f..919746c 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1 +1 @@ -@font-face{font-family:'Iosevka Term SS03';font-style:normal;font-weight:normal;src:url("/iosevka-term-ss03-regular.woff2") format("woff2"),url("/iosevka-term-ss03-regular.woff") format("woff")}body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1.0625em;line-height:1.5}h1,h2,h3,h4,h5,h6{font:inherit;color:#fff;margin:0}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#5d646a}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em;color:#5d646a}.location__prefix{margin-right:.3em;color:#5d646a}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit}.content--wrap{white-space:pre-wrap;word-wrap:break-word}.content--has-type-annotations{padding-left:3em;padding-left:5ch}.type-annotation{margin-left:-3em;margin-left:-5ch;color:#929ba3;white-space:pre}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}} +body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1.0625em;line-height:1.5}h1,h2,h3,h4,h5,h6{font:inherit;color:#fff;margin:0}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#5d646a}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em;color:#5d646a}.location__prefix{margin-right:.3em;color:#5d646a}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit}.content--wrap{white-space:pre-wrap;word-wrap:break-word}.content--has-type-annotations{padding-left:3em;padding-left:5ch}.type-annotation{margin-left:-3em;margin-left:-5ch;color:#929ba3;white-space:pre}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}} diff --git a/css/main.scss b/css/main.scss index be44d5f..f746fa1 100644 --- a/css/main.scss +++ b/css/main.scss @@ -12,14 +12,6 @@ $sel-text: #000; $font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; -@font-face { - font-family: 'Iosevka Term SS03'; - font-style: normal; - font-weight: normal; - src: url('/iosevka-term-ss03-regular.woff2') format('woff2'), - url('/iosevka-term-ss03-regular.woff') format('woff'); -} - // // Basic element styles diff --git a/gopherproxy.go b/gopherproxy.go index 62b7446..1fcd29f 100644 --- a/gopherproxy.go +++ b/gopherproxy.go @@ -2,6 +2,7 @@ package gopherproxy import ( "bytes" + "crypto/md5" "fmt" "html" "html/template" @@ -28,7 +29,14 @@ type Item struct { Text string } -func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext string, jstext string, uri string, hostport string, d gopher.Directory) error { +type AssetHashList struct { + Style string + JS string + FontW string + FontW2 string +} + +func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetHashList AssetHashList, uri string, hostport string, d gopher.Directory) error { var title string out := make([]Item, len(d.Items)) @@ -78,13 +86,12 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st } return tpl.Execute(w, struct { - Title string - URI string - Style string - Script string - Lines []Item - RawText string - }{title, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, out, ""}) + Title string + URI string + AssetHashList AssetHashList + Lines []Item + RawText string + }{title, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, out, ""}) } // GopherHandler returns a Handler that proxies requests @@ -92,7 +99,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st // to the request path and renders the content using the provided template. // The optional robots parameters points to a robotstxt.RobotsData struct // to test user agents against a configurable robotst.txt file. -func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, robotsdebug bool, styletext string, jstext string, uri string) http.HandlerFunc { +func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetHashList AssetHashList, robotsdebug bool, uri string) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { agent := req.UserAgent() path := strings.TrimPrefix(req.URL.Path, "/") @@ -141,13 +148,12 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob buf := new(bytes.Buffer) buf.ReadFrom(res.Body) tpl.Execute(w, struct { - Title string - URI string - Style string - Script string - RawText string - Lines []Item - }{uri, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, buf.String(), nil}) + Title string + URI string + AssetHashList AssetHashList + RawText string + Lines []Item + }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, buf.String(), nil}) } else if parts[1] == "T" { _, _, err = vips.NewTransform(). Load(res.Body). @@ -160,7 +166,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob io.Copy(w, res.Body) } } else { - if err := renderDirectory(w, tpl, styletext, jstext, uri, hostport, res.Dir); err != nil { + if err := renderDirectory(w, tpl, assetHashList, uri, hostport, res.Dir); err != nil { io.WriteString(w, fmt.Sprintf("Error:
%s
", err)) return } @@ -190,10 +196,27 @@ func FaviconHandler(favicondata []byte) http.HandlerFunc { } w.Header().Set("Content-Type", "image/vnd.microsoft.icon") + w.Header().Set("Cache-Control", "max-age=2592000") w.Write(favicondata) } } +func StyleHandler(styledata []byte) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "text/css") + w.Header().Set("Cache-Control", "max-age=2592000") + w.Write(styledata) + } +} + +func JavaScriptHandler(jsdata []byte) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "text/javascript") + w.Header().Set("Cache-Control", "max-age=2592000") + w.Write(jsdata) + } +} + func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { if fontdata == nil { @@ -206,6 +229,7 @@ func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { } else { w.Header().Set("Content-Type", "font/woff") } + w.Header().Set("Cache-Control", "max-age=2592000") w.Write(fontdata) } @@ -242,21 +266,25 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error if err != nil { fontdataw = []byte{} } + fontwhash := fmt.Sprintf("%x", md5.Sum(fontdataw)) fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") if err != nil { fontdataw2 = []byte{} } + fontw2hash := fmt.Sprintf("%x", md5.Sum(fontdataw2)) - styletext, err := box.FindString("style.css") + styledata, err := box.Find("style.css") if err != nil { - styletext = "" + styledata = []byte{} } + stylehash := fmt.Sprintf("%x", md5.Sum(styledata)) - jstext, err := box.FindString("main.js") + jsdata, err := box.Find("main.js") if err != nil { - jstext = "" + jsdata = []byte{} } + jshash := fmt.Sprintf("%x", md5.Sum(jsdata)) favicondata, err := box.Find("favicon.ico") if err != nil { @@ -304,11 +332,13 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error ConcurrencyLevel: 2, }) - http.HandleFunc("/", GopherHandler(tpl, robotsdata, robotsdebug, styletext, jstext, uri)) + http.HandleFunc("/", GopherHandler(tpl, robotsdata, AssetHashList{stylehash, jshash, fontwhash, fontw2hash}, robotsdebug, uri)) http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) http.HandleFunc("/favicon.ico", FaviconHandler(favicondata)) - http.HandleFunc("/iosevka-term-ss03-regular.woff", FontHandler(false, fontdataw)) - http.HandleFunc("/iosevka-term-ss03-regular.woff2", FontHandler(true, fontdataw2)) + http.HandleFunc("/style-"+stylehash+".css", StyleHandler(styledata)) + http.HandleFunc("/main-"+jshash+".js", JavaScriptHandler(jsdata)) + http.HandleFunc("/iosevka-term-ss03-regular-"+fontwhash+".woff", FontHandler(false, fontdataw)) + http.HandleFunc("/iosevka-term-ss03-regular-"+fontw2hash+".woff2", FontHandler(true, fontdataw2)) //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) return http.ListenAndServe(bind, nil) diff --git a/template.go b/template.go index ef02ce0..cb37aff 100644 --- a/template.go +++ b/template.go @@ -6,9 +6,15 @@ var tpltext = ` {{ .Title }} - + @@ -91,8 +97,6 @@ var tpltext = ` - + ` -- cgit v1.2.3-70-g09d2