diff --git a/.gitignore b/.gitignore index 58dae17..a07071a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea /event-gateway vendor *.swp diff --git a/functions/httpapi.go b/functions/httpapi.go index 05bd2c2..020b75b 100644 --- a/functions/httpapi.go +++ b/functions/httpapi.go @@ -34,7 +34,7 @@ func (h HTTPAPI) getFunction(w http.ResponseWriter, r *http.Request, params http w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) } else { encoder.Encode(fn) } @@ -47,7 +47,7 @@ func (h HTTPAPI) getFunctions(w http.ResponseWriter, r *http.Request, params htt fns, err := h.Functions.GetAllFunctions() if err != nil { w.WriteHeader(http.StatusInternalServerError) - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) } else { encoder.Encode(&functions{fns}) } @@ -80,7 +80,7 @@ func (h HTTPAPI) registerFunction(w http.ResponseWriter, r *http.Request, params w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -111,7 +111,7 @@ func (h HTTPAPI) updateFunction(w http.ResponseWriter, r *http.Request, params h w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -130,7 +130,7 @@ func (h HTTPAPI) deleteFunction(w http.ResponseWriter, r *http.Request, params h w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) } else { w.WriteHeader(http.StatusNoContent) } diff --git a/internal/httpapi/error.go b/internal/httpapi/error.go index 7b56231..46fe956 100644 --- a/internal/httpapi/error.go +++ b/internal/httpapi/error.go @@ -2,9 +2,13 @@ package httpapi import "fmt" +type Response struct { + Errors []Error `json:"errors"` +} + // Error represents generic HTTP error returned by Configuration API. type Error struct { - Error string `json:"error"` + Message string `json:"message"` } // ErrMalformedJSON occurring when it's impossible to decode JSON payload. diff --git a/router/router.go b/router/router.go index aaa2f65..8378362 100644 --- a/router/router.go +++ b/router/router.go @@ -3,7 +3,6 @@ package router import ( "encoding/json" "errors" - "fmt" "io/ioutil" "net/http" "regexp" @@ -17,6 +16,7 @@ import ( eventpkg "github.com/serverless/event-gateway/event" "github.com/serverless/event-gateway/functions" "github.com/serverless/event-gateway/plugin" + "github.com/serverless/event-gateway/internal/httpapi" ) // Router calls a target function when an endpoint is hit, and handles pubsub message delivery. @@ -47,9 +47,12 @@ func New(workersNumber uint, backlogLength uint, targetCache Targeter, plugins * } func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { + encoder := json.NewEncoder(w) // if we're draining requests, spit back a 503 if router.isDraining() { - http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) + w.WriteHeader(http.StatusServiceUnavailable) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: http.StatusText(http.StatusServiceUnavailable) }}}) return } @@ -60,7 +63,9 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { event, _, err := router.eventFromRequest(r) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -69,13 +74,16 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { cors.AllowAll().ServeHTTP(w, r, func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, "custom event can be emitted only with POST method") + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: "custom event can be emitted only with POST method" }}}) return } event, path, err := router.eventFromRequest(r) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -242,6 +250,7 @@ func (router *Router) eventFromRequest(r *http.Request) (*eventpkg.Event, string return event, path, nil } func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWriter, r *http.Request) { + encoder := json.NewEncoder(w) reqMethod := r.Method // check if CORS pre-flight request @@ -253,7 +262,9 @@ func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWrit ) if backingFunction == nil { router.log.Debug("Function not found for HTTP event.", zap.Object("event", event)) - http.Error(w, "resource not found", http.StatusNotFound) + w.WriteHeader(http.StatusNotFound) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: "resource not found" }}}) return } @@ -263,7 +274,9 @@ func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWrit event.Data = httpdata resp, err := router.callFunction(*backingFunction, *event) if err != nil { - http.Error(w, "function call failed", http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: "function call failed" }}}) return } @@ -271,7 +284,9 @@ func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWrit err = json.Unmarshal(resp, httpResponse) if err != nil { router.log.Info("HTTP response object malformed.", zap.String("response", string(resp))) - http.Error(w, "HTTP response object malformed", http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: "HTTP response object malformed" }}}) return } @@ -283,7 +298,9 @@ func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWrit _, err = w.Write(resp) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -306,23 +323,30 @@ func (router *Router) handleHTTPEvent(event *eventpkg.Event, w http.ResponseWrit } func (router *Router) handleInvokeEvent(path string, event *eventpkg.Event, w http.ResponseWriter, r *http.Request) { + encoder := json.NewEncoder(w) routerEventsSyncReceived.Inc() functionID := functions.FunctionID(r.Header.Get(headerFunctionID)) if !router.targetCache.InvokableFunction(path, functionID) { - http.Error(w, "function or subscription not found", http.StatusNotFound) + w.WriteHeader(http.StatusNotFound) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: "function or subscription not found" }}}) return } resp, err := router.callFunction(functionID, *event) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } _, err = w.Write(resp) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("content-type", "application/json") + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } diff --git a/subscriptions/httpapi.go b/subscriptions/httpapi.go index e0c3035..2b295ce 100644 --- a/subscriptions/httpapi.go +++ b/subscriptions/httpapi.go @@ -48,7 +48,7 @@ func (h HTTPAPI) createSubscription(w http.ResponseWriter, r *http.Request, para w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) return } @@ -70,7 +70,7 @@ func (h HTTPAPI) deleteSubscription(w http.ResponseWriter, r *http.Request, para } else { w.WriteHeader(http.StatusInternalServerError) } - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) } else { w.WriteHeader(http.StatusNoContent) } @@ -83,7 +83,7 @@ func (h HTTPAPI) getSubscriptions(w http.ResponseWriter, r *http.Request, params subs, err := h.Subscriptions.GetAllSubscriptions() if err != nil { w.WriteHeader(http.StatusInternalServerError) - encoder.Encode(&httpapi.Error{Error: err.Error()}) + encoder.Encode(&httpapi.Response{Errors: []httpapi.Error{{ Message: err.Error() }}}) } else { encoder.Encode(&subscriptions{subs}) }