Exemple #1
0
// 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)
		}
	}
}
Exemple #2
0
// 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)
		}
	}
}
Exemple #4
0
// 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)
		}
	}
}
Exemple #5
0
	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)
Exemple #7
0
		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() {