aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdnan Maolood <[email protected]>2020-10-21 17:07:28 -0400
committerAdnan Maolood <[email protected]>2020-10-21 17:07:28 -0400
commit9506f69f1aef8722f06bb47d65f99ff0bffb7347 (patch)
tree35fec7ce8ecb79c758f01d9d48550945b845e2e8
parentRename Handler to Responder (diff)
downloadgo-gemini-9506f69f1aef8722f06bb47d65f99ff0bffb7347.tar.xz
go-gemini-9506f69f1aef8722f06bb47d65f99ff0bffb7347.zip
Refactor
-rw-r--r--client.go165
-rw-r--r--doc.go6
-rw-r--r--request.go92
-rw-r--r--response.go85
-rw-r--r--server.go119
5 files changed, 213 insertions, 254 deletions
diff --git a/client.go b/client.go
index abf9a57..a513a0a 100644
--- a/client.go
+++ b/client.go
@@ -4,173 +4,8 @@ import (
"bufio"
"crypto/tls"
"crypto/x509"
- "io/ioutil"
- "net"
- "net/url"
- "strconv"
)
-// Request represents a Gemini request.
-type Request struct {
- // URL specifies the URL being requested.
- URL *url.URL
-
- // For client requests, Host specifies the host on which the URL is sought.
- // Host must contain a port.
- // This field is ignored by the server.
- Host string
-
- // Certificate specifies the TLS certificate to use for the request.
- // Request certificates take precedence over client certificates.
- // This field is ignored by the server.
- Certificate *tls.Certificate
-
- // RemoteAddr allows servers and other software to record the network
- // address that sent the request.
- // This field is ignored by the client.
- RemoteAddr net.Addr
-
- // TLS allows servers and other software to record information about the TLS
- // connection on which the request was received.
- // This field is ignored by the client.
- TLS tls.ConnectionState
-}
-
-// hostname returns the host without the port.
-func hostname(host string) string {
- hostname, _, err := net.SplitHostPort(host)
- if err != nil {
- return host
- }
- return hostname
-}
-
-// NewRequest returns a new request. The host is inferred from the provided URL.
-func NewRequest(rawurl string) (*Request, error) {
- u, err := url.Parse(rawurl)
- if err != nil {
- return nil, err
- }
-
- // If there is no port, use the default port of 1965
- host := u.Host
- if u.Port() == "" {
- host += ":1965"
- }
-
- return &Request{
- Host: host,
- URL: u,
- }, nil
-}
-
-// NewRequestTo returns a new request for the provided URL to the provided host.
-// The host must contain a port.
-func NewRequestTo(rawurl, host string) (*Request, error) {
- u, err := url.Parse(rawurl)
- if err != nil {
- return nil, err
- }
-
- return &Request{
- Host: host,
- URL: u,
- }, nil
-}
-
-// write writes the Gemini request to the provided buffered writer.
-func (r *Request) write(w *bufio.Writer) error {
- url := r.URL.String()
- // User is invalid
- if r.URL.User != nil || len(url) > 1024 {
- return ErrInvalidURL
- }
- if _, err := w.WriteString(url); err != nil {
- return err
- }
- if _, err := w.Write(crlf); err != nil {
- return err
- }
- return nil
-}
-
-// Response is a Gemini response.
-type Response struct {
- // Status represents the response status.
- Status int
-
- // Meta contains more information related to the response status.
- // For successful responses, Meta should contain the mimetype of the response.
- // For failure responses, Meta should contain a short description of the failure.
- // Meta should not be longer than 1024 bytes.
- Meta string
-
- // Body contains the response body.
- Body []byte
-
- // TLS contains information about the TLS connection on which the response
- // was received.
- TLS tls.ConnectionState
-}
-
-// read reads a Gemini response from the provided buffered reader.
-func (resp *Response) read(r *bufio.Reader) error {
- // Read the status
- statusB := make([]byte, 2)
- if _, err := r.Read(statusB); err != nil {
- return err
- }
- status, err := strconv.Atoi(string(statusB))
- if err != nil {
- return err
- }
- resp.Status = status
-
- // Disregard invalid status codes
- const minStatus, maxStatus = 1, 6
- statusClass := status / 10
- if statusClass < minStatus || statusClass > maxStatus {
- return ErrInvalidResponse
- }
-
- // Read one space
- if b, err := r.ReadByte(); err != nil {
- return err
- } else if b != ' ' {
- return ErrInvalidResponse
- }
-
- // Read the meta
- meta, err := r.ReadString('\r')
- if err != nil {
- return err
- }
- // Trim carriage return
- meta = meta[:len(meta)-1]
- // Ensure meta is less than or equal to 1024 bytes
- if len(meta) > 1024 {
- return ErrInvalidResponse
- }
- resp.Meta = meta
-
- // Read terminating newline
- if b, err := r.ReadByte(); err != nil {
- return err
- } else if b != '\n' {
- return ErrInvalidResponse
- }
-
- // Read response body
- if status/10 == StatusClassSuccess {
- var err error
- resp.Body, err = ioutil.ReadAll(r)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
// Client represents a Gemini client.
type Client struct {
// KnownHosts is a list of known hosts that the client trusts.
diff --git a/doc.go b/doc.go
index 7f7e62d..84dea50 100644
--- a/doc.go
+++ b/doc.go
@@ -64,13 +64,13 @@ Servers must be configured with certificates:
Servers can accept requests for multiple hosts and schemes:
- server.HandleFunc("example.com", func(w *gmi.ResponseWriter, r *gmi.Request) {
+ server.RegisterFunc("example.com", func(w *gmi.ResponseWriter, r *gmi.Request) {
fmt.Fprint(w, "Welcome to example.com")
})
- server.HandleFunc("example.org", func(w *gmi.ResponseWriter, r *gmi.Request) {
+ server.RegisterFunc("example.org", func(w *gmi.ResponseWriter, r *gmi.Request) {
fmt.Fprint(w, "Welcome to example.org")
})
- server.HandleFunc("http://example.net", func(w *gmi.ResponseWriter, r *gmi.Request) {
+ server.RegisterFunc("http://example.net", func(w *gmi.ResponseWriter, r *gmi.Request) {
fmt.Fprint(w, "Proxied content from http://example.net")
})
diff --git a/request.go b/request.go
new file mode 100644
index 0000000..7b2d209
--- /dev/null
+++ b/request.go
@@ -0,0 +1,92 @@
+package gmi
+
+import (
+ "bufio"
+ "crypto/tls"
+ "net"
+ "net/url"
+)
+
+// Request represents a Gemini request.
+type Request struct {
+ // URL specifies the URL being requested.
+ URL *url.URL
+
+ // For client requests, Host specifies the host on which the URL is sought.
+ // Host must contain a port.
+ // This field is ignored by the server.
+ Host string
+
+ // Certificate specifies the TLS certificate to use for the request.
+ // Request certificates take precedence over client certificates.
+ // This field is ignored by the server.
+ Certificate *tls.Certificate
+
+ // RemoteAddr allows servers and other software to record the network
+ // address that sent the request.
+ // This field is ignored by the client.
+ RemoteAddr net.Addr
+
+ // TLS allows servers and other software to record information about the TLS
+ // connection on which the request was received.
+ // This field is ignored by the client.
+ TLS tls.ConnectionState
+}
+
+// hostname returns the host without the port.
+func hostname(host string) string {
+ hostname, _, err := net.SplitHostPort(host)
+ if err != nil {
+ return host
+ }
+ return hostname
+}
+
+// NewRequest returns a new request. The host is inferred from the provided URL.
+func NewRequest(rawurl string) (*Request, error) {
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return nil, err
+ }
+
+ // If there is no port, use the default port of 1965
+ host := u.Host
+ if u.Port() == "" {
+ host += ":1965"
+ }
+
+ return &Request{
+ Host: host,
+ URL: u,
+ }, nil
+}
+
+// NewRequestTo returns a new request for the provided URL to the provided host.
+// The host must contain a port.
+func NewRequestTo(rawurl, host string) (*Request, error) {
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Request{
+ Host: host,
+ URL: u,
+ }, nil
+}
+
+// write writes the Gemini request to the provided buffered writer.
+func (r *Request) write(w *bufio.Writer) error {
+ url := r.URL.String()
+ // User is invalid
+ if r.URL.User != nil || len(url) > 1024 {
+ return ErrInvalidURL
+ }
+ if _, err := w.WriteString(url); err != nil {
+ return err
+ }
+ if _, err := w.Write(crlf); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/response.go b/response.go
new file mode 100644
index 0000000..975425a
--- /dev/null
+++ b/response.go
@@ -0,0 +1,85 @@
+package gmi
+
+import (
+ "bufio"
+ "crypto/tls"
+ "io/ioutil"
+ "strconv"
+)
+
+// Response is a Gemini response.
+type Response struct {
+ // Status represents the response status.
+ Status int
+
+ // Meta contains more information related to the response status.
+ // For successful responses, Meta should contain the mimetype of the response.
+ // For failure responses, Meta should contain a short description of the failure.
+ // Meta should not be longer than 1024 bytes.
+ Meta string
+
+ // Body contains the response body.
+ Body []byte
+
+ // TLS contains information about the TLS connection on which the response
+ // was received.
+ TLS tls.ConnectionState
+}
+
+// read reads a Gemini response from the provided buffered reader.
+func (resp *Response) read(r *bufio.Reader) error {
+ // Read the status
+ statusB := make([]byte, 2)
+ if _, err := r.Read(statusB); err != nil {
+ return err
+ }
+ status, err := strconv.Atoi(string(statusB))
+ if err != nil {
+ return err
+ }
+ resp.Status = status
+
+ // Disregard invalid status codes
+ const minStatus, maxStatus = 1, 6
+ statusClass := status / 10
+ if statusClass < minStatus || statusClass > maxStatus {
+ return ErrInvalidResponse
+ }
+
+ // Read one space
+ if b, err := r.ReadByte(); err != nil {
+ return err
+ } else if b != ' ' {
+ return ErrInvalidResponse
+ }
+
+ // Read the meta
+ meta, err := r.ReadString('\r')
+ if err != nil {
+ return err
+ }
+ // Trim carriage return
+ meta = meta[:len(meta)-1]
+ // Ensure meta is less than or equal to 1024 bytes
+ if len(meta) > 1024 {
+ return ErrInvalidResponse
+ }
+ resp.Meta = meta
+
+ // Read terminating newline
+ if b, err := r.ReadByte(); err != nil {
+ return err
+ } else if b != '\n' {
+ return ErrInvalidResponse
+ }
+
+ // Read response body
+ if status/10 == StatusClassSuccess {
+ var err error
+ resp.Body, err = ioutil.ReadAll(r)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/server.go b/server.go
index 7995160..f978d82 100644
--- a/server.go
+++ b/server.go
@@ -184,7 +184,7 @@ func (s *Server) responder(r *Request) Responder {
return h
}
}
- return NotFoundHandler()
+ return NotFoundResponder()
}
// ResponseWriter is used by a Gemini handler to construct a Gemini response.
@@ -313,35 +313,19 @@ func Redirect(w *ResponseWriter, r *Request, url string) {
w.WriteHeader(StatusRedirect, url)
}
-// RedirectHandler returns a simple handler that responds to each request with
-// a redirect to the given URL.
-func RedirectHandler(url string) Responder {
- return ResponderFunc(func(w *ResponseWriter, r *Request) {
- Redirect(w, r, url)
- })
-}
-
// PermanentRedirect replies to the request with a permanent redirect to the given URL.
func PermanentRedirect(w *ResponseWriter, r *Request, url string) {
w.WriteHeader(StatusRedirectPermanent, url)
}
-// PermanentRedirectHandler returns a simple handler that responds to each request with
-// a redirect to the given URL.
-func PermanentRedirectHandler(url string) Responder {
- return ResponderFunc(func(w *ResponseWriter, r *Request) {
- PermanentRedirect(w, r, url)
- })
-}
-
// NotFound replies to the request with the NotFound status code.
func NotFound(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusNotFound, "Not found")
}
-// NotFoundHandler returns a simple handler that responds to each request with
+// NotFoundResponder returns a simple responder that responds to each request with
// the status code NotFound.
-func NotFoundHandler() Responder {
+func NotFoundResponder() Responder {
return ResponderFunc(NotFound)
}
@@ -350,12 +334,6 @@ func Gone(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusGone, "Gone")
}
-// GoneHandler returns a simple handler that responds to each request with
-// the status code Gone.
-func GoneHandler() Responder {
- return ResponderFunc(Gone)
-}
-
// CertificateRequired responds to the request with the CertificateRequired
// status code.
func CertificateRequired(w *ResponseWriter, r *Request) {
@@ -379,15 +357,6 @@ func WithCertificate(w *ResponseWriter, r *Request, f func(*x509.Certificate)) {
f(cert)
}
-// CertificateHandler returns a simple handler that requests a certificate from
-// clients if they did not provide one, and calls f with the first certificate
-// if they did.
-func CertificateHandler(f func(*x509.Certificate)) Responder {
- return ResponderFunc(func(w *ResponseWriter, r *Request) {
- WithCertificate(w, r, f)
- })
-}
-
// ResponderFunc is a wrapper around a bare function that implements Handler.
type ResponderFunc func(*ResponseWriter, *Request)
@@ -437,7 +406,7 @@ type ServeMux struct {
}
type muxEntry struct {
- h Responder
+ r Responder
pattern string
}
@@ -465,21 +434,21 @@ func cleanPath(p string) string {
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
-func (mux *ServeMux) match(path string) (h Responder, pattern string) {
+func (mux *ServeMux) match(path string) Responder {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
- return v.h, v.pattern
+ return v.r
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
- return e.h, e.pattern
+ return e.r
}
}
- return nil, ""
+ return nil
}
// redirectToPathSlash determines if the given path needs appending "/" to it.
@@ -517,69 +486,47 @@ func (mux *ServeMux) shouldRedirectRLocked(path string) bool {
return false
}
-// Handler returns the handler to use for the given request.
-// It consults r.URL.Path. It always returns a non-nil handler.
-// If the path is not in its canonical form, the
-// handler will be an internally-generated handler that redirects
-// to the canonical path. If the host contains a port, it is ignored
-// when matching handlers.
-//
-// Handler also returns the registered pattern that matches the
-// request or, in the case of internally-generated redirects,
-// the pattern that will match after following the redirect.
-//
-// If there is no registered handler that applies to the request,
-// Handler returns a "not found" handler and an empty pattern.
-func (mux *ServeMux) Handler(r *Request) (h Responder, pattern string) {
+// Respond dispatches the request to the responder whose
+// pattern most closely matches the request URL.
+func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) {
path := cleanPath(r.URL.Path)
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
- return RedirectHandler(u.String()), u.Path
+ Redirect(w, r, u.String())
+ return
}
if path != r.URL.Path {
- _, pattern = mux.handler(path)
- url := *r.URL
- url.Path = path
- return RedirectHandler(url.String()), pattern
+ u := *r.URL
+ u.Path = path
+ Redirect(w, r, u.String())
+ return
}
- return mux.handler(r.URL.Path)
-}
-
-// handler is the main implementation of Handler.
-// The path is known to be in canonical form.
-func (mux *ServeMux) handler(path string) (h Responder, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
- h, pattern = mux.match(path)
- if h == nil {
- h, pattern = NotFoundHandler(), ""
+ resp := mux.match(path)
+ if resp == nil {
+ NotFound(w, r)
+ return
}
- return
+ resp.Respond(w, r)
}
-// Respond dispatches the request to the handler whose
-// pattern most closely matches the request URL.
-func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) {
- h, _ := mux.Handler(r)
- h.Respond(w, r)
-}
-
-// Handle registers the handler for the given pattern.
-// If a handler already exists for pattern, Handle panics.
-func (mux *ServeMux) Handle(pattern string, handler Responder) {
+// Handle registers the responder for the given pattern.
+// If a responder already exists for pattern, Handle panics.
+func (mux *ServeMux) Handle(pattern string, responder Responder) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("gmi: invalid pattern")
}
- if handler == nil {
- panic("gmi: nil handler")
+ if responder == nil {
+ panic("gmi: nil responder")
}
if _, exist := mux.m[pattern]; exist {
panic("gmi: multiple registrations for " + pattern)
@@ -588,7 +535,7 @@ func (mux *ServeMux) Handle(pattern string, handler Responder) {
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
- e := muxEntry{h: handler, pattern: pattern}
+ e := muxEntry{responder, pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
@@ -610,10 +557,10 @@ func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
return es
}
-// HandleFunc registers the handler function for the given pattern.
-func (mux *ServeMux) HandleFunc(pattern string, handler func(*ResponseWriter, *Request)) {
- if handler == nil {
- panic("gmi: nil handler")
+// HandleFunc registers the responder function for the given pattern.
+func (mux *ServeMux) HandleFunc(pattern string, responder func(*ResponseWriter, *Request)) {
+ if responder == nil {
+ panic("gmi: nil responder")
}
- mux.Handle(pattern, ResponderFunc(handler))
+ mux.Handle(pattern, ResponderFunc(responder))
}