diff options
| author | Feuerfuchs <git@feuerfuchs.dev> | 2019-06-27 15:34:16 +0200 |
|---|---|---|
| committer | Feuerfuchs <git@feuerfuchs.dev> | 2019-06-27 15:34:16 +0200 |
| commit | ccdc998836038ba2fb8bb72d417a0aa09648ad89 (patch) | |
| tree | 9fa11257818da341b00def940d16a738fdc3c04d | |
| parent | Small fixes in the readme (diff) | |
| download | gopherproxy-ccdc998836038ba2fb8bb72d417a0aa09648ad89.tar.gz gopherproxy-ccdc998836038ba2fb8bb72d417a0aa09648ad89.tar.bz2 gopherproxy-ccdc998836038ba2fb8bb72d417a0aa09648ad89.zip | |
Serve assets as own files, use caching strategies
| -rw-r--r-- | assets/style.css | 2 | ||||
| -rw-r--r-- | css/main.scss | 8 | ||||
| -rw-r--r-- | gopherproxy.go | 78 | ||||
| -rw-r--r-- | 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; | |||
| 12 | 12 | ||
| 13 | $font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; | 13 | $font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; |
| 14 | 14 | ||
| 15 | @font-face { | ||
| 16 | font-family: 'Iosevka Term SS03'; | ||
| 17 | font-style: normal; | ||
| 18 | font-weight: normal; | ||
| 19 | src: url('/iosevka-term-ss03-regular.woff2') format('woff2'), | ||
| 20 | url('/iosevka-term-ss03-regular.woff') format('woff'); | ||
| 21 | } | ||
| 22 | |||
| 23 | // | 15 | // |
| 24 | // Basic element styles | 16 | // Basic element styles |
| 25 | 17 | ||
diff --git a/gopherproxy.go b/gopherproxy.go index 62b7446..1fcd29f 100644 --- a/gopherproxy.go +++ b/gopherproxy.go | |||
| @@ -2,6 +2,7 @@ package gopherproxy | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "bytes" | 4 | "bytes" |
| 5 | "crypto/md5" | ||
| 5 | "fmt" | 6 | "fmt" |
| 6 | "html" | 7 | "html" |
| 7 | "html/template" | 8 | "html/template" |
| @@ -28,7 +29,14 @@ type Item struct { | |||
| 28 | Text string | 29 | Text string |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 31 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext string, jstext string, uri string, hostport string, d gopher.Directory) error { | 32 | type AssetHashList struct { |
| 33 | Style string | ||
| 34 | JS string | ||
| 35 | FontW string | ||
| 36 | FontW2 string | ||
| 37 | } | ||
| 38 | |||
| 39 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetHashList AssetHashList, uri string, hostport string, d gopher.Directory) error { | ||
| 32 | var title string | 40 | var title string |
| 33 | 41 | ||
| 34 | out := make([]Item, len(d.Items)) | 42 | out := make([]Item, len(d.Items)) |
| @@ -78,13 +86,12 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st | |||
| 78 | } | 86 | } |
| 79 | 87 | ||
| 80 | return tpl.Execute(w, struct { | 88 | return tpl.Execute(w, struct { |
| 81 | Title string | 89 | Title string |
| 82 | URI string | 90 | URI string |
| 83 | Style string | 91 | AssetHashList AssetHashList |
| 84 | Script string | 92 | Lines []Item |
| 85 | Lines []Item | 93 | RawText string |
| 86 | RawText string | 94 | }{title, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, out, ""}) |
| 87 | }{title, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, out, ""}) | ||
| 88 | } | 95 | } |
| 89 | 96 | ||
| 90 | // GopherHandler returns a Handler that proxies requests | 97 | // GopherHandler returns a Handler that proxies requests |
| @@ -92,7 +99,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st | |||
| 92 | // to the request path and renders the content using the provided template. | 99 | // to the request path and renders the content using the provided template. |
| 93 | // The optional robots parameters points to a robotstxt.RobotsData struct | 100 | // The optional robots parameters points to a robotstxt.RobotsData struct |
| 94 | // to test user agents against a configurable robotst.txt file. | 101 | // to test user agents against a configurable robotst.txt file. |
| 95 | func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, robotsdebug bool, styletext string, jstext string, uri string) http.HandlerFunc { | 102 | func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetHashList AssetHashList, robotsdebug bool, uri string) http.HandlerFunc { |
| 96 | return func(w http.ResponseWriter, req *http.Request) { | 103 | return func(w http.ResponseWriter, req *http.Request) { |
| 97 | agent := req.UserAgent() | 104 | agent := req.UserAgent() |
| 98 | path := strings.TrimPrefix(req.URL.Path, "/") | 105 | path := strings.TrimPrefix(req.URL.Path, "/") |
| @@ -141,13 +148,12 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob | |||
| 141 | buf := new(bytes.Buffer) | 148 | buf := new(bytes.Buffer) |
| 142 | buf.ReadFrom(res.Body) | 149 | buf.ReadFrom(res.Body) |
| 143 | tpl.Execute(w, struct { | 150 | tpl.Execute(w, struct { |
| 144 | Title string | 151 | Title string |
| 145 | URI string | 152 | URI string |
| 146 | Style string | 153 | AssetHashList AssetHashList |
| 147 | Script string | 154 | RawText string |
| 148 | RawText string | 155 | Lines []Item |
| 149 | Lines []Item | 156 | }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, buf.String(), nil}) |
| 150 | }{uri, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, buf.String(), nil}) | ||
| 151 | } else if parts[1] == "T" { | 157 | } else if parts[1] == "T" { |
| 152 | _, _, err = vips.NewTransform(). | 158 | _, _, err = vips.NewTransform(). |
| 153 | Load(res.Body). | 159 | Load(res.Body). |
| @@ -160,7 +166,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob | |||
| 160 | io.Copy(w, res.Body) | 166 | io.Copy(w, res.Body) |
| 161 | } | 167 | } |
| 162 | } else { | 168 | } else { |
| 163 | if err := renderDirectory(w, tpl, styletext, jstext, uri, hostport, res.Dir); err != nil { | 169 | if err := renderDirectory(w, tpl, assetHashList, uri, hostport, res.Dir); err != nil { |
| 164 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | 170 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) |
| 165 | return | 171 | return |
| 166 | } | 172 | } |
| @@ -190,10 +196,27 @@ func FaviconHandler(favicondata []byte) http.HandlerFunc { | |||
| 190 | } | 196 | } |
| 191 | 197 | ||
| 192 | w.Header().Set("Content-Type", "image/vnd.microsoft.icon") | 198 | w.Header().Set("Content-Type", "image/vnd.microsoft.icon") |
| 199 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
| 193 | w.Write(favicondata) | 200 | w.Write(favicondata) |
| 194 | } | 201 | } |
| 195 | } | 202 | } |
| 196 | 203 | ||
| 204 | func StyleHandler(styledata []byte) http.HandlerFunc { | ||
| 205 | return func(w http.ResponseWriter, req *http.Request) { | ||
| 206 | w.Header().Set("Content-Type", "text/css") | ||
| 207 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
| 208 | w.Write(styledata) | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | func JavaScriptHandler(jsdata []byte) http.HandlerFunc { | ||
| 213 | return func(w http.ResponseWriter, req *http.Request) { | ||
| 214 | w.Header().Set("Content-Type", "text/javascript") | ||
| 215 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
| 216 | w.Write(jsdata) | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 197 | func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { | 220 | func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { |
| 198 | return func(w http.ResponseWriter, req *http.Request) { | 221 | return func(w http.ResponseWriter, req *http.Request) { |
| 199 | if fontdata == nil { | 222 | if fontdata == nil { |
| @@ -206,6 +229,7 @@ func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { | |||
| 206 | } else { | 229 | } else { |
| 207 | w.Header().Set("Content-Type", "font/woff") | 230 | w.Header().Set("Content-Type", "font/woff") |
| 208 | } | 231 | } |
| 232 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
| 209 | 233 | ||
| 210 | w.Write(fontdata) | 234 | w.Write(fontdata) |
| 211 | } | 235 | } |
| @@ -242,21 +266,25 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error | |||
| 242 | if err != nil { | 266 | if err != nil { |
| 243 | fontdataw = []byte{} | 267 | fontdataw = []byte{} |
| 244 | } | 268 | } |
| 269 | fontwhash := fmt.Sprintf("%x", md5.Sum(fontdataw)) | ||
| 245 | 270 | ||
| 246 | fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") | 271 | fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") |
| 247 | if err != nil { | 272 | if err != nil { |
| 248 | fontdataw2 = []byte{} | 273 | fontdataw2 = []byte{} |
| 249 | } | 274 | } |
| 275 | fontw2hash := fmt.Sprintf("%x", md5.Sum(fontdataw2)) | ||
| 250 | 276 | ||
| 251 | styletext, err := box.FindString("style.css") | 277 | styledata, err := box.Find("style.css") |
| 252 | if err != nil { | 278 | if err != nil { |
| 253 | styletext = "" | 279 | styledata = []byte{} |
| 254 | } | 280 | } |
| 281 | stylehash := fmt.Sprintf("%x", md5.Sum(styledata)) | ||
| 255 | 282 | ||
| 256 | jstext, err := box.FindString("main.js") | 283 | jsdata, err := box.Find("main.js") |
| 257 | if err != nil { | 284 | if err != nil { |
| 258 | jstext = "" | 285 | jsdata = []byte{} |
| 259 | } | 286 | } |
| 287 | jshash := fmt.Sprintf("%x", md5.Sum(jsdata)) | ||
| 260 | 288 | ||
| 261 | favicondata, err := box.Find("favicon.ico") | 289 | favicondata, err := box.Find("favicon.ico") |
| 262 | if err != nil { | 290 | if err != nil { |
| @@ -304,11 +332,13 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error | |||
| 304 | ConcurrencyLevel: 2, | 332 | ConcurrencyLevel: 2, |
| 305 | }) | 333 | }) |
| 306 | 334 | ||
| 307 | http.HandleFunc("/", GopherHandler(tpl, robotsdata, robotsdebug, styletext, jstext, uri)) | 335 | http.HandleFunc("/", GopherHandler(tpl, robotsdata, AssetHashList{stylehash, jshash, fontwhash, fontw2hash}, robotsdebug, uri)) |
| 308 | http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) | 336 | http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) |
| 309 | http.HandleFunc("/favicon.ico", FaviconHandler(favicondata)) | 337 | http.HandleFunc("/favicon.ico", FaviconHandler(favicondata)) |
| 310 | http.HandleFunc("/iosevka-term-ss03-regular.woff", FontHandler(false, fontdataw)) | 338 | http.HandleFunc("/style-"+stylehash+".css", StyleHandler(styledata)) |
| 311 | http.HandleFunc("/iosevka-term-ss03-regular.woff2", FontHandler(true, fontdataw2)) | 339 | http.HandleFunc("/main-"+jshash+".js", JavaScriptHandler(jsdata)) |
| 340 | http.HandleFunc("/iosevka-term-ss03-regular-"+fontwhash+".woff", FontHandler(false, fontdataw)) | ||
| 341 | http.HandleFunc("/iosevka-term-ss03-regular-"+fontw2hash+".woff2", FontHandler(true, fontdataw2)) | ||
| 312 | //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) | 342 | //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) |
| 313 | 343 | ||
| 314 | return http.ListenAndServe(bind, nil) | 344 | 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 = `<!doctype html> | |||
| 6 | <meta charset="utf-8"> | 6 | <meta charset="utf-8"> |
| 7 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | 7 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 8 | <title>{{ .Title }}</title> | 8 | <title>{{ .Title }}</title> |
| 9 | <link rel="preload" href="/iosevka-term-ss03-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous" /> | 9 | <link rel="stylesheet" href="/style-{{ .AssetHashList.Style }}.css" /> |
| 10 | <style> | 10 | <style> |
| 11 | {{ .Style | safeCss }} | 11 | @font-face { |
| 12 | font-family: 'Iosevka Term SS03'; | ||
| 13 | font-style: normal; | ||
| 14 | font-weight: normal; | ||
| 15 | src: url('/iosevka-term-ss03-regular-{{ .AssetHashList.FontW2 }}.woff2') format('woff2'), | ||
| 16 | url('/iosevka-term-ss03-regular-{{ .AssetHashList.FontW }}.woff') format('woff'); | ||
| 17 | } | ||
| 12 | </style> | 18 | </style> |
| 13 | </head> | 19 | </head> |
| 14 | <body class="{{ if not .Lines }}is-plain{{ end }}"> | 20 | <body class="{{ if not .Lines }}is-plain{{ end }}"> |
| @@ -91,8 +97,6 @@ var tpltext = `<!doctype html> | |||
| 91 | </div> | 97 | </div> |
| 92 | </div> | 98 | </div> |
| 93 | </aside> | 99 | </aside> |
| 94 | <script type="text/javascript"> | 100 | <script src="/main-{{ .AssetHashList.JS }}.js"></script> |
| 95 | {{ .Script | safeJs }} | ||
| 96 | </script> | ||
| 97 | </body> | 101 | </body> |
| 98 | </html>` | 102 | </html>` |
