diff options
Diffstat (limited to 'echo/error_handler.go')
-rw-r--r-- | echo/error_handler.go | 120 |
1 files changed, 90 insertions, 30 deletions
diff --git a/echo/error_handler.go b/echo/error_handler.go index bfbcfb6..bb4102b 100644 --- a/echo/error_handler.go +++ b/echo/error_handler.go | |||
@@ -7,19 +7,94 @@ import ( | |||
7 | "io/fs" | 7 | "io/fs" |
8 | "net/http" | 8 | "net/http" |
9 | 9 | ||
10 | "github.com/elnormous/contenttype" | ||
10 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" |
11 | ) | 12 | ) |
12 | 13 | ||
13 | // Copied from echo and tweaked to make our errors nicer | 14 | // TODO: This should allow plugging in other content types |
15 | // TODO: This should also be refactored into something prettier | ||
14 | func ErrorHandler(templates fs.FS, funcs template.FuncMap) func(error, echo.Context) { | 16 | func ErrorHandler(templates fs.FS, funcs template.FuncMap) func(error, echo.Context) { |
17 | handleHtml := func(c echo.Context, he *echo.HTTPError) error { | ||
18 | t, err := template.New("").Funcs(funcs).ParseFS( | ||
19 | templates, | ||
20 | "404.tpl", | ||
21 | "40x.tpl", | ||
22 | "50x.tpl", | ||
23 | "header.tpl", | ||
24 | "footer.tpl", | ||
25 | ) | ||
26 | if err != nil { | ||
27 | return err | ||
28 | } | ||
29 | |||
30 | path := "50x.tpl" | ||
31 | if he.Code == 404 { | ||
32 | path = "404.tpl" | ||
33 | } else if he.Code >= 400 && he.Code <= 499 { | ||
34 | path = "40x.tpl" | ||
35 | } | ||
36 | |||
37 | buf := bytes.Buffer{} | ||
38 | if err = t.ExecuteTemplate(&buf, path, nil); err != nil { | ||
39 | err = c.String(he.Code, fmt.Sprintf("%s", he.Message)) | ||
40 | } | ||
41 | |||
42 | return c.HTMLBlob(he.Code, buf.Bytes()) | ||
43 | } | ||
44 | |||
45 | handlePlain := func(c echo.Context, he *echo.HTTPError) error { | ||
46 | return c.String(he.Code, fmt.Sprintf("%s", he.Message)) | ||
47 | } | ||
48 | |||
49 | handleJson := func(c echo.Context, he *echo.HTTPError) error { | ||
50 | code := he.Code | ||
51 | message := he.Message | ||
52 | if m, ok := he.Message.(string); ok { | ||
53 | if c.Echo().Debug { | ||
54 | message = echo.Map{"message": m, "error": he.Error()} | ||
55 | } else { | ||
56 | message = echo.Map{"message": m} | ||
57 | } | ||
58 | } | ||
59 | |||
60 | return c.JSON(code, message) | ||
61 | } | ||
62 | |||
63 | errorWhileErroring := func(c echo.Context, err interface{}) { | ||
64 | c.Echo().Logger.Error(err) | ||
65 | if c.Echo().Debug { | ||
66 | c.JSON(http.StatusInternalServerError, &echo.HTTPError{ | ||
67 | Code: http.StatusInternalServerError, | ||
68 | Message: fmt.Sprintf("Error while processing error page. %w", err), | ||
69 | }) | ||
70 | } else { | ||
71 | c.JSON(http.StatusInternalServerError, &echo.HTTPError{ | ||
72 | Code: http.StatusInternalServerError, | ||
73 | Message: http.StatusText(http.StatusInternalServerError), | ||
74 | }) | ||
75 | } | ||
76 | } | ||
77 | |||
78 | handlers := map[string]func(echo.Context, *echo.HTTPError) error{ | ||
79 | "text/plain": handlePlain, | ||
80 | "text/html": handleHtml, | ||
81 | "text/json": handleJson, | ||
82 | "application/json": handleJson, | ||
83 | } | ||
84 | |||
85 | // This is hand maintained here because order is important for negotiation, | ||
86 | // especially in the case of */* | ||
87 | hIndex := []contenttype.MediaType{ | ||
88 | contenttype.NewMediaType("text/json"), | ||
89 | contenttype.NewMediaType("application/json"), | ||
90 | contenttype.NewMediaType("text/plain"), | ||
91 | contenttype.NewMediaType("text/html"), | ||
92 | } | ||
93 | |||
15 | return func(err error, c echo.Context) { | 94 | return func(err error, c echo.Context) { |
16 | defer func() { | 95 | defer func() { |
17 | if r := recover(); r != nil { | 96 | if r := recover(); r != nil { |
18 | if c.Echo().Debug { | 97 | errorWhileErroring(c, r) |
19 | c.String(http.StatusInternalServerError, fmt.Sprintf("Error while processing error page. %s", r)) | ||
20 | } else { | ||
21 | c.String(http.StatusInternalServerError, "Error while processing error page.") | ||
22 | } | ||
23 | } | 98 | } |
24 | }() | 99 | }() |
25 | 100 | ||
@@ -37,41 +112,26 @@ func ErrorHandler(templates fs.FS, funcs template.FuncMap) func(error, echo.Cont | |||
37 | } | 112 | } |
38 | } | 113 | } |
39 | 114 | ||
40 | t, err := template.New("").Funcs(funcs).ParseFS( | 115 | ct, _, err := contenttype.GetAcceptableMediaType(c.Request(), hIndex) |
41 | templates, | ||
42 | "404.tpl", | ||
43 | "40x.tpl", | ||
44 | "50x.tpl", | ||
45 | "header.tpl", | ||
46 | "footer.tpl", | ||
47 | ) | ||
48 | if err != nil { | 116 | if err != nil { |
49 | he = &echo.HTTPError{ | 117 | c.Echo().Logger.Error("Error negotiating content type in error handler, using json") |
50 | Code: http.StatusInternalServerError, | 118 | ct = contenttype.NewMediaType("text/json") |
51 | Message: http.StatusText(http.StatusInternalServerError), | ||
52 | } | ||
53 | } | 119 | } |
54 | 120 | ||
55 | path := "50x.tpl" | 121 | handle, ok := handlers[ct.String()] |
56 | if he.Code == 404 { | 122 | if !ok { |
57 | path = "404.tpl" | 123 | c.Echo().Logger.Errorf("Error handler content type %s is unknown", ct.String()) |
58 | } else if he.Code >= 400 && he.Code <= 499 { | 124 | handle = handleJson |
59 | path = "40x.tpl" | ||
60 | } | 125 | } |
61 | 126 | ||
62 | // Send response | ||
63 | if !c.Response().Committed { | 127 | if !c.Response().Committed { |
64 | if c.Request().Method == http.MethodHead { // Issue #608 | 128 | if c.Request().Method == http.MethodHead { // Issue #608 |
65 | err = c.NoContent(he.Code) | 129 | err = c.NoContent(he.Code) |
66 | } else { | 130 | } else { |
67 | buf := bytes.Buffer{} | 131 | err = handle(c, he) |
68 | if err = t.ExecuteTemplate(&buf, path, nil); err != nil { | ||
69 | err = c.String(he.Code, fmt.Sprintf("%s", he.Message)) | ||
70 | } | ||
71 | c.HTMLBlob(he.Code, buf.Bytes()) | ||
72 | } | 132 | } |
73 | if err != nil { | 133 | if err != nil { |
74 | c.Echo().Logger.Error(err) | 134 | errorWhileErroring(c, err) |
75 | } | 135 | } |
76 | } | 136 | } |
77 | } | 137 | } |