diff options
| -rw-r--r-- | cmd/gopherproxy/main.go | 19 | ||||
| -rw-r--r-- | gopherproxy.go | 131 | ||||
| -rw-r--r-- | main.go | 126 | ||||
| -rw-r--r-- | template.go | 2 |
4 files changed, 151 insertions, 127 deletions
diff --git a/cmd/gopherproxy/main.go b/cmd/gopherproxy/main.go new file mode 100644 index 0000000..bc5050e --- /dev/null +++ b/cmd/gopherproxy/main.go | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | package main | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "flag" | ||
| 5 | "log" | ||
| 6 | |||
| 7 | "github.com/prologic/gopherproxy" | ||
| 8 | ) | ||
| 9 | |||
| 10 | var ( | ||
| 11 | bind = flag.String("bind", ":80", "[int]:port to bind to") | ||
| 12 | uri = flag.String("uri", "127.0.0.1:70", "<host>:[port] to proxy to") | ||
| 13 | ) | ||
| 14 | |||
| 15 | func main() { | ||
| 16 | flag.Parse() | ||
| 17 | |||
| 18 | log.Fatal(gopherproxy.ListenAndServe(*bind, *uri)) | ||
| 19 | } | ||
diff --git a/gopherproxy.go b/gopherproxy.go new file mode 100644 index 0000000..4a2255f --- /dev/null +++ b/gopherproxy.go | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | package gopherproxy | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | "html/template" | ||
| 6 | "io" | ||
| 7 | "io/ioutil" | ||
| 8 | "log" | ||
| 9 | "net/http" | ||
| 10 | "net/url" | ||
| 11 | "strings" | ||
| 12 | |||
| 13 | "github.com/prologic/go-gopher" | ||
| 14 | ) | ||
| 15 | |||
| 16 | type tplRow struct { | ||
| 17 | Link template.URL | ||
| 18 | Type string | ||
| 19 | Text string | ||
| 20 | } | ||
| 21 | |||
| 22 | // Handler is an aliased type for the standard HTTP handler functions | ||
| 23 | type Handler func(w http.ResponseWriter, req *http.Request) | ||
| 24 | |||
| 25 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error { | ||
| 26 | out := make([]tplRow, len(d)) | ||
| 27 | |||
| 28 | for i, x := range d { | ||
| 29 | tr := tplRow{ | ||
| 30 | Text: x.Description, | ||
| 31 | Type: x.Type.String(), | ||
| 32 | } | ||
| 33 | |||
| 34 | if x.Type == gopher.INFO { | ||
| 35 | out[i] = tr | ||
| 36 | continue | ||
| 37 | } | ||
| 38 | |||
| 39 | if strings.HasPrefix(x.Selector, "URL:") { | ||
| 40 | tr.Link = template.URL(x.Selector[4:]) | ||
| 41 | } else { | ||
| 42 | var hostport string | ||
| 43 | if x.Port == 70 { | ||
| 44 | hostport = x.Host | ||
| 45 | } else { | ||
| 46 | hostport = fmt.Sprintf("%s:%d", x.Host, x.Port) | ||
| 47 | } | ||
| 48 | path := url.QueryEscape(x.Selector) | ||
| 49 | path = strings.Replace(path, "%2F", "/", -1) | ||
| 50 | tr.Link = template.URL( | ||
| 51 | fmt.Sprintf( | ||
| 52 | "/%s/%s%s", | ||
| 53 | hostport, | ||
| 54 | string(byte(x.Type)), | ||
| 55 | path, | ||
| 56 | ), | ||
| 57 | ) | ||
| 58 | } | ||
| 59 | |||
| 60 | out[i] = tr | ||
| 61 | } | ||
| 62 | |||
| 63 | return tpl.Execute(w, struct { | ||
| 64 | Title string | ||
| 65 | Lines []tplRow | ||
| 66 | }{hostport, out}) | ||
| 67 | } | ||
| 68 | |||
| 69 | // MakeGopherProxyHandler returns a Handler that proxies requests | ||
| 70 | // to the specified Gopher server as denoated by the first argument | ||
| 71 | // to the request path and renders the content using the provided template. | ||
| 72 | func MakeGopherProxyHandler(tpl *template.Template, uri string) Handler { | ||
| 73 | return func(w http.ResponseWriter, req *http.Request) { | ||
| 74 | parts := strings.Split(strings.TrimPrefix(req.URL.Path, "/"), "/") | ||
| 75 | hostport := parts[0] | ||
| 76 | path := strings.Join(parts[1:], "/") | ||
| 77 | |||
| 78 | if len(hostport) == 0 { | ||
| 79 | http.Redirect(w, req, "/"+uri, http.StatusFound) | ||
| 80 | return | ||
| 81 | } | ||
| 82 | |||
| 83 | uri, err := url.QueryUnescape(path) | ||
| 84 | if err != nil { | ||
| 85 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 86 | return | ||
| 87 | } | ||
| 88 | res, err := gopher.Get( | ||
| 89 | fmt.Sprintf( | ||
| 90 | "gopher://%s/%s", | ||
| 91 | hostport, | ||
| 92 | uri, | ||
| 93 | ), | ||
| 94 | ) | ||
| 95 | if err != nil { | ||
| 96 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 97 | return | ||
| 98 | } | ||
| 99 | |||
| 100 | if res.Body != nil { | ||
| 101 | io.Copy(w, res.Body) | ||
| 102 | } else { | ||
| 103 | if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil { | ||
| 104 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 105 | return | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | // ListenAndServe creates a listening HTTP server bound to | ||
| 112 | // the interface specified by bind and sets up a Gopher to HTTP | ||
| 113 | // proxy proxying requests as requested and by default will prozy | ||
| 114 | // to a Gopher server address specified by uri if no servers is | ||
| 115 | // specified by the request. | ||
| 116 | func ListenAndServe(bind, uri string) error { | ||
| 117 | var tpl *template.Template | ||
| 118 | |||
| 119 | tpldata, err := ioutil.ReadFile(".template") | ||
| 120 | if err == nil { | ||
| 121 | tpltext = string(tpldata) | ||
| 122 | } | ||
| 123 | |||
| 124 | tpl, err = template.New("gophermenu").Parse(tpltext) | ||
| 125 | if err != nil { | ||
| 126 | log.Fatal(err) | ||
| 127 | } | ||
| 128 | |||
| 129 | http.HandleFunc("/", MakeGopherProxyHandler(tpl, uri)) | ||
| 130 | return http.ListenAndServe(bind, nil) | ||
| 131 | } | ||
diff --git a/main.go b/main.go deleted file mode 100644 index 1da5f8e..0000000 --- a/main.go +++ /dev/null | |||
| @@ -1,126 +0,0 @@ | |||
| 1 | package main | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "flag" | ||
| 5 | "fmt" | ||
| 6 | "html/template" | ||
| 7 | "io" | ||
| 8 | "io/ioutil" | ||
| 9 | "log" | ||
| 10 | "net/http" | ||
| 11 | "net/url" | ||
| 12 | "strings" | ||
| 13 | |||
| 14 | "github.com/prologic/go-gopher" | ||
| 15 | ) | ||
| 16 | |||
| 17 | var ( | ||
| 18 | bind = flag.String("bind", ":80", "[int]:port to bind to") | ||
| 19 | uri = flag.String("uri", "127.0.0.1:70", "<host>:[port] to proxy to") | ||
| 20 | |||
| 21 | tpl *template.Template | ||
| 22 | ) | ||
| 23 | |||
| 24 | type tplRow struct { | ||
| 25 | Link template.URL | ||
| 26 | Type string | ||
| 27 | Text string | ||
| 28 | } | ||
| 29 | |||
| 30 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error { | ||
| 31 | out := make([]tplRow, len(d)) | ||
| 32 | |||
| 33 | for i, x := range d { | ||
| 34 | tr := tplRow{ | ||
| 35 | Text: x.Description, | ||
| 36 | Type: x.Type.String(), | ||
| 37 | } | ||
| 38 | |||
| 39 | if x.Type == gopher.INFO { | ||
| 40 | out[i] = tr | ||
| 41 | continue | ||
| 42 | } | ||
| 43 | |||
| 44 | if strings.HasPrefix(x.Selector, "URL:") { | ||
| 45 | tr.Link = template.URL(x.Selector[4:]) | ||
| 46 | } else { | ||
| 47 | var hostport string | ||
| 48 | if x.Port == 70 { | ||
| 49 | hostport = x.Host | ||
| 50 | } else { | ||
| 51 | hostport = fmt.Sprintf("%s:%d", x.Host, x.Port) | ||
| 52 | } | ||
| 53 | path := url.QueryEscape(x.Selector) | ||
| 54 | path = strings.Replace(path, "%2F", "/", -1) | ||
| 55 | tr.Link = template.URL( | ||
| 56 | fmt.Sprintf( | ||
| 57 | "/%s/%s%s", | ||
| 58 | hostport, | ||
| 59 | string(byte(x.Type)), | ||
| 60 | path, | ||
| 61 | ), | ||
| 62 | ) | ||
| 63 | } | ||
| 64 | |||
| 65 | out[i] = tr | ||
| 66 | } | ||
| 67 | |||
| 68 | return tpl.Execute(w, struct { | ||
| 69 | Title string | ||
| 70 | Lines []tplRow | ||
| 71 | }{hostport, out}) | ||
| 72 | } | ||
| 73 | |||
| 74 | func proxy(w http.ResponseWriter, req *http.Request) { | ||
| 75 | parts := strings.Split(strings.TrimPrefix(req.URL.Path, "/"), "/") | ||
| 76 | hostport := parts[0] | ||
| 77 | path := strings.Join(parts[1:], "/") | ||
| 78 | |||
| 79 | if len(hostport) == 0 { | ||
| 80 | http.Redirect(w, req, "/"+*uri, http.StatusFound) | ||
| 81 | return | ||
| 82 | } | ||
| 83 | |||
| 84 | uri, err := url.QueryUnescape(path) | ||
| 85 | if err != nil { | ||
| 86 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 87 | return | ||
| 88 | } | ||
| 89 | res, err := gopher.Get( | ||
| 90 | fmt.Sprintf( | ||
| 91 | "gopher://%s/%s", | ||
| 92 | hostport, | ||
| 93 | uri, | ||
| 94 | ), | ||
| 95 | ) | ||
| 96 | if err != nil { | ||
| 97 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 98 | return | ||
| 99 | } | ||
| 100 | |||
| 101 | if res.Body != nil { | ||
| 102 | io.Copy(w, res.Body) | ||
| 103 | } else { | ||
| 104 | if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil { | ||
| 105 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
| 106 | return | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | func main() { | ||
| 112 | flag.Parse() | ||
| 113 | |||
| 114 | tpldata, err := ioutil.ReadFile(".template") | ||
| 115 | if err == nil { | ||
| 116 | tpltext = string(tpldata) | ||
| 117 | } | ||
| 118 | |||
| 119 | tpl, err = template.New("gophermenu").Parse(tpltext) | ||
| 120 | if err != nil { | ||
| 121 | log.Fatal(err) | ||
| 122 | } | ||
| 123 | |||
| 124 | http.HandleFunc("/", proxy) | ||
| 125 | log.Fatal(http.ListenAndServe(*bind, nil)) | ||
| 126 | } | ||
diff --git a/template.go b/template.go index 514ff30..604f380 100644 --- a/template.go +++ b/template.go | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | package main | 1 | package gopherproxy |
| 2 | 2 | ||
| 3 | var tpltext = `<!doctype html> | 3 | var tpltext = `<!doctype html> |
| 4 | <html> | 4 | <html> |
