forked from TrueCloudLab/distribution
Merge pull request #705 from stevvooe/export-servejson-errors
Export ServeJSON for serving error codes
This commit is contained in:
commit
5c6c88196d
4 changed files with 65 additions and 44 deletions
|
@ -16,6 +16,8 @@ type ErrorCoder interface {
|
||||||
// and the integer format may change and should *never* be exported.
|
// and the integer format may change and should *never* be exported.
|
||||||
type ErrorCode int
|
type ErrorCode int
|
||||||
|
|
||||||
|
var _ error = ErrorCode(0)
|
||||||
|
|
||||||
// ErrorCode just returns itself
|
// ErrorCode just returns itself
|
||||||
func (ec ErrorCode) ErrorCode() ErrorCode {
|
func (ec ErrorCode) ErrorCode() ErrorCode {
|
||||||
return ec
|
return ec
|
||||||
|
@ -93,6 +95,8 @@ type Error struct {
|
||||||
// variable substitution right before showing the message to the user
|
// variable substitution right before showing the message to the user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ error = Error{}
|
||||||
|
|
||||||
// ErrorCode returns the ID/Value of this Error
|
// ErrorCode returns the ID/Value of this Error
|
||||||
func (e Error) ErrorCode() ErrorCode {
|
func (e Error) ErrorCode() ErrorCode {
|
||||||
return e.Code
|
return e.Code
|
||||||
|
@ -163,6 +167,8 @@ func ParseErrorCode(value string) ErrorCode {
|
||||||
// for use within the application.
|
// for use within the application.
|
||||||
type Errors []error
|
type Errors []error
|
||||||
|
|
||||||
|
var _ error = Errors{}
|
||||||
|
|
||||||
func (errs Errors) Error() string {
|
func (errs Errors) Error() string {
|
||||||
switch len(errs) {
|
switch len(errs) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
44
docs/api/errcode/handler.go
Normal file
44
docs/api/errcode/handler.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
|
||||||
|
// and sets the content-type header to 'application/json'. It will handle
|
||||||
|
// ErrorCoder and Errors, and if necessary will create an envelope.
|
||||||
|
func ServeJSON(w http.ResponseWriter, err error) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
var sc int
|
||||||
|
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case Errors:
|
||||||
|
if len(errs) < 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := errs[0].(ErrorCoder); ok {
|
||||||
|
sc = err.ErrorCode().Descriptor().HTTPStatusCode
|
||||||
|
}
|
||||||
|
case ErrorCoder:
|
||||||
|
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
|
||||||
|
err = Errors{err} // create an envelope.
|
||||||
|
default:
|
||||||
|
// We just have an unhandled error type, so just place in an envelope
|
||||||
|
// and move along.
|
||||||
|
err = Errors{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc == 0 {
|
||||||
|
sc = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(sc)
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -379,7 +379,9 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
context.Errors = append(context.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
|
context.Errors = append(context.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
serveJSON(w, context.Errors)
|
if err := errcode.ServeJSON(w, context.Errors); err != nil {
|
||||||
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +395,9 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
ctxu.GetLogger(context).Errorf("error initializing repository middleware: %v", err)
|
ctxu.GetLogger(context).Errorf("error initializing repository middleware: %v", err)
|
||||||
context.Errors = append(context.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
context.Errors = append(context.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
||||||
|
|
||||||
serveJSON(w, context.Errors)
|
if err := errcode.ServeJSON(w, context.Errors); err != nil {
|
||||||
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,7 +409,9 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
if context.Errors.Len() > 0 {
|
if context.Errors.Len() > 0 {
|
||||||
app.logError(context, context.Errors)
|
app.logError(context, context.Errors)
|
||||||
|
|
||||||
serveJSON(w, context.Errors)
|
if err := errcode.ServeJSON(w, context.Errors); err != nil {
|
||||||
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -482,11 +488,9 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
// base route is accessed. This section prevents us from making
|
// base route is accessed. This section prevents us from making
|
||||||
// that mistake elsewhere in the code, allowing any operation to
|
// that mistake elsewhere in the code, allowing any operation to
|
||||||
// proceed.
|
// proceed.
|
||||||
|
if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized); err != nil {
|
||||||
var errs errcode.Errors
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
errs = append(errs, v2.ErrorCodeUnauthorized)
|
}
|
||||||
|
|
||||||
serveJSON(w, errs)
|
|
||||||
return fmt.Errorf("forbidden: no repository name")
|
return fmt.Errorf("forbidden: no repository name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,9 +502,9 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
// Add the appropriate WWW-Auth header
|
// Add the appropriate WWW-Auth header
|
||||||
err.ServeHTTP(w, r)
|
err.ServeHTTP(w, r)
|
||||||
|
|
||||||
var errs errcode.Errors
|
if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
|
||||||
errs = append(errs, v2.ErrorCodeUnauthorized.WithDetail(accessRecords))
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
serveJSON(w, errs)
|
}
|
||||||
default:
|
default:
|
||||||
// This condition is a potential security problem either in
|
// This condition is a potential security problem either in
|
||||||
// the configuration or whatever is backing the access
|
// the configuration or whatever is backing the access
|
||||||
|
|
|
@ -1,43 +1,10 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// serveJSON marshals v and sets the content-type header to
|
|
||||||
// 'application/json'. If a different status code is required, call
|
|
||||||
// ResponseWriter.WriteHeader before this function.
|
|
||||||
func serveJSON(w http.ResponseWriter, v interface{}) error {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
sc := http.StatusInternalServerError
|
|
||||||
|
|
||||||
if errs, ok := v.(errcode.Errors); ok && len(errs) > 0 {
|
|
||||||
if err, ok := errs[0].(errcode.ErrorCoder); ok {
|
|
||||||
if sc2 := err.ErrorCode().Descriptor().HTTPStatusCode; sc2 != 0 {
|
|
||||||
sc = sc2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err, ok := v.(errcode.ErrorCoder); ok {
|
|
||||||
if sc2 := err.ErrorCode().Descriptor().HTTPStatusCode; sc2 != 0 {
|
|
||||||
sc = sc2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(sc)
|
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
|
|
||||||
if err := enc.Encode(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeResources closes all the provided resources after running the target
|
// closeResources closes all the provided resources after running the target
|
||||||
// handler.
|
// handler.
|
||||||
func closeResources(handler http.Handler, closers ...io.Closer) http.Handler {
|
func closeResources(handler http.Handler, closers ...io.Closer) http.Handler {
|
||||||
|
|
Loading…
Reference in a new issue