aboutsummaryrefslogtreecommitdiffstats
path: root/internal/port/main.go
diff options
context:
space:
mode:
authorFeuerfuchs <git@feuerfuchs.dev>2020-05-18 12:12:43 +0200
committerFeuerfuchs <git@feuerfuchs.dev>2020-05-18 12:12:43 +0200
commit4bf44b16562335b3d09b6df0150521bb5b5f776f (patch)
tree576723e6dc9f9db48d0892f7ec354a11b973aef4 /internal/port/main.go
parentAdded 2 more glyphs (diff)
downloadgopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.tar.gz
gopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.tar.bz2
gopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.zip
WIP: Refactoring
Diffstat (limited to 'internal/port/main.go')
-rw-r--r--internal/port/main.go301
1 files changed, 301 insertions, 0 deletions
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 @@
1package port
2
3import (
4 "crypto/md5"
5 "fmt"
6 "html"
7 "html/template"
8 "io/ioutil"
9 "log"
10 "net/http"
11 "regexp"
12 "strings"
13
14 "github.com/NYTimes/gziphandler"
15 "github.com/davidbyttow/govips/pkg/vips"
16 "github.com/gobuffalo/packr/v2"
17 "github.com/temoto/robotstxt"
18)
19
20type AssetList struct {
21 Style string
22 JS string
23 FontW string
24 FontW2 string
25 PropFontW string
26 PropFontW2 string
27}
28
29type TemplateVariables struct {
30 Title string
31 URI string
32 Assets AssetList
33 RawText string
34 Lines []Item
35 Error bool
36 Protocol string
37}
38
39func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc {
40 return func(w http.ResponseWriter, req *http.Request) {
41 if err := tpl.Execute(w, TemplateVariables{
42 Title: "Gopher/Gemini proxy",
43 Assets: assetList,
44 RawText: startpagetext,
45 Protocol: "startpage",
46 }); err != nil {
47 log.Println("Template error: " + err.Error())
48 }
49 }
50}
51
52// RobotsTxtHandler returns the contents of the robots.txt file
53// if configured and valid.
54func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc {
55 return func(w http.ResponseWriter, req *http.Request) {
56 if robotstxtdata == nil {
57 http.Error(w, "Not Found", http.StatusNotFound)
58 return
59 }
60
61 w.Header().Set("Content-Type", "text/plain")
62 w.Write(robotstxtdata)
63 }
64}
65
66func FaviconHandler(favicondata []byte) http.HandlerFunc {
67 return func(w http.ResponseWriter, req *http.Request) {
68 if favicondata == nil {
69 http.Error(w, "Not Found", http.StatusNotFound)
70 return
71 }
72
73 w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
74 w.Header().Set("Cache-Control", "max-age=2592000")
75 w.Write(favicondata)
76 }
77}
78
79func StyleHandler(styledata []byte) http.HandlerFunc {
80 return func(w http.ResponseWriter, req *http.Request) {
81 w.Header().Set("Content-Type", "text/css")
82 w.Header().Set("Cache-Control", "max-age=2592000")
83 w.Write(styledata)
84 }
85}
86
87func JavaScriptHandler(jsdata []byte) http.HandlerFunc {
88 return func(w http.ResponseWriter, req *http.Request) {
89 w.Header().Set("Content-Type", "text/javascript")
90 w.Header().Set("Cache-Control", "max-age=2592000")
91 w.Write(jsdata)
92 }
93}
94
95func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
96 return func(w http.ResponseWriter, req *http.Request) {
97 if fontdata == nil {
98 http.Error(w, "Not Found", http.StatusNotFound)
99 return
100 }
101
102 if woff2 {
103 w.Header().Set("Content-Type", "font/woff2")
104 } else {
105 w.Header().Set("Content-Type", "font/woff")
106 }
107 w.Header().Set("Cache-Control", "max-age=2592000")
108
109 w.Write(fontdata)
110 }
111}
112
113// ListenAndServe creates a listening HTTP server bound to
114// the interface specified by bind and sets up a Gopher to HTTP
115// proxy proxying requests as requested and by default will prozy
116// to a Gopher server address specified by uri if no servers is
117// specified by the request. The robots argument is a pointer to
118// a robotstxt.RobotsData struct for testing user agents against
119// a configurable robots.txt file.
120func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error {
121 box := packr.New("assets", "../assets")
122
123 //
124 // Robots
125
126 var robotsdata *robotstxt.RobotsData
127
128 robotstxtdata, err := ioutil.ReadFile(robotsfile)
129 if err != nil {
130 log.Printf("error reading robots.txt: %s", err)
131 robotstxtdata = nil
132 } else {
133 robotsdata, err = robotstxt.FromBytes(robotstxtdata)
134 if err != nil {
135 log.Printf("error reading robots.txt: %s", err)
136 robotstxtdata = nil
137 }
138 }
139
140 //
141 // Fonts
142
143 fontdataw, err := box.Find("iosevka-term-ss03-regular.woff")
144 if err != nil {
145 fontdataw = []byte{}
146 }
147 fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw))
148
149 fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2")
150 if err != nil {
151 fontdataw2 = []byte{}
152 }
153 fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2))
154
155 propfontdataw, err := box.Find("iosevka-aile-regular.woff")
156 if err != nil {
157 propfontdataw = []byte{}
158 }
159 propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw))
160
161 propfontdataw2, err := box.Find("iosevka-aile-regular.woff2")
162 if err != nil {
163 propfontdataw2 = []byte{}
164 }
165 propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2))
166
167 //
168 // Stylesheet
169
170 styledata, err := box.Find("style.css")
171 if err != nil {
172 styledata = []byte{}
173 }
174 styleAsset := fmt.Sprintf("/style-%x.css", md5.Sum(styledata))
175
176 //
177 // JavaScript
178
179 jsdata, err := box.Find("main.js")
180 if err != nil {
181 jsdata = []byte{}
182 }
183 jsAsset := fmt.Sprintf("/main-%x.js", md5.Sum(jsdata))
184
185 //
186 // Favicon
187
188 favicondata, err := box.Find("favicon.ico")
189 if err != nil {
190 favicondata = []byte{}
191 }
192
193 //
194 // Start page text
195
196 startpagedata, err := ioutil.ReadFile(startpagefile)
197 if err != nil {
198 startpagedata, err = box.Find("startpage.txt")
199 if err != nil {
200 startpagedata = []byte{}
201 }
202 }
203 startpagetext := string(startpagedata)
204
205 //
206 //
207
208 var allFiles []string
209 files, err := ioutil.ReadDir("./tpl")
210 if err != nil {
211 fmt.Println(err)
212 }
213 for _, file := range files {
214 filename := file.Name()
215 if strings.HasSuffix(filename, ".html") {
216 allFiles = append(allFiles, "./tpl/"+filename)
217 }
218 }
219
220 templates, err = template.ParseFiles(allFiles...)
221
222 //
223
224 funcMap := template.FuncMap{
225 "safeHtml": func(s string) template.HTML {
226 return template.HTML(s)
227 },
228 "safeCss": func(s string) template.CSS {
229 return template.CSS(s)
230 },
231 "safeJs": func(s string) template.JS {
232 return template.JS(s)
233 },
234 "HTMLEscape": func(s string) string {
235 return html.EscapeString(s)
236 },
237 "split": strings.Split,
238 "last": func(s []string) string {
239 return s[len(s)-1]
240 },
241 "pop": func(s []string) []string {
242 return s[:len(s)-1]
243 },
244 "replace": func(pattern, output string, input interface{}) string {
245 var re = regexp.MustCompile(pattern)
246 var inputStr = fmt.Sprintf("%v", input)
247 return re.ReplaceAllString(inputStr, output)
248 },
249 "trimLeftChar": func(s string) string {
250 for i := range s {
251 if i > 0 {
252 return s[i:]
253 }
254 }
255 return s[:0]
256 },
257 "hasPrefix": func(s string, prefix string) bool {
258 return strings.HasPrefix(s, prefix)
259 },
260 "title": func(s string) string {
261 return strings.Title(s)
262 },
263 }
264
265 //
266
267 startpageTpl := templates.Lookup("startpage.html").Funcs(funcMap)
268 geminiTpl := templates.Lookup("gemini.html").Funcs(funcMap)
269 gopherTpl := templates.Lookup("gopher.html").Funcs(funcMap)
270
271 //
272 //
273
274 vips.Startup(&vips.Config{
275 ConcurrencyLevel: vipsconcurrency,
276 })
277
278 assets := AssetList{
279 Style: styleAsset,
280 JS: jsAsset,
281 FontW: fontwAsset,
282 FontW2: fontw2Asset,
283 PropFontW: propfontwAsset,
284 PropFontW2: propfontw2Asset,
285 }
286
287 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets)))
288 http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(gopherTpl, robotsdata, assets, robotsdebug)))
289 http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(geminiTpl, robotsdata, assets, robotsdebug)))
290 http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata)))
291 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata)))
292 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata)))
293 http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata)))
294 http.HandleFunc(fontwAsset, FontHandler(false, fontdataw))
295 http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2))
296 http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw))
297 http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2))
298 //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
299
300 return http.ListenAndServe(bind, nil)
301}