Redesign ResponseWriter interface
This commit is contained in:
parent
8543eca416
commit
110c2de6de
18
fs.go
18
fs.go
@ -49,7 +49,7 @@ func serveContent(w ResponseWriter, name string, content io.Reader) {
|
|||||||
// Detect mimetype from file extension
|
// Detect mimetype from file extension
|
||||||
ext := path.Ext(name)
|
ext := path.Ext(name)
|
||||||
mimetype := mime.TypeByExtension(ext)
|
mimetype := mime.TypeByExtension(ext)
|
||||||
w.Meta(mimetype)
|
w.MediaType(mimetype)
|
||||||
io.Copy(w, content)
|
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.
|
// here and ".." may not be wanted.
|
||||||
// Note that name might not contain "..", for example if code (still
|
// Note that name might not contain "..", for example if code (still
|
||||||
// incorrectly) used filepath.Join(myDir, r.URL.Path).
|
// incorrectly) used filepath.Join(myDir, r.URL.Path).
|
||||||
w.Header(StatusBadRequest, "invalid URL path")
|
w.WriteHeader(StatusBadRequest, "invalid URL path")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serveFile(w, r, fsys, name, false)
|
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 .../
|
// Redirect .../index.gmi to .../
|
||||||
if strings.HasSuffix(r.URL.Path, indexPage) {
|
if strings.HasSuffix(r.URL.Path, indexPage) {
|
||||||
w.Header(StatusPermanentRedirect, "./")
|
w.WriteHeader(StatusPermanentRedirect, "./")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,14 +117,14 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
|
|||||||
|
|
||||||
f, err := fsys.Open(name)
|
f, err := fsys.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Status(StatusNotFound)
|
w.WriteHeader(StatusNotFound, "Not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
stat, err := f.Stat()
|
stat, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Status(StatusTemporaryFailure)
|
w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,13 +134,13 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
|
|||||||
if stat.IsDir() {
|
if stat.IsDir() {
|
||||||
// Add trailing slash
|
// Add trailing slash
|
||||||
if url[len(url)-1] != '/' {
|
if url[len(url)-1] != '/' {
|
||||||
w.Header(StatusPermanentRedirect, path.Base(url)+"/")
|
w.WriteHeader(StatusPermanentRedirect, path.Base(url)+"/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Remove trailing slash
|
// Remove trailing slash
|
||||||
if url[len(url)-1] == '/' {
|
if url[len(url)-1] == '/' {
|
||||||
w.Header(StatusPermanentRedirect, "../"+path.Base(url))
|
w.WriteHeader(StatusPermanentRedirect, "../"+path.Base(url))
|
||||||
return
|
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
|
// Redirect if the directory name doesn't end in a slash
|
||||||
url := r.URL.Path
|
url := r.URL.Path
|
||||||
if url[len(url)-1] != '/' {
|
if url[len(url)-1] != '/' {
|
||||||
w.Header(StatusRedirect, path.Base(url)+"/")
|
w.WriteHeader(StatusRedirect, path.Base(url)+"/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ func dirList(w ResponseWriter, f fs.File) {
|
|||||||
entries, err = d.ReadDir(-1)
|
entries, err = d.ReadDir(-1)
|
||||||
}
|
}
|
||||||
if !ok || err != nil {
|
if !ok || err != nil {
|
||||||
w.Header(StatusTemporaryFailure, "Error reading directory")
|
w.WriteHeader(StatusTemporaryFailure, "Error reading directory")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
mux.go
6
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,
|
// If the given path is /tree and its handler is not registered,
|
||||||
// redirect for /tree/.
|
// redirect for /tree/.
|
||||||
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
|
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
|
||||||
w.Header(StatusRedirect, u.String())
|
w.WriteHeader(StatusRedirect, u.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != r.URL.Path {
|
if path != r.URL.Path {
|
||||||
u := *r.URL
|
u := *r.URL
|
||||||
u.Path = path
|
u.Path = path
|
||||||
w.Header(StatusRedirect, u.String())
|
w.WriteHeader(StatusRedirect, u.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) {
|
|||||||
|
|
||||||
resp := mux.match(path)
|
resp := mux.match(path)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
w.Status(StatusNotFound)
|
w.WriteHeader(StatusNotFound, "Not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.ServeGemini(w, r)
|
resp.ServeGemini(w, r)
|
||||||
|
96
response.go
96
response.go
@ -7,6 +7,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The default media type for responses.
|
||||||
|
const defaultMediaType = "text/gemini; charset=utf-8"
|
||||||
|
|
||||||
// Response represents the response from a Gemini request.
|
// Response represents the response from a Gemini request.
|
||||||
//
|
//
|
||||||
// The Client returns Responses from servers once the response
|
// The Client returns Responses from servers once the response
|
||||||
@ -73,9 +76,9 @@ func ReadResponse(rc io.ReadCloser) (*Response, error) {
|
|||||||
if len(meta) > 1024 {
|
if len(meta) > 1024 {
|
||||||
return nil, ErrInvalidResponse
|
return nil, ErrInvalidResponse
|
||||||
}
|
}
|
||||||
// Default mime type of text/gemini; charset=utf-8
|
|
||||||
if StatusClass(status) == StatusSuccess && meta == "" {
|
if StatusClass(status) == StatusSuccess && meta == "" {
|
||||||
meta = "text/gemini; charset=utf-8"
|
// Use default media type
|
||||||
|
meta = defaultMediaType
|
||||||
}
|
}
|
||||||
resp.Meta = meta
|
resp.Meta = meta
|
||||||
|
|
||||||
@ -132,30 +135,39 @@ func (b *readCloserBody) Read(p []byte) (n int, err error) {
|
|||||||
return b.ReadCloser.Read(p)
|
return b.ReadCloser.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ResponseWriter interface is used by a Gemini handler
|
// A ResponseWriter interface is used by a Gemini handler to construct
|
||||||
// to construct a Gemini response.
|
// a Gemini response.
|
||||||
|
//
|
||||||
|
// A ResponseWriter may not be used after the Handler.ServeGemini method
|
||||||
|
// has returned.
|
||||||
type ResponseWriter interface {
|
type ResponseWriter interface {
|
||||||
// Header sets the response header.
|
// MediaType sets the media type that will be sent by Write for a
|
||||||
Header(status int, meta string)
|
// successful response. If no media type is set, a default of
|
||||||
|
// "text/gemini; charset=utf-8" will be used.
|
||||||
// Status sets the response status code.
|
|
||||||
// It also sets the response meta to StatusText(status).
|
|
||||||
Status(status int)
|
|
||||||
|
|
||||||
// Meta sets the response meta.
|
|
||||||
//
|
//
|
||||||
// For successful responses, meta should contain the media type of the response.
|
// Setting the media type after a call to Write or WriteHeader has
|
||||||
// For failure responses, meta should contain a short description of the failure.
|
// no effect.
|
||||||
// The response meta should not be greater than 1024 bytes.
|
MediaType(string)
|
||||||
Meta(meta string)
|
|
||||||
|
|
||||||
// Write writes data to the connection as part of the response body.
|
// Write writes the data to the connection as part of a Gemini response.
|
||||||
// If the response status does not allow for a response body, Write returns
|
|
||||||
// ErrBodyNotAllowed.
|
|
||||||
//
|
//
|
||||||
// Write writes the response header if it has not already been written.
|
// If WriteHeader has not yet been called, Write calls WriteHeader with
|
||||||
// It writes a successful status code if one is not set.
|
// 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)
|
Write([]byte) (int, error)
|
||||||
|
|
||||||
|
// WriteHeader sends a Gemini response header with the provided
|
||||||
|
// status code and meta.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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
|
// The Flusher interface is implemented by ResponseWriters that allow a
|
||||||
@ -171,8 +183,7 @@ type Flusher interface {
|
|||||||
|
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
b *bufio.Writer
|
b *bufio.Writer
|
||||||
status int
|
mediatype string
|
||||||
meta string
|
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
bodyAllowed bool
|
bodyAllowed bool
|
||||||
}
|
}
|
||||||
@ -188,23 +199,18 @@ func newResponseWriter(w io.Writer) *responseWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Header(status int, meta string) {
|
func (w *responseWriter) MediaType(mediatype string) {
|
||||||
w.status = status
|
w.mediatype = mediatype
|
||||||
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) Write(b []byte) (int, error) {
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
||||||
if !w.wroteHeader {
|
if !w.wroteHeader {
|
||||||
w.writeHeader(StatusSuccess)
|
meta := w.mediatype
|
||||||
|
if meta == "" {
|
||||||
|
// Use default media type
|
||||||
|
meta = defaultMediaType
|
||||||
|
}
|
||||||
|
w.WriteHeader(StatusSuccess, meta)
|
||||||
}
|
}
|
||||||
if !w.bodyAllowed {
|
if !w.bodyAllowed {
|
||||||
return 0, ErrBodyNotAllowed
|
return 0, ErrBodyNotAllowed
|
||||||
@ -212,22 +218,12 @@ func (w *responseWriter) Write(b []byte) (int, error) {
|
|||||||
return w.b.Write(b)
|
return w.b.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) writeHeader(defaultStatus int) {
|
func (w *responseWriter) WriteHeader(statusCode int, meta string) {
|
||||||
status := w.status
|
if StatusClass(statusCode) == StatusSuccess {
|
||||||
if status == 0 {
|
|
||||||
status = defaultStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
meta := w.meta
|
|
||||||
if StatusClass(status) == StatusSuccess {
|
|
||||||
w.bodyAllowed = true
|
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.WriteByte(' ')
|
||||||
w.b.WriteString(meta)
|
w.b.WriteString(meta)
|
||||||
w.b.Write(crlf)
|
w.b.Write(crlf)
|
||||||
@ -236,7 +232,7 @@ func (w *responseWriter) writeHeader(defaultStatus int) {
|
|||||||
|
|
||||||
func (w *responseWriter) Flush() error {
|
func (w *responseWriter) Flush() error {
|
||||||
if !w.wroteHeader {
|
if !w.wroteHeader {
|
||||||
w.writeHeader(StatusTemporaryFailure)
|
w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
|
||||||
}
|
}
|
||||||
// Write errors from writeHeader will be returned here.
|
// Write errors from writeHeader will be returned here.
|
||||||
return w.b.Flush()
|
return w.b.Flush()
|
||||||
|
@ -380,7 +380,7 @@ func (srv *Server) respond(conn net.Conn) {
|
|||||||
|
|
||||||
req, err := ReadRequest(conn)
|
req, err := ReadRequest(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Status(StatusBadRequest)
|
w.WriteHeader(StatusBadRequest, "Bad request")
|
||||||
w.Flush()
|
w.Flush()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -396,7 +396,7 @@ func (srv *Server) respond(conn net.Conn) {
|
|||||||
|
|
||||||
h := srv.handler(req)
|
h := srv.handler(req)
|
||||||
if h == nil {
|
if h == nil {
|
||||||
w.Status(StatusNotFound)
|
w.WriteHeader(StatusNotFound, "Not found")
|
||||||
w.Flush()
|
w.Flush()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user