// ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler // understands instances of goa.Error and returns the status and response body embodied in them, // it turns other Go error types into a 500 internal error response. // If verbose is false the details of internal errors is not included in HTTP responses. func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := h(ctx, rw, req) if e == nil { return nil } status := http.StatusInternalServerError var respBody interface{} if err, ok := e.(*goa.Error); ok { status = err.Status respBody = err goa.ContextResponse(ctx).ErrorCode = err.Code rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) } else { respBody = e.Error() rw.Header().Set("Content-Type", "text/plain") } if status >= 500 && status < 600 { reqID := ctx.Value(reqIDKey) if reqID == nil { reqID = shortID() ctx = context.WithValue(ctx, reqIDKey, reqID) } goa.LogError(ctx, "uncaught error", "id", reqID, "msg", respBody) if !verbose { rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) respBody = goa.ErrInternal("internal error [%s]", reqID) } } return service.Send(ctx, status, respBody) } } }
// RequireHeader requires a request header to match a value pattern. If the // header is missing or does not match then the failureStatus is the response // (e.g. http.StatusUnauthorized). If pathPattern is nil then any path is // included. If requiredHeaderValue is nil then any value is accepted so long as // the header is non-empty. func RequireHeader( service *goa.Service, pathPattern *regexp.Regexp, requiredHeaderName string, requiredHeaderValue *regexp.Regexp, failureStatus int) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) (err error) { if pathPattern == nil || pathPattern.MatchString(req.URL.Path) { matched := false headerValue := req.Header.Get(requiredHeaderName) if len(headerValue) > 0 { if requiredHeaderValue == nil { matched = true } else { matched = requiredHeaderValue.MatchString(headerValue) } } if matched { err = h(ctx, rw, req) } else { err = service.Send(ctx, failureStatus, http.StatusText(failureStatus)) } } else { err = h(ctx, rw, req) } return } } }
// ErrorHandler turns a Go error into an JSONAPI HTTP response. It should be placed in the middleware chain // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler // understands instances of goa.ServiceError and returns the status and response body embodied in // them, it turns other Go error types into a 500 internal error response. // If verbose is false the details of internal errors is not included in HTTP responses. // If you use github.com/pkg/errors then wrapping the error will allow a trace to be printed to the logs func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := h(ctx, rw, req) if e == nil { return nil } cause := errs.Cause(e) status := http.StatusInternalServerError var respBody interface{} respBody, status = ErrorToJSONAPIErrors(e) rw.Header().Set("Content-Type", ErrorMediaIdentifier) if err, ok := cause.(goa.ServiceError); ok { status = err.ResponseStatus() //respBody = err goa.ContextResponse(ctx).ErrorCode = err.Token() //rw.Header().Set("Content-Type", ErrorMediaIdentifier) } else { //respBody = e.Error() //rw.Header().Set("Content-Type", "text/plain") } if status >= 500 && status < 600 { //reqID := ctx.Value(reqIDKey) reqID := ctx.Value(1) // TODO remove this hack if reqID == nil { reqID = shortID() //ctx = context.WithValue(ctx, reqIDKey, reqID) ctx = context.WithValue(ctx, 1, reqID) // TODO remove this hack } log.Error(ctx, map[string]interface{}{ "msg": respBody, "err": fmt.Sprintf("%+v", e), }, "uncaught error detected in ErrorHandler") if !verbose { rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) msg := errors.NewInternalError(fmt.Sprintf("%s [%s]", http.StatusText(http.StatusInternalServerError), reqID)) //respBody = goa.ErrInternal(msg) respBody, status = ErrorToJSONAPIErrors(msg) // Preserve the ID of the original error as that's what gets logged, the client // received error ID must match the original // TODO for JSONAPI this won't work I guess. if origErrID := goa.ContextResponse(ctx).ErrorCode; origErrID != "" { respBody.(*goa.ErrorResponse).ID = origErrID } } } return service.Send(ctx, status, respBody) } } }
// ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler // understands instances of goa.ServiceError and returns the status and response body embodied in // them, it turns other Go error types into a 500 internal error response. // If verbose is false the details of internal errors is not included in HTTP responses. // If you use github.com/pkg/errors then wrapping the error will allow a trace to be printed to the logs func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { return func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { e := h(ctx, rw, req) if e == nil { return nil } cause := errors.Cause(e) status := http.StatusInternalServerError var respBody interface{} if err, ok := cause.(goa.ServiceError); ok { status = err.ResponseStatus() respBody = err goa.ContextResponse(ctx).ErrorCode = err.Token() rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) } else { respBody = e.Error() rw.Header().Set("Content-Type", "text/plain") } if status >= 500 && status < 600 { reqID := ctx.Value(reqIDKey) if reqID == nil { reqID = shortID() ctx = context.WithValue(ctx, reqIDKey, reqID) } goa.LogError(ctx, "uncaught error", "err", fmt.Sprintf("%+v", e), "id", reqID, "msg", respBody) if !verbose { rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) msg := fmt.Sprintf("%s [%s]", http.StatusText(http.StatusInternalServerError), reqID) respBody = goa.ErrInternal(msg) // Preserve the ID of the original error as that's what gets logged, the client // received error ID must match the original if origErrID := goa.ContextResponse(ctx).ErrorCode; origErrID != "" { respBody.(*goa.ErrorResponse).ID = origErrID } } } return service.Send(ctx, status, respBody) } } }
BeforeEach(func() { var err error service = newService(nil) req, err = http.NewRequest("POST", "/foo/bar", strings.NewReader(`{"payload":42}`)) Ω(err).ShouldNot(HaveOccurred()) rw = new(testResponseWriter) ctx = newContext(service, rw, req, nil) }) It("matches a header value", func() { req.Header.Set(headerName, "some value") var newCtx context.Context h := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { newCtx = ctx return service.Send(ctx, http.StatusOK, "ok") } t := middleware.RequireHeader( service, regexp.MustCompile("^/foo"), headerName, regexp.MustCompile("^some value$"), http.StatusUnauthorized)(h) err := t(ctx, rw, req) Ω(err).ShouldNot(HaveOccurred()) Ω(goa.ContextResponse(newCtx).Status).Should(Equal(http.StatusOK)) }) It("responds with failure on mismatch", func() { req.Header.Set(headerName, "some other value") h := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
var err error req, err = http.NewRequest("GET", "/goo", nil) Ω(err).ShouldNot(HaveOccurred()) req.Header.Set(middleware.RequestIDHeader, reqID) rw = new(testResponseWriter) params = url.Values{"query": []string{"value"}} service.Encoder.Register(goa.NewJSONEncoder, "*/*") ctx = newContext(service, rw, req, params) }) It("sets the request ID in the context", func() { var newCtx context.Context h := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { newCtx = ctx return service.Send(ctx, 200, "ok") } rg := middleware.RequestID()(h) Ω(rg(ctx, rw, req)).ShouldNot(HaveOccurred()) Ω(middleware.ContextRequestID(newCtx)).Should(Equal(reqID)) }) It("truncates request ID when it exceeds a default limit", func() { var newCtx context.Context h := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { newCtx = ctx return service.Send(ctx, 200, "ok") } tooLong := makeRequestID(2 * middleware.DefaultRequestIDLengthLimit) expected := makeRequestID(middleware.DefaultRequestIDLengthLimit) req.Header.Set(middleware.RequestIDHeader, tooLong)
BeforeEach(func() { service = goa.New("test") ctrl := service.NewController("foo") var err error req, err = http.NewRequest("GET", "/goo", nil) Ω(err).ShouldNot(HaveOccurred()) rw = new(TestResponseWriter) ctx = goa.NewContext(ctrl.Context, rw, req, nil) Ω(goa.ContextResponse(ctx).Status).Should(Equal(0)) }) Context("using a goa handler", func() { BeforeEach(func() { var goaHandler goa.Handler = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { service.Send(ctx, 200, "ok") return nil } input = goaHandler }) It("wraps it in a middleware", func() { Ω(mErr).ShouldNot(HaveOccurred()) h := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { return nil } Ω(middleware(h)(ctx, rw, req)).ShouldNot(HaveOccurred()) Ω(goa.ContextResponse(ctx).Status).Should(Equal(200)) }) }) Context("using a goa handler func", func() { BeforeEach(func() {