Add (*ResponseWriter).WriteStatus function
This commit is contained in:
parent
8ab4064841
commit
79165833de
2
fs.go
2
fs.go
@ -28,7 +28,7 @@ func (fsh fsHandler) Respond(w *ResponseWriter, r *Request) {
|
||||
path := path.Clean(r.URL.Path)
|
||||
f, err := fsh.Open(path)
|
||||
if err != nil {
|
||||
NotFound(w, r)
|
||||
w.WriteStatus(StatusNotFound)
|
||||
return
|
||||
}
|
||||
// Detect mimetype
|
||||
|
93
gemini.go
93
gemini.go
@ -9,35 +9,80 @@ import (
|
||||
)
|
||||
|
||||
// Status codes.
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusInput = 10
|
||||
StatusSensitiveInput = 11
|
||||
StatusSuccess = 20
|
||||
StatusRedirect = 30
|
||||
StatusRedirectPermanent = 31
|
||||
StatusTemporaryFailure = 40
|
||||
StatusServerUnavailable = 41
|
||||
StatusCGIError = 42
|
||||
StatusProxyError = 43
|
||||
StatusSlowDown = 44
|
||||
StatusPermanentFailure = 50
|
||||
StatusNotFound = 51
|
||||
StatusGone = 52
|
||||
StatusProxyRequestRefused = 53
|
||||
StatusBadRequest = 59
|
||||
StatusCertificateRequired = 60
|
||||
StatusCertificateNotAuthorized = 61
|
||||
StatusCertificateNotValid = 62
|
||||
StatusInput Status = 10
|
||||
StatusSensitiveInput Status = 11
|
||||
StatusSuccess Status = 20
|
||||
StatusRedirect Status = 30
|
||||
StatusRedirectPermanent Status = 31
|
||||
StatusTemporaryFailure Status = 40
|
||||
StatusServerUnavailable Status = 41
|
||||
StatusCGIError Status = 42
|
||||
StatusProxyError Status = 43
|
||||
StatusSlowDown Status = 44
|
||||
StatusPermanentFailure Status = 50
|
||||
StatusNotFound Status = 51
|
||||
StatusGone Status = 52
|
||||
StatusProxyRequestRefused Status = 53
|
||||
StatusBadRequest Status = 59
|
||||
StatusCertificateRequired Status = 60
|
||||
StatusCertificateNotAuthorized Status = 61
|
||||
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.
|
||||
type StatusClass int
|
||||
|
||||
const (
|
||||
StatusClassInput = 1
|
||||
StatusClassSuccess = 2
|
||||
StatusClassRedirect = 3
|
||||
StatusClassTemporaryFailure = 4
|
||||
StatusClassPermanentFailure = 5
|
||||
StatusClassCertificateRequired = 6
|
||||
StatusClassInput StatusClass = 1
|
||||
StatusClassSuccess StatusClass = 2
|
||||
StatusClassRedirect StatusClass = 3
|
||||
StatusClassTemporaryFailure StatusClass = 4
|
||||
StatusClassPermanentFailure StatusClass = 5
|
||||
StatusClassCertificateRequired StatusClass = 6
|
||||
)
|
||||
|
||||
// Errors.
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
// Response is a Gemini response.
|
||||
type Response struct {
|
||||
// Status represents the response status.
|
||||
Status int
|
||||
Status Status
|
||||
|
||||
// Meta contains more information related to the response status.
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
resp.Status = status
|
||||
resp.Status = Status(status)
|
||||
|
||||
// Disregard invalid status codes
|
||||
const minStatus, maxStatus = 1, 6
|
||||
statusClass := status / 10
|
||||
statusClass := resp.Status.Class()
|
||||
if statusClass < minStatus || statusClass > maxStatus {
|
||||
return ErrInvalidResponse
|
||||
}
|
||||
@ -74,7 +74,7 @@ func (resp *Response) read(r *bufio.Reader) error {
|
||||
}
|
||||
|
||||
// Read response body
|
||||
if status/10 == StatusClassSuccess {
|
||||
if resp.Status.Class() == StatusClassSuccess {
|
||||
var err error
|
||||
resp.Body, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
|
54
server.go
54
server.go
@ -168,7 +168,12 @@ func (s *Server) respond(conn net.Conn) {
|
||||
RemoteAddr: conn.RemoteAddr(),
|
||||
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()
|
||||
conn.Close()
|
||||
@ -184,7 +189,7 @@ func (s *Server) responder(r *Request) Responder {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return ResponderFunc(NotFound)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 failure responses, Meta should contain a short description of the failure.
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
w.b.WriteString(strconv.Itoa(status))
|
||||
w.b.WriteString(strconv.Itoa(int(status)))
|
||||
w.b.WriteByte(' ')
|
||||
w.b.WriteString(meta)
|
||||
w.b.Write(crlf)
|
||||
|
||||
// Only allow body to be written on successful status codes.
|
||||
if status/10 == StatusClassSuccess {
|
||||
if status.Class() == StatusClassSuccess {
|
||||
w.bodyAllowed = 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.
|
||||
// The provided mimetype will only be used if Write is called without calling
|
||||
// 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.
|
||||
func Redirect(w *ResponseWriter, r *Request, url string) {
|
||||
func Redirect(w *ResponseWriter, url string) {
|
||||
w.WriteHeader(StatusRedirect, 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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// it returns nil and responds with StatusCertificateRequired.
|
||||
func Certificate(w *ResponseWriter, r *Request) (*x509.Certificate, bool) {
|
||||
if len(r.TLS.PeerCertificates) == 0 {
|
||||
CertificateRequired(w, r)
|
||||
w.WriteStatus(StatusCertificateRequired)
|
||||
return nil, false
|
||||
}
|
||||
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,
|
||||
// redirect for /tree/.
|
||||
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
|
||||
Redirect(w, r, u.String())
|
||||
Redirect(w, u.String())
|
||||
return
|
||||
}
|
||||
|
||||
if path != r.URL.Path {
|
||||
u := *r.URL
|
||||
u.Path = path
|
||||
Redirect(w, r, u.String())
|
||||
Redirect(w, u.String())
|
||||
return
|
||||
}
|
||||
|
||||
@ -474,7 +462,7 @@ func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) {
|
||||
|
||||
resp := mux.match(path)
|
||||
if resp == nil {
|
||||
NotFound(w, r)
|
||||
w.WriteStatus(StatusNotFound)
|
||||
return
|
||||
}
|
||||
resp.Respond(w, r)
|
||||
|
Loading…
Reference in New Issue
Block a user