Add (*ResponseWriter).WriteStatus function

This commit is contained in:
Adnan Maolood 2020-10-27 13:30:35 -04:00
parent 8ab4064841
commit 79165833de
4 changed files with 95 additions and 62 deletions

2
fs.go
View File

@ -28,7 +28,7 @@ func (fsh fsHandler) Respond(w *ResponseWriter, r *Request) {
path := path.Clean(r.URL.Path) path := path.Clean(r.URL.Path)
f, err := fsh.Open(path) f, err := fsh.Open(path)
if err != nil { if err != nil {
NotFound(w, r) w.WriteStatus(StatusNotFound)
return return
} }
// Detect mimetype // Detect mimetype

View File

@ -9,35 +9,80 @@ import (
) )
// Status codes. // Status codes.
type Status int
const ( const (
StatusInput = 10 StatusInput Status = 10
StatusSensitiveInput = 11 StatusSensitiveInput Status = 11
StatusSuccess = 20 StatusSuccess Status = 20
StatusRedirect = 30 StatusRedirect Status = 30
StatusRedirectPermanent = 31 StatusRedirectPermanent Status = 31
StatusTemporaryFailure = 40 StatusTemporaryFailure Status = 40
StatusServerUnavailable = 41 StatusServerUnavailable Status = 41
StatusCGIError = 42 StatusCGIError Status = 42
StatusProxyError = 43 StatusProxyError Status = 43
StatusSlowDown = 44 StatusSlowDown Status = 44
StatusPermanentFailure = 50 StatusPermanentFailure Status = 50
StatusNotFound = 51 StatusNotFound Status = 51
StatusGone = 52 StatusGone Status = 52
StatusProxyRequestRefused = 53 StatusProxyRequestRefused Status = 53
StatusBadRequest = 59 StatusBadRequest Status = 59
StatusCertificateRequired = 60 StatusCertificateRequired Status = 60
StatusCertificateNotAuthorized = 61 StatusCertificateNotAuthorized Status = 61
StatusCertificateNotValid = 62 StatusCertificateNotValid Status = 62
) )
// Class returns the status class for this status code.
func (s Status) Class() StatusClass {
return StatusClass(s / 10)
}
// StatusMessage returns the status message corresponding to the provided
// status code.
// StatusMessage returns an empty string for input, successs, and redirect
// status codes.
func (s Status) Message() string {
switch s {
case StatusTemporaryFailure:
return "TemporaryFailure"
case StatusServerUnavailable:
return "Server unavailable"
case StatusCGIError:
return "CGI error"
case StatusProxyError:
return "Proxy error"
case StatusSlowDown:
return "Slow down"
case StatusPermanentFailure:
return "PermanentFailure"
case StatusNotFound:
return "Not found"
case StatusGone:
return "Gone"
case StatusProxyRequestRefused:
return "Proxy request refused"
case StatusBadRequest:
return "Bad request"
case StatusCertificateRequired:
return "Certificate required"
case StatusCertificateNotAuthorized:
return "Certificate not authorized"
case StatusCertificateNotValid:
return "Certificate not valid"
}
return ""
}
// Status code categories. // Status code categories.
type StatusClass int
const ( const (
StatusClassInput = 1 StatusClassInput StatusClass = 1
StatusClassSuccess = 2 StatusClassSuccess StatusClass = 2
StatusClassRedirect = 3 StatusClassRedirect StatusClass = 3
StatusClassTemporaryFailure = 4 StatusClassTemporaryFailure StatusClass = 4
StatusClassPermanentFailure = 5 StatusClassPermanentFailure StatusClass = 5
StatusClassCertificateRequired = 6 StatusClassCertificateRequired StatusClass = 6
) )
// Errors. // Errors.

View File

@ -10,7 +10,7 @@ import (
// Response is a Gemini response. // Response is a Gemini response.
type Response struct { type Response struct {
// Status represents the response status. // Status represents the response status.
Status int Status Status
// Meta contains more information related to the response status. // Meta contains more information related to the response status.
// For successful responses, Meta should contain the mimetype of the response. // For successful responses, Meta should contain the mimetype of the response.
@ -37,11 +37,11 @@ func (resp *Response) read(r *bufio.Reader) error {
if err != nil { if err != nil {
return err return err
} }
resp.Status = status resp.Status = Status(status)
// Disregard invalid status codes // Disregard invalid status codes
const minStatus, maxStatus = 1, 6 const minStatus, maxStatus = 1, 6
statusClass := status / 10 statusClass := resp.Status.Class()
if statusClass < minStatus || statusClass > maxStatus { if statusClass < minStatus || statusClass > maxStatus {
return ErrInvalidResponse return ErrInvalidResponse
} }
@ -74,7 +74,7 @@ func (resp *Response) read(r *bufio.Reader) error {
} }
// Read response body // Read response body
if status/10 == StatusClassSuccess { if resp.Status.Class() == StatusClassSuccess {
var err error var err error
resp.Body, err = ioutil.ReadAll(r) resp.Body, err = ioutil.ReadAll(r)
if err != nil { if err != nil {

View File

@ -168,7 +168,12 @@ func (s *Server) respond(conn net.Conn) {
RemoteAddr: conn.RemoteAddr(), RemoteAddr: conn.RemoteAddr(),
TLS: conn.(*tls.Conn).ConnectionState(), TLS: conn.(*tls.Conn).ConnectionState(),
} }
s.responder(req).Respond(w, req) resp := s.responder(req)
if resp != nil {
resp.Respond(w, req)
} else {
w.WriteStatus(StatusNotFound)
}
} }
w.b.Flush() w.b.Flush()
conn.Close() conn.Close()
@ -184,7 +189,7 @@ func (s *Server) responder(r *Request) Responder {
return h return h
} }
} }
return ResponderFunc(NotFound) return nil
} }
// ResponseWriter is used by a Gemini handler to construct a Gemini response. // ResponseWriter is used by a Gemini handler to construct a Gemini response.
@ -208,22 +213,27 @@ func newResponseWriter(conn net.Conn) *ResponseWriter {
// For successful responses, Meta should contain the mimetype of the response. // For successful responses, Meta should contain the mimetype of the response.
// For failure responses, Meta should contain a short description of the failure. // For failure responses, Meta should contain a short description of the failure.
// Meta should not be longer than 1024 bytes. // Meta should not be longer than 1024 bytes.
func (w *ResponseWriter) WriteHeader(status int, meta string) { func (w *ResponseWriter) WriteHeader(status Status, meta string) {
if w.wroteHeader { if w.wroteHeader {
return return
} }
w.b.WriteString(strconv.Itoa(status)) w.b.WriteString(strconv.Itoa(int(status)))
w.b.WriteByte(' ') w.b.WriteByte(' ')
w.b.WriteString(meta) w.b.WriteString(meta)
w.b.Write(crlf) w.b.Write(crlf)
// Only allow body to be written on successful status codes. // Only allow body to be written on successful status codes.
if status/10 == StatusClassSuccess { if status.Class() == StatusClassSuccess {
w.bodyAllowed = true w.bodyAllowed = true
} }
w.wroteHeader = true w.wroteHeader = true
} }
// WriteStatus writes the response header with the given status code.
func (w *ResponseWriter) WriteStatus(status Status) {
w.WriteHeader(status, status.Message())
}
// SetMimetype sets the mimetype that will be written for a successful response. // SetMimetype sets the mimetype that will be written for a successful response.
// The provided mimetype will only be used if Write is called without calling // The provided mimetype will only be used if Write is called without calling
// WriteHeader. // WriteHeader.
@ -280,42 +290,20 @@ func SensitiveInput(w *ResponseWriter, r *Request, prompt string) (string, bool)
} }
// Redirect replies to the request with a redirect to the given URL. // Redirect replies to the request with a redirect to the given URL.
func Redirect(w *ResponseWriter, r *Request, url string) { func Redirect(w *ResponseWriter, url string) {
w.WriteHeader(StatusRedirect, url) w.WriteHeader(StatusRedirect, url)
} }
// PermanentRedirect replies to the request with a permanent redirect to the given URL. // PermanentRedirect replies to the request with a permanent redirect to the given URL.
func PermanentRedirect(w *ResponseWriter, r *Request, url string) { func PermanentRedirect(w *ResponseWriter, url string) {
w.WriteHeader(StatusRedirectPermanent, url) w.WriteHeader(StatusRedirectPermanent, url)
} }
// NotFound replies to the request with the NotFound status code.
func NotFound(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusNotFound, "Not found")
}
// Gone replies to the request with the Gone status code.
func Gone(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusGone, "Gone")
}
// CertificateRequired responds to the request with the CertificateRequired
// status code.
func CertificateRequired(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusCertificateRequired, "Certificate required")
}
// CertificateNotAuthorized responds to the request with
// the CertificateNotAuthorized status code.
func CertificateNotAuthorized(w *ResponseWriter, r *Request) {
w.WriteHeader(StatusCertificateNotAuthorized, "Certificate not authorized")
}
// Certificate returns the request certificate. If one is not provided, // Certificate returns the request certificate. If one is not provided,
// it returns nil and responds with StatusCertificateRequired. // it returns nil and responds with StatusCertificateRequired.
func Certificate(w *ResponseWriter, r *Request) (*x509.Certificate, bool) { func Certificate(w *ResponseWriter, r *Request) (*x509.Certificate, bool) {
if len(r.TLS.PeerCertificates) == 0 { if len(r.TLS.PeerCertificates) == 0 {
CertificateRequired(w, r) w.WriteStatus(StatusCertificateRequired)
return nil, false return nil, false
} }
return r.TLS.PeerCertificates[0], true return r.TLS.PeerCertificates[0], true
@ -458,14 +446,14 @@ func (mux *ServeMux) Respond(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 {
Redirect(w, r, u.String()) Redirect(w, 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
Redirect(w, r, u.String()) Redirect(w, u.String())
return return
} }
@ -474,7 +462,7 @@ func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) {
resp := mux.match(path) resp := mux.match(path)
if resp == nil { if resp == nil {
NotFound(w, r) w.WriteStatus(StatusNotFound)
return return
} }
resp.Respond(w, r) resp.Respond(w, r)