aboutsummaryrefslogtreecommitdiff
path: root/echo/error_handler.go
diff options
context:
space:
mode:
Diffstat (limited to 'echo/error_handler.go')
-rw-r--r--echo/error_handler.go252
1 files changed, 155 insertions, 97 deletions
diff --git a/echo/error_handler.go b/echo/error_handler.go
index 2316ccd..b44155f 100644
--- a/echo/error_handler.go
+++ b/echo/error_handler.go
@@ -4,135 +4,193 @@ import (
4 "bytes" 4 "bytes"
5 "fmt" 5 "fmt"
6 "html/template" 6 "html/template"
7 "io/fs"
8 "net/http" 7 "net/http"
9 8
10 "github.com/elnormous/contenttype" 9 "github.com/elnormous/contenttype"
11 "github.com/labstack/echo/v4" 10 "github.com/labstack/echo/v4"
12) 11)
13 12
14// TODO: This should allow plugging in other content types 13// TODO: This should have some kind of HTML formatting of panics in debug mode
15// TODO: This should also be refactored into something prettier
16func 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 14
30 path := "50x.tpl" 15type ContentErrorHandler interface {
31 if he.Code == 404 { 16 Handle(echo.Context, *echo.HTTPError) error
32 path = "404.tpl" 17}
33 } else if he.Code >= 400 && he.Code <= 499 {
34 path = "40x.tpl"
35 }
36 18
37 buf := bytes.Buffer{} 19type HTMLErrorHandler struct {
38 if err = t.ExecuteTemplate(&buf, path, nil); err != nil { 20 r *TemplateRenderer
39 err = c.String(he.Code, fmt.Sprintf("%s", he.Message)) 21 fallback *template.Template
40 } 22}
41 23
42 return c.HTMLBlob(he.Code, buf.Bytes()) 24func NewHTMLErrorHandler(r *TemplateRenderer) *HTMLErrorHandler {
25 t, err := template.New("").Parse("<html><body><h1>Error</h1><pre>{{ .Message }}</pre></body></html>\n")
26 if err != nil {
27 panic("NewHTMLErrorHandler: error parsing fallback template")
43 } 28 }
29 return &HTMLErrorHandler{r: r, fallback: t}
30}
44 31
45 handlePlain := func(c echo.Context, he *echo.HTTPError) error { 32func (h *HTMLErrorHandler) Handle(c echo.Context, e *echo.HTTPError) error {
46 return c.String(he.Code, fmt.Sprintf("%s", he.Message)) 33 path := "50x.tpl"
34 if e.Code == 404 {
35 path = "404.tpl"
36 } else if e.Code >= 400 && e.Code <= 499 {
37 path = "40x.tpl"
47 } 38 }
48 39
49 handleJson := func(c echo.Context, he *echo.HTTPError) error { 40 buf := bytes.Buffer{}
50 code := he.Code 41 if !h.r.HaveTemplate(path) {
51 message := he.Message 42 c.Logger().Errorf("Error template %s is missing, using fallback", path)
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 43
60 return c.JSON(code, message) 44 if err := h.fallback.Execute(&buf, e); err != nil {
45 c.Logger().Errorf("Error rendering HTML error page: %s", err)
46 return c.String(e.Code, e.Error())
47 }
48 } else {
49 if err := h.r.Render(&buf, path, e, c); err != nil {
50 c.Logger().Errorf("Error rendering HTML error page: %s", err)
51 return c.String(e.Code, e.Error())
52 }
61 } 53 }
62 54
63 errorWhileErroring := func(c echo.Context, err interface{}) { 55 return c.HTMLBlob(e.Code, buf.Bytes())
64 c.Echo().Logger.Error(err) 56}
57
58type PlainTextErrorHandler struct{}
59
60func (h *PlainTextErrorHandler) Handle(c echo.Context, e *echo.HTTPError) error {
61 return c.String(e.Code, fmt.Sprintf("%s", e.Message))
62}
63
64type JSONErrorHandler struct{}
65
66func (h *JSONErrorHandler) Handle(c echo.Context, e *echo.HTTPError) error {
67 code := e.Code
68 message := e.Message
69 if m, ok := e.Message.(string); ok {
65 if c.Echo().Debug { 70 if c.Echo().Debug {
66 c.JSON(http.StatusInternalServerError, &echo.HTTPError{ 71 message = echo.Map{"message": m, "error": e.Error()}
67 Code: http.StatusInternalServerError,
68 Message: fmt.Sprintf("Error while processing error page. %s", err),
69 })
70 } else { 72 } else {
71 c.JSON(http.StatusInternalServerError, &echo.HTTPError{ 73 message = echo.Map{"message": m}
72 Code: http.StatusInternalServerError,
73 Message: http.StatusText(http.StatusInternalServerError),
74 })
75 } 74 }
76 } 75 }
77 76
78 handlers := map[string]func(echo.Context, *echo.HTTPError) error{ 77 return c.JSON(code, message)
79 "text/plain": handlePlain, 78}
80 "text/html": handleHtml, 79
81 "text/json": handleJson, 80type failsafeHandler struct{}
82 "application/json": handleJson, 81
82func (h *failsafeHandler) Handle(c echo.Context, e *echo.HTTPError) error {
83 c.Echo().Logger.Error("Error while processing error page: %s", e)
84 c.JSON(http.StatusInternalServerError, e)
85 return nil
86}
87
88type ErrorHandler struct {
89 types []contenttype.MediaType
90 typeMap map[string]ContentErrorHandler
91 failsafeHandler ContentErrorHandler
92}
93
94func NewErrorHandler() *ErrorHandler {
95 return &ErrorHandler{
96 types: []contenttype.MediaType{},
97 typeMap: map[string]ContentErrorHandler{},
98 failsafeHandler: &failsafeHandler{},
83 } 99 }
100}
101
102func NewDefaultErrorHandler(tr *TemplateRenderer) *ErrorHandler {
103 h := NewErrorHandler()
104
105 // The order of these is important, especially when negotiating */*
106 h.AddHandler(&JSONErrorHandler{}, "text/json", "application/json")
107 h.AddHandler(&PlainTextErrorHandler{}, "text/plain")
108 h.AddHandler(NewHTMLErrorHandler(tr), "text/html")
109
110 return h
111}
112
113func NewNoHTMLErrorHandler() *ErrorHandler {
114 h := NewErrorHandler()
115
116 // The order of these is important, especially when negotiating */*
117 h.AddHandler(&JSONErrorHandler{}, "text/json", "application/json")
118 h.AddHandler(&PlainTextErrorHandler{}, "text/plain")
119
120 return h
121}
84 122
85 // This is hand maintained here because order is important for negotiation, 123func (h *ErrorHandler) AddHandler(eh ContentErrorHandler, contentTypes ...string) {
86 // especially in the case of */* 124 for _, ct := range contentTypes {
87 hIndex := []contenttype.MediaType{ 125 h.types = append(h.types, contenttype.NewMediaType(ct))
88 contenttype.NewMediaType("text/json"), 126 h.typeMap[ct] = eh
89 contenttype.NewMediaType("application/json"),
90 contenttype.NewMediaType("text/plain"),
91 contenttype.NewMediaType("text/html"),
92 } 127 }
128}
93 129
94 return func(err error, c echo.Context) { 130func (h *ErrorHandler) castToHttpError(e error) *echo.HTTPError {
95 defer func() { 131 he, ok := e.(*echo.HTTPError)
96 if r := recover(); r != nil { 132 if ok {
97 errorWhileErroring(c, r) 133 if he.Internal != nil {
98 } 134 if ihe, ok := he.Internal.(*echo.HTTPError); ok {
99 }() 135 return ihe
100
101 he, ok := err.(*echo.HTTPError)
102 if ok {
103 if he.Internal != nil {
104 if herr, ok := he.Internal.(*echo.HTTPError); ok {
105 he = herr
106 }
107 }
108 } else {
109 he = &echo.HTTPError{
110 Code: http.StatusInternalServerError,
111 Message: http.StatusText(http.StatusInternalServerError),
112 } 136 }
113 } 137 }
114 138 return he
115 ct, _, err := contenttype.GetAcceptableMediaType(c.Request(), hIndex) 139 } else {
116 if err != nil { 140 return &echo.HTTPError{
117 c.Echo().Logger.Error("Error negotiating content type in error handler, using json") 141 Code: http.StatusInternalServerError,
118 ct = contenttype.NewMediaType("text/json") 142 Message: http.StatusText(http.StatusInternalServerError),
119 } 143 }
144 }
145}
120 146
121 handle, ok := handlers[ct.String()] 147func (h *ErrorHandler) negotiateContentType(r *http.Request) (ContentErrorHandler, error) {
122 if !ok { 148 var err error
123 c.Echo().Logger.Errorf("Error handler content type %s is unknown", ct.String())
124 handle = handleJson
125 }
126 149
127 if !c.Response().Committed { 150 ct, _, err := contenttype.GetAcceptableMediaType(r, h.types)
128 if c.Request().Method == http.MethodHead { // Issue #608 151 if err != nil {
129 err = c.NoContent(he.Code) 152 err = fmt.Errorf("Error negotiating content type in error handler, falling back to JSON: %w", err)
153 ct = contenttype.NewMediaType("text/json")
154 }
155
156 handle, found := h.typeMap[ct.String()]
157 if !found {
158 err = fmt.Errorf("Unable to find handler for content type: %s", ct)
159 handle = h.failsafeHandler
160 }
161
162 return handle, err
163}
164
165func (h *ErrorHandler) HandleError(err error, c echo.Context) {
166 defer func() {
167 if r := recover(); r != nil {
168 if err, ok := r.(error); !ok {
169 h.failsafeHandler.Handle(c, h.castToHttpError(fmt.Errorf("%s", err)))
130 } else { 170 } else {
131 err = handle(c, he) 171 h.failsafeHandler.Handle(c, h.castToHttpError(err))
132 }
133 if err != nil {
134 errorWhileErroring(c, err)
135 } 172 }
136 } 173 }
174 }()
175
176 logger := c.Echo().Logger
177 he := h.castToHttpError(err)
178
179 handle, cterr := h.negotiateContentType(c.Request())
180 if cterr != nil {
181 logger.Errorf("%s", cterr)
182 }
183
184 if !c.Response().Committed {
185 if c.Request().Method == http.MethodHead { // Issue #608
186 err = c.NoContent(he.Code)
187 } else {
188 err = handle.Handle(c, he)
189 }
190 if err != nil {
191 h.failsafeHandler.Handle(c, h.castToHttpError(err))
192 }
193 } else {
194 logger.Errorf("Error occurred in committed response: %s", he)
137 } 195 }
138} 196}