Allow data store to set the status code for errors

Closes https://github.com/tus/tusd/issues/77
This commit is contained in:
Marius 2017-01-26 23:15:05 +01:00
parent 675f8fcf04
commit f50f03fe6f
2 changed files with 49 additions and 46 deletions

View File

@ -31,11 +31,14 @@ func (m Metrics) incRequestsTotal(method string) {
// incErrorsTotal increases the counter for this error atomically by one. // incErrorsTotal increases the counter for this error atomically by one.
func (m Metrics) incErrorsTotal(err error) { func (m Metrics) incErrorsTotal(err error) {
msg := err.Error() msg := err.Error()
if _, ok := ErrStatusCodes[err]; !ok {
msg = "system error"
}
atomic.AddUint64(m.ErrorsTotal[msg], 1) if addr, ok := m.ErrorsTotal[msg]; ok {
atomic.AddUint64(addr, 1)
} else {
addr := new(uint64)
*addr = 1
m.ErrorsTotal[msg] = addr
}
} }
// incBytesReceived increases the number of received bytes atomically be the // incBytesReceived increases the number of received bytes atomically be the
@ -78,13 +81,6 @@ func newMetrics() Metrics {
} }
func newErrorsTotalMap() map[string]*uint64 { func newErrorsTotalMap() map[string]*uint64 {
m := make(map[string]*uint64, len(ErrStatusCodes)+1) m := make(map[string]*uint64, 20)
for err := range ErrStatusCodes {
m[err.Error()] = new(uint64)
}
m["system error"] = new(uint64)
return m return m
} }

View File

@ -20,39 +20,46 @@ var (
reForwardedProto = regexp.MustCompile(`proto=(https?)`) reForwardedProto = regexp.MustCompile(`proto=(https?)`)
) )
var ( // HTTPError represents an error with an additional status code attached
ErrUnsupportedVersion = errors.New("unsupported version") // which may be used when this error is sent in a HTTP response.
ErrMaxSizeExceeded = errors.New("maximum size exceeded") // See the net/http package for standardized status codes.
ErrInvalidContentType = errors.New("missing or invalid Content-Type header") type HTTPError interface {
ErrInvalidUploadLength = errors.New("missing or invalid Upload-Length header") error
ErrInvalidOffset = errors.New("missing or invalid Upload-Offset header") StatusCode() int
ErrNotFound = errors.New("upload not found")
ErrFileLocked = errors.New("file currently locked")
ErrMismatchOffset = errors.New("mismatched offset")
ErrSizeExceeded = errors.New("resource's size exceeded")
ErrNotImplemented = errors.New("feature not implemented")
ErrUploadNotFinished = errors.New("one of the partial uploads is not finished")
ErrInvalidConcat = errors.New("invalid Upload-Concat header")
ErrModifyFinal = errors.New("modifying a final upload is not allowed")
)
// HTTP status codes sent in the response when the specific error is returned.
var ErrStatusCodes = map[error]int{
ErrUnsupportedVersion: http.StatusPreconditionFailed,
ErrMaxSizeExceeded: http.StatusRequestEntityTooLarge,
ErrInvalidContentType: http.StatusBadRequest,
ErrInvalidUploadLength: http.StatusBadRequest,
ErrInvalidOffset: http.StatusBadRequest,
ErrNotFound: http.StatusNotFound,
ErrFileLocked: 423, // Locked (WebDAV) (RFC 4918)
ErrMismatchOffset: http.StatusConflict,
ErrSizeExceeded: http.StatusRequestEntityTooLarge,
ErrNotImplemented: http.StatusNotImplemented,
ErrUploadNotFinished: http.StatusBadRequest,
ErrInvalidConcat: http.StatusBadRequest,
ErrModifyFinal: http.StatusForbidden,
} }
type httpError struct {
error
statusCode int
}
func (err httpError) StatusCode() int {
return err.statusCode
}
// NewHTTPError adds the given status code to the provided error and returns
// the new error instance. The status code may be used in corresponding HTTP
// responses. See the net/http package for standardized status codes.
func NewHTTPError(err error, statusCode int) HTTPError {
return httpError{err, statusCode}
}
var (
ErrUnsupportedVersion = NewHTTPError(errors.New("unsupported version"), http.StatusPreconditionFailed)
ErrMaxSizeExceeded = NewHTTPError(errors.New("maximum size exceeded"), http.StatusRequestEntityTooLarge)
ErrInvalidContentType = NewHTTPError(errors.New("missing or invalid Content-Type header"), http.StatusBadRequest)
ErrInvalidUploadLength = NewHTTPError(errors.New("missing or invalid Upload-Length header"), http.StatusBadRequest)
ErrInvalidOffset = NewHTTPError(errors.New("missing or invalid Upload-Offset header"), http.StatusBadRequest)
ErrNotFound = NewHTTPError(errors.New("upload not found"), http.StatusNotFound)
ErrFileLocked = NewHTTPError(errors.New("file currently locked"), 423) // Locked (WebDAV) (RFC 4918)
ErrMismatchOffset = NewHTTPError(errors.New("mismatched offset"), http.StatusConflict)
ErrSizeExceeded = NewHTTPError(errors.New("resource's size exceeded"), http.StatusRequestEntityTooLarge)
ErrNotImplemented = NewHTTPError(errors.New("feature not implemented"), http.StatusNotImplemented)
ErrUploadNotFinished = NewHTTPError(errors.New("one of the partial uploads is not finished"), http.StatusBadRequest)
ErrInvalidConcat = NewHTTPError(errors.New("invalid Upload-Concat header"), http.StatusBadRequest)
ErrModifyFinal = NewHTTPError(errors.New("modifying a final upload is not allowed"), http.StatusForbidden)
)
// UnroutedHandler exposes methods to handle requests as part of the tus protocol, // UnroutedHandler exposes methods to handle requests as part of the tus protocol,
// such as PostFile, HeadFile, PatchFile and DelFile. In addition the GetFile method // such as PostFile, HeadFile, PatchFile and DelFile. In addition the GetFile method
// is provided which is, however, not part of the specification. // is provided which is, however, not part of the specification.
@ -593,9 +600,9 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request
err = ErrNotFound err = ErrNotFound
} }
status, ok := ErrStatusCodes[err] status := 500
if !ok { if statusErr, ok := err.(HTTPError); ok {
status = 500 status = statusErr.StatusCode()
} }
reason := err.Error() + "\n" reason := err.Error() + "\n"