From d59348b54625c5a91fd2ee23c01109acf9baa116 Mon Sep 17 00:00:00 2001 From: Richard Kettelerij Date: Wed, 3 Apr 2024 13:51:57 +0200 Subject: [PATCH] feat(errors): Also handle errors as RFC 7807 problems in reverse proxy --- engine/engine.go | 13 ++++++++++--- engine/engine_test.go | 10 +++++----- engine/problems.go | 12 ++++++++---- engine/templates/openapi/problems.go.json | 10 ++++++++++ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 79dc849c..de75f5c7 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -315,6 +315,11 @@ func (e *Engine) ReverseProxyAndValidate(w http.ResponseWriter, r *http.Request, r.Out.Header.Set(HeaderBaseURL, e.Config.BaseURL.String()) } + errorHandler := func(w http.ResponseWriter, _ *http.Request, err error) { + log.Printf("failed to proxy request: %v", err) + RenderProblem(ProblemBadGateway, w) + } + modifyResponse := func(proxyRes *http.Response) error { if prefer204 { // OGC spec: If the tile has no content due to lack of data in the area, but is within the data @@ -334,7 +339,6 @@ func (e *Engine) ReverseProxyAndValidate(w http.ResponseWriter, r *http.Request, if proxyRes.Header.Get(HeaderContentEncoding) == FormatGzip { reader, err = gzip.NewReader(proxyRes.Body) if err != nil { - log.Printf("%v", err.Error()) return err } } else { @@ -343,7 +347,6 @@ func (e *Engine) ReverseProxyAndValidate(w http.ResponseWriter, r *http.Request, defer reader.Close() res, err := io.ReadAll(reader) if err != nil { - log.Printf("%v", err.Error()) return err } e.ServeResponse(w, r, false, true, contentType, res) @@ -351,7 +354,11 @@ func (e *Engine) ReverseProxyAndValidate(w http.ResponseWriter, r *http.Request, return nil } - reverseProxy := &httputil.ReverseProxy{Rewrite: rewrite, ModifyResponse: modifyResponse} + reverseProxy := &httputil.ReverseProxy{ + Rewrite: rewrite, + ModifyResponse: modifyResponse, + ErrorHandler: errorHandler, + } reverseProxy.ServeHTTP(w, r) } diff --git a/engine/engine_test.go b/engine/engine_test.go index 3f271296..9cd7f633 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -63,7 +63,7 @@ func TestEngine_ReverseProxy(t *testing.T) { defer mockTargetServer.Close() engine, targetURL := makeEngine(mockTargetServer) - rec, req := makeAPICall(t, mockTargetServer) + rec, req := makeAPICall(t, mockTargetServer.URL) // when engine.ReverseProxy(rec, req, targetURL, false, "") @@ -85,7 +85,7 @@ func TestEngine_ReverseProxyAndValidate(t *testing.T) { defer mockTargetServer.Close() engine, targetURL := makeEngine(mockTargetServer) - rec, req := makeAPICall(t, mockTargetServer) + rec, req := makeAPICall(t, mockTargetServer.URL) // when engine.ReverseProxyAndValidate(rec, req, targetURL, false, MediaTypeJSON, true) @@ -103,7 +103,7 @@ func TestEngine_ReverseProxy_Status204(t *testing.T) { defer mockTargetServer.Close() engine, targetURL := makeEngine(mockTargetServer) - rec, req := makeAPICall(t, mockTargetServer) + rec, req := makeAPICall(t, mockTargetServer.URL) // when engine.ReverseProxy(rec, req, targetURL, true, "audio/wav") @@ -126,9 +126,9 @@ func makeEngine(mockTargetServer *httptest.Server) (*Engine, *url.URL) { return engine, targetURL } -func makeAPICall(t *testing.T, mockTargetServer *httptest.Server) (*httptest.ResponseRecorder, *http.Request) { +func makeAPICall(t *testing.T, mockTargetServer string) (*httptest.ResponseRecorder, *http.Request) { rec := httptest.NewRecorder() - req, err := http.NewRequest(http.MethodGet, mockTargetServer.URL+"/some/path", nil) + req, err := http.NewRequest(http.MethodGet, mockTargetServer+"/some/path", nil) if err != nil { t.Fatal(err) } diff --git a/engine/problems.go b/engine/problems.go index 0dfb8063..3b2fa57d 100644 --- a/engine/problems.go +++ b/engine/problems.go @@ -9,16 +9,20 @@ import ( ) const ( - timestampKey = "timestamp" - messageInternalServer = "An unexpected error has occurred, try again or contact support if the problem persists" + timestampKey = "timestamp" + defaultMessageServerErr = "An unexpected error has occurred, try again or contact support if the problem persists" + defaultMessageBadGateway = "Failed to proxy request, try again or contact support if the problem persists" ) +var Now = time.Now // allow mocking + +// The following problems should be added to openapi/problems.go.json var ( - Now = time.Now // allow mocking - ProblemServerError = problem.Of(http.StatusInternalServerError).Append(problem.Detail(messageInternalServer)) ProblemBadRequest = problem.Of(http.StatusBadRequest) ProblemNotFound = problem.Of(http.StatusNotFound) ProblemNotAcceptable = problem.Of(http.StatusNotAcceptable) + ProblemServerError = problem.Of(http.StatusInternalServerError).Append(problem.Detail(defaultMessageServerErr)) + ProblemBadGateway = problem.Of(http.StatusBadGateway).Append(problem.Detail(defaultMessageBadGateway)) ) func RenderProblem(p *problem.Problem, w http.ResponseWriter, details ...string) { diff --git a/engine/templates/openapi/problems.go.json b/engine/templates/openapi/problems.go.json index 0e1de935..bb4793c6 100644 --- a/engine/templates/openapi/problems.go.json +++ b/engine/templates/openapi/problems.go.json @@ -39,4 +39,14 @@ } } } +"502": { + "description": "Bad Gateway: An unexpected error occurred while forwarding/proxying the request to another server.", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/exception" + } + } + } +} {{ end }} \ No newline at end of file