From 4bf44b16562335b3d09b6df0150521bb5b5f776f Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Mon, 18 May 2020 12:12:43 +0200 Subject: WIP: Refactoring --- internal/port/main.go | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 internal/port/main.go (limited to 'internal/port/main.go') diff --git a/internal/port/main.go b/internal/port/main.go new file mode 100644 index 0000000..5cdd794 --- /dev/null +++ b/internal/port/main.go @@ -0,0 +1,301 @@ +package port + +import ( + "crypto/md5" + "fmt" + "html" + "html/template" + "io/ioutil" + "log" + "net/http" + "regexp" + "strings" + + "github.com/NYTimes/gziphandler" + "github.com/davidbyttow/govips/pkg/vips" + "github.com/gobuffalo/packr/v2" + "github.com/temoto/robotstxt" +) + +type AssetList struct { + Style string + JS string + FontW string + FontW2 string + PropFontW string + PropFontW2 string +} + +type TemplateVariables struct { + Title string + URI string + Assets AssetList + RawText string + Lines []Item + Error bool + Protocol string +} + +func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if err := tpl.Execute(w, TemplateVariables{ + Title: "Gopher/Gemini proxy", + Assets: assetList, + RawText: startpagetext, + Protocol: "startpage", + }); err != nil { + log.Println("Template error: " + err.Error()) + } + } +} + +// RobotsTxtHandler returns the contents of the robots.txt file +// if configured and valid. +func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if robotstxtdata == nil { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "text/plain") + w.Write(robotstxtdata) + } +} + +func FaviconHandler(favicondata []byte) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if favicondata == nil { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + 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 { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + if woff2 { + w.Header().Set("Content-Type", "font/woff2") + } else { + w.Header().Set("Content-Type", "font/woff") + } + w.Header().Set("Cache-Control", "max-age=2592000") + + w.Write(fontdata) + } +} + +// ListenAndServe creates a listening HTTP server bound to +// the interface specified by bind and sets up a Gopher to HTTP +// proxy proxying requests as requested and by default will prozy +// to a Gopher server address specified by uri if no servers is +// specified by the request. The robots argument is a pointer to +// a robotstxt.RobotsData struct for testing user agents against +// a configurable robots.txt file. +func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error { + box := packr.New("assets", "../assets") + + // + // Robots + + var robotsdata *robotstxt.RobotsData + + robotstxtdata, err := ioutil.ReadFile(robotsfile) + if err != nil { + log.Printf("error reading robots.txt: %s", err) + robotstxtdata = nil + } else { + robotsdata, err = robotstxt.FromBytes(robotstxtdata) + if err != nil { + log.Printf("error reading robots.txt: %s", err) + robotstxtdata = nil + } + } + + // + // Fonts + + fontdataw, err := box.Find("iosevka-term-ss03-regular.woff") + if err != nil { + fontdataw = []byte{} + } + fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw)) + + fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") + if err != nil { + fontdataw2 = []byte{} + } + fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2)) + + propfontdataw, err := box.Find("iosevka-aile-regular.woff") + if err != nil { + propfontdataw = []byte{} + } + propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw)) + + propfontdataw2, err := box.Find("iosevka-aile-regular.woff2") + if err != nil { + propfontdataw2 = []byte{} + } + propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2)) + + // + // Stylesheet + + styledata, err := box.Find("style.css") + if err != nil { + styledata = []byte{} + } + styleAsset := fmt.Sprintf("/style-%x.css", md5.Sum(styledata)) + + // + // JavaScript + + jsdata, err := box.Find("main.js") + if err != nil { + jsdata = []byte{} + } + jsAsset := fmt.Sprintf("/main-%x.js", md5.Sum(jsdata)) + + // + // Favicon + + favicondata, err := box.Find("favicon.ico") + if err != nil { + favicondata = []byte{} + } + + // + // Start page text + + startpagedata, err := ioutil.ReadFile(startpagefile) + if err != nil { + startpagedata, err = box.Find("startpage.txt") + if err != nil { + startpagedata = []byte{} + } + } + startpagetext := string(startpagedata) + + // + // + + var allFiles []string + files, err := ioutil.ReadDir("./tpl") + if err != nil { + fmt.Println(err) + } + for _, file := range files { + filename := file.Name() + if strings.HasSuffix(filename, ".html") { + allFiles = append(allFiles, "./tpl/"+filename) + } + } + + templates, err = template.ParseFiles(allFiles...) + + // + + funcMap := template.FuncMap{ + "safeHtml": func(s string) template.HTML { + return template.HTML(s) + }, + "safeCss": func(s string) template.CSS { + return template.CSS(s) + }, + "safeJs": func(s string) template.JS { + return template.JS(s) + }, + "HTMLEscape": func(s string) string { + return html.EscapeString(s) + }, + "split": strings.Split, + "last": func(s []string) string { + return s[len(s)-1] + }, + "pop": func(s []string) []string { + return s[:len(s)-1] + }, + "replace": func(pattern, output string, input interface{}) string { + var re = regexp.MustCompile(pattern) + var inputStr = fmt.Sprintf("%v", input) + return re.ReplaceAllString(inputStr, output) + }, + "trimLeftChar": func(s string) string { + for i := range s { + if i > 0 { + return s[i:] + } + } + return s[:0] + }, + "hasPrefix": func(s string, prefix string) bool { + return strings.HasPrefix(s, prefix) + }, + "title": func(s string) string { + return strings.Title(s) + }, + } + + // + + startpageTpl := templates.Lookup("startpage.html").Funcs(funcMap) + geminiTpl := templates.Lookup("gemini.html").Funcs(funcMap) + gopherTpl := templates.Lookup("gopher.html").Funcs(funcMap) + + // + // + + vips.Startup(&vips.Config{ + ConcurrencyLevel: vipsconcurrency, + }) + + assets := AssetList{ + Style: styleAsset, + JS: jsAsset, + FontW: fontwAsset, + FontW2: fontw2Asset, + PropFontW: propfontwAsset, + PropFontW2: propfontw2Asset, + } + + http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets))) + http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(gopherTpl, robotsdata, assets, robotsdebug))) + http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(geminiTpl, robotsdata, assets, robotsdebug))) + http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata))) + http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) + http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) + http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata))) + http.HandleFunc(fontwAsset, FontHandler(false, fontdataw)) + http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2)) + http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw)) + http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2)) + //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) + + return http.ListenAndServe(bind, nil) +} -- cgit v1.3.1