aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdnan Maolood <[email protected]>2021-02-17 13:36:16 -0500
committerAdnan Maolood <[email protected]>2021-02-17 13:36:16 -0500
commit110c2de6de7b0a82b7ce60bb815a02b4bae80bca (patch)
treef1a41dc9166e9801be5f7894b739c417b240c671
parentstatus: Fix typo (diff)
downloadgo-gemini-110c2de6de7b0a82b7ce60bb815a02b4bae80bca.tar.xz
go-gemini-110c2de6de7b0a82b7ce60bb815a02b4bae80bca.zip
Redesign ResponseWriter interface
-rw-r--r--fs.go18
-rw-r--r--mux.go6
-rw-r--r--response.go96
-rw-r--r--server.go4
4 files changed, 60 insertions, 64 deletions
diff --git a/fs.go b/fs.go
index 67d9206..52a4f77 100644
--- a/fs.go
+++ b/fs.go
@@ -49,7 +49,7 @@ func serveContent(w ResponseWriter, name string, content io.Reader) {
// Detect mimetype from file extension
ext := path.Ext(name)
mimetype := mime.TypeByExtension(ext)
- w.Meta(mimetype)
+ w.MediaType(mimetype)
io.Copy(w, content)
}
@@ -80,7 +80,7 @@ func ServeFile(w ResponseWriter, r *Request, fsys fs.FS, name string) {
// here and ".." may not be wanted.
// Note that name might not contain "..", for example if code (still
// incorrectly) used filepath.Join(myDir, r.URL.Path).
- w.Header(StatusBadRequest, "invalid URL path")
+ w.WriteHeader(StatusBadRequest, "invalid URL path")
return
}
serveFile(w, r, fsys, name, false)
@@ -105,7 +105,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
// Redirect .../index.gmi to .../
if strings.HasSuffix(r.URL.Path, indexPage) {
- w.Header(StatusPermanentRedirect, "./")
+ w.WriteHeader(StatusPermanentRedirect, "./")
return
}
@@ -117,14 +117,14 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
f, err := fsys.Open(name)
if err != nil {
- w.Status(StatusNotFound)
+ w.WriteHeader(StatusNotFound, "Not found")
return
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
- w.Status(StatusTemporaryFailure)
+ w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
return
}
@@ -134,13 +134,13 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
if stat.IsDir() {
// Add trailing slash
if url[len(url)-1] != '/' {
- w.Header(StatusPermanentRedirect, path.Base(url)+"/")
+ w.WriteHeader(StatusPermanentRedirect, path.Base(url)+"/")
return
}
} else {
// Remove trailing slash
if url[len(url)-1] == '/' {
- w.Header(StatusPermanentRedirect, "../"+path.Base(url))
+ w.WriteHeader(StatusPermanentRedirect, "../"+path.Base(url))
return
}
}
@@ -150,7 +150,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
// Redirect if the directory name doesn't end in a slash
url := r.URL.Path
if url[len(url)-1] != '/' {
- w.Header(StatusRedirect, path.Base(url)+"/")
+ w.WriteHeader(StatusRedirect, path.Base(url)+"/")
return
}
@@ -183,7 +183,7 @@ func dirList(w ResponseWriter, f fs.File) {
entries, err = d.ReadDir(-1)
}
if !ok || err != nil {
- w.Header(StatusTemporaryFailure, "Error reading directory")
+ w.WriteHeader(StatusTemporaryFailure, "Error reading directory")
return
}
diff --git a/mux.go b/mux.go
index 80ddaa6..6073ba1 100644
--- a/mux.go
+++ b/mux.go
@@ -138,14 +138,14 @@ func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) {
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
- w.Header(StatusRedirect, u.String())
+ w.WriteHeader(StatusRedirect, u.String())
return
}
if path != r.URL.Path {
u := *r.URL
u.Path = path
- w.Header(StatusRedirect, u.String())
+ w.WriteHeader(StatusRedirect, u.String())
return
}
@@ -154,7 +154,7 @@ func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) {
resp := mux.match(path)
if resp == nil {
- w.Status(StatusNotFound)
+ w.WriteHeader(StatusNotFound, "Not found")
return
}
resp.ServeGemini(w, r)
diff --git a/response.go b/response.go
index ea2184b..a6f40f5 100644
--- a/response.go
+++ b/response.go
@@ -7,6 +7,9 @@ import (
"strconv"
)
+// The default media type for responses.
+const defaultMediaType = "text/gemini; charset=utf-8"
+
// Response represents the response from a Gemini request.
//
// The Client returns Responses from servers once the response
@@ -73,9 +76,9 @@ func ReadResponse(rc io.ReadCloser) (*Response, error) {
if len(meta) > 1024 {
return nil, ErrInvalidResponse
}
- // Default mime type of text/gemini; charset=utf-8
if StatusClass(status) == StatusSuccess && meta == "" {
- meta = "text/gemini; charset=utf-8"
+ // Use default media type
+ meta = defaultMediaType
}
resp.Meta = meta
@@ -132,30 +135,39 @@ func (b *readCloserBody) Read(p []byte) (n int, err error) {
return b.ReadCloser.Read(p)
}
-// A ResponseWriter interface is used by a Gemini handler
-// to construct a Gemini response.
+// A ResponseWriter interface is used by a Gemini handler to construct
+// a Gemini response.
+//
+// A ResponseWriter may not be used after the Handler.ServeGemini method
+// has returned.
type ResponseWriter interface {
- // Header sets the response header.
- Header(status int, meta string)
+ // MediaType sets the media type that will be sent by Write for a
+ // successful response. If no media type is set, a default of
+ // "text/gemini; charset=utf-8" will be used.
+ //
+ // Setting the media type after a call to Write or WriteHeader has
+ // no effect.
+ MediaType(string)
- // Status sets the response status code.
- // It also sets the response meta to StatusText(status).
- Status(status int)
+ // Write writes the data to the connection as part of a Gemini response.
+ //
+ // If WriteHeader has not yet been called, Write calls WriteHeader with
+ // StatusSuccess and the media type set in MediaType before writing the data.
+ // If no media type was set, Write uses a default media type of
+ // "text/gemini; charset=utf-8".
+ Write([]byte) (int, error)
- // Meta sets the response meta.
+ // WriteHeader sends a Gemini response header with the provided
+ // status code and meta.
//
- // For successful responses, meta should contain the media type of the response.
- // For failure responses, meta should contain a short description of the failure.
- // The response meta should not be greater than 1024 bytes.
- Meta(meta string)
-
- // Write writes data to the connection as part of the response body.
- // If the response status does not allow for a response body, Write returns
- // ErrBodyNotAllowed.
+ // If WriteHeader is not called explicitly, the first call to Write
+ // will trigger an implicit call to WriteHeader with a successful
+ // status code and the media type set in MediaType.
//
- // Write writes the response header if it has not already been written.
- // It writes a successful status code if one is not set.
- Write([]byte) (int, error)
+ // The provided code must be a valid Gemini status code.
+ // The provided meta must not be longer than 1024 bytes.
+ // Only one header may be written.
+ WriteHeader(statusCode int, meta string)
}
// The Flusher interface is implemented by ResponseWriters that allow a
@@ -171,8 +183,7 @@ type Flusher interface {
type responseWriter struct {
b *bufio.Writer
- status int
- meta string
+ mediatype string
wroteHeader bool
bodyAllowed bool
}
@@ -188,23 +199,18 @@ func newResponseWriter(w io.Writer) *responseWriter {
}
}
-func (w *responseWriter) Header(status int, meta string) {
- w.status = status
- w.meta = meta
-}
-
-func (w *responseWriter) Status(status int) {
- w.status = status
- w.meta = StatusText(status)
-}
-
-func (w *responseWriter) Meta(meta string) {
- w.meta = meta
+func (w *responseWriter) MediaType(mediatype string) {
+ w.mediatype = mediatype
}
func (w *responseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
- w.writeHeader(StatusSuccess)
+ meta := w.mediatype
+ if meta == "" {
+ // Use default media type
+ meta = defaultMediaType
+ }
+ w.WriteHeader(StatusSuccess, meta)
}
if !w.bodyAllowed {
return 0, ErrBodyNotAllowed
@@ -212,22 +218,12 @@ func (w *responseWriter) Write(b []byte) (int, error) {
return w.b.Write(b)
}
-func (w *responseWriter) writeHeader(defaultStatus int) {
- status := w.status
- if status == 0 {
- status = defaultStatus
- }
-
- meta := w.meta
- if StatusClass(status) == StatusSuccess {
+func (w *responseWriter) WriteHeader(statusCode int, meta string) {
+ if StatusClass(statusCode) == StatusSuccess {
w.bodyAllowed = true
-
- if meta == "" {
- meta = "text/gemini"
- }
}
- w.b.WriteString(strconv.Itoa(status))
+ w.b.WriteString(strconv.Itoa(statusCode))
w.b.WriteByte(' ')
w.b.WriteString(meta)
w.b.Write(crlf)
@@ -236,7 +232,7 @@ func (w *responseWriter) writeHeader(defaultStatus int) {
func (w *responseWriter) Flush() error {
if !w.wroteHeader {
- w.writeHeader(StatusTemporaryFailure)
+ w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
}
// Write errors from writeHeader will be returned here.
return w.b.Flush()
diff --git a/server.go b/server.go
index d5dacd9..a90fe50 100644
--- a/server.go
+++ b/server.go
@@ -380,7 +380,7 @@ func (srv *Server) respond(conn net.Conn) {
req, err := ReadRequest(conn)
if err != nil {
- w.Status(StatusBadRequest)
+ w.WriteHeader(StatusBadRequest, "Bad request")
w.Flush()
return
}
@@ -396,7 +396,7 @@ func (srv *Server) respond(conn net.Conn) {
h := srv.handler(req)
if h == nil {
- w.Status(StatusNotFound)
+ w.WriteHeader(StatusNotFound, "Not found")
w.Flush()
return
}