diff options
author | Mike Crute <mike@crute.us> | 2023-08-19 22:14:54 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2023-08-19 22:14:54 -0700 |
commit | 41f2382f9baaf86be5420d9587d5f7c4d54a81b8 (patch) | |
tree | 862aa05254fbca08836244ef8a297c9743704d90 | |
parent | e93af9c1bd2684898f5ebc31733df667ca0a8f12 (diff) | |
download | golib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.tar.bz2 golib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.tar.xz golib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.zip |
echo: remove mandatory templatesecho/v0.11.0
-rw-r--r-- | echo/controller/generic_template.go | 2 | ||||
-rw-r--r-- | echo/echo_default.go | 25 | ||||
-rw-r--r-- | echo/error_handler.go | 252 | ||||
-rw-r--r-- | echo/go.mod | 8 | ||||
-rw-r--r-- | echo/go.sum | 13 | ||||
-rw-r--r-- | echo/middleware/recover_middleware.go | 27 | ||||
-rw-r--r-- | echo/template_renderer.go | 34 |
7 files changed, 219 insertions, 142 deletions
diff --git a/echo/controller/generic_template.go b/echo/controller/generic_template.go index 190182b..bd64304 100644 --- a/echo/controller/generic_template.go +++ b/echo/controller/generic_template.go | |||
@@ -80,7 +80,7 @@ func (h *GenericTemplateHandler) canonicalizeUrl(c echo.Context, tr glecho.Templ | |||
80 | path = "index.tpl" | 80 | path = "index.tpl" |
81 | } else if !strings.HasSuffix(path, ".html") { | 81 | } else if !strings.HasSuffix(path, ".html") { |
82 | p := fmt.Sprintf("%s.tpl", path) | 82 | p := fmt.Sprintf("%s.tpl", path) |
83 | if !tr.HaveTemplate(c, p) { | 83 | if !tr.HaveTemplate(p) { |
84 | path = fmt.Sprintf("%s/index.tpl", path) | 84 | path = fmt.Sprintf("%s/index.tpl", path) |
85 | } else { | 85 | } else { |
86 | path = p | 86 | path = p |
diff --git a/echo/echo_default.go b/echo/echo_default.go index 76f543d..ff3c754 100644 --- a/echo/echo_default.go +++ b/echo/echo_default.go | |||
@@ -22,17 +22,8 @@ import ( | |||
22 | 22 | ||
23 | // Docs: https://echo.labstack.com/guide/ | 23 | // Docs: https://echo.labstack.com/guide/ |
24 | 24 | ||
25 | // TODO: | ||
26 | // - Integrate CSRF | ||
27 | // - Integrate session | ||
28 | |||
29 | const defaultBodySizeLimit = "10M" | 25 | const defaultBodySizeLimit = "10M" |
30 | 26 | ||
31 | var mandatoryTemplates = []string{ | ||
32 | "404.tpl", "40x.tpl", "50x.tpl", | ||
33 | "header.tpl", "footer.tpl", | ||
34 | } | ||
35 | |||
36 | func makeMiddlewareSkipper(c *prometheus.PrometheusConfig) middleware.Skipper { | 27 | func makeMiddlewareSkipper(c *prometheus.PrometheusConfig) middleware.Skipper { |
37 | var path string | 28 | var path string |
38 | 29 | ||
@@ -209,21 +200,17 @@ func (w *EchoWrapper) configureTemplates(c *EchoConfig) error { | |||
209 | templates = c.EmbeddedTemplates | 200 | templates = c.EmbeddedTemplates |
210 | } | 201 | } |
211 | 202 | ||
212 | // Only install template handlers if the path and glob are set | 203 | // Only install template handlers if the path is set |
213 | if templates != nil { | 204 | if templates != nil { |
214 | w.HTTPErrorHandler = ErrorHandler(templates, c.TemplateFunctions) | 205 | tr, err := NewTemplateRenderer(templates, "*.tpl", c.TemplateFunctions, w.Debug) |
215 | |||
216 | tr, err := NewTemplateRenderer(templates, "*.tpl", c.TemplateFunctions) | ||
217 | if err != nil { | 206 | if err != nil { |
218 | return fmt.Errorf("Error loading template renderer: %w", err) | 207 | return fmt.Errorf("Error loading template renderer: %w", err) |
219 | } | 208 | } |
220 | w.Renderer = tr | ||
221 | 209 | ||
222 | for _, t := range mandatoryTemplates { | 210 | w.HTTPErrorHandler = NewDefaultErrorHandler(tr).HandleError |
223 | if !tr.HaveTemplate(w.NewContext(nil, nil), t) { | 211 | w.Renderer = tr |
224 | return fmt.Errorf("Template renderer is missing required template %s", t) | 212 | } else { |
225 | } | 213 | w.HTTPErrorHandler = NewNoHTMLErrorHandler().HandleError |
226 | } | ||
227 | } | 214 | } |
228 | 215 | ||
229 | w.templateFS = templates | 216 | w.templateFS = templates |
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 | ||
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 | 14 | ||
30 | path := "50x.tpl" | 15 | type 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{} | 19 | type 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()) | 24 | func 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 { | 32 | func (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 | |||
58 | type PlainTextErrorHandler struct{} | ||
59 | |||
60 | func (h *PlainTextErrorHandler) Handle(c echo.Context, e *echo.HTTPError) error { | ||
61 | return c.String(e.Code, fmt.Sprintf("%s", e.Message)) | ||
62 | } | ||
63 | |||
64 | type JSONErrorHandler struct{} | ||
65 | |||
66 | func (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, | 80 | type failsafeHandler struct{} |
82 | "application/json": handleJson, | 81 | |
82 | func (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 | |||
88 | type ErrorHandler struct { | ||
89 | types []contenttype.MediaType | ||
90 | typeMap map[string]ContentErrorHandler | ||
91 | failsafeHandler ContentErrorHandler | ||
92 | } | ||
93 | |||
94 | func NewErrorHandler() *ErrorHandler { | ||
95 | return &ErrorHandler{ | ||
96 | types: []contenttype.MediaType{}, | ||
97 | typeMap: map[string]ContentErrorHandler{}, | ||
98 | failsafeHandler: &failsafeHandler{}, | ||
83 | } | 99 | } |
100 | } | ||
101 | |||
102 | func 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 | |||
113 | func 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, | 123 | func (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) { | 130 | func (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()] | 147 | func (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 | |||
165 | func (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 | } |
diff --git a/echo/go.mod b/echo/go.mod index fcedf30..782c6e8 100644 --- a/echo/go.mod +++ b/echo/go.mod | |||
@@ -66,10 +66,10 @@ require ( | |||
66 | github.com/valyala/bytebufferpool v1.0.0 // indirect | 66 | github.com/valyala/bytebufferpool v1.0.0 // indirect |
67 | github.com/valyala/fasttemplate v1.2.1 // indirect | 67 | github.com/valyala/fasttemplate v1.2.1 // indirect |
68 | go.uber.org/atomic v1.9.0 // indirect | 68 | go.uber.org/atomic v1.9.0 // indirect |
69 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect | 69 | golang.org/x/crypto v0.11.0 // indirect |
70 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect | 70 | golang.org/x/net v0.10.0 // indirect |
71 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect | 71 | golang.org/x/sys v0.10.0 // indirect |
72 | golang.org/x/text v0.3.7 // indirect | 72 | golang.org/x/text v0.11.0 // indirect |
73 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect | 73 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect |
74 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect | 74 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect |
75 | google.golang.org/grpc v1.41.0 // indirect | 75 | google.golang.org/grpc v1.41.0 // indirect |
diff --git a/echo/go.sum b/echo/go.sum index fe3ba40..3b58191 100644 --- a/echo/go.sum +++ b/echo/go.sum | |||
@@ -370,8 +370,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U | |||
370 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | 370 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
371 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | 371 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
372 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | 372 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
373 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= | 373 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= |
374 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | 374 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= |
375 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | 375 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
376 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | 376 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
377 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | 377 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= |
@@ -433,8 +433,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R | |||
433 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | 433 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= |
434 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | 434 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
435 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | 435 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
436 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8= | ||
437 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | 436 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
437 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= | ||
438 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | ||
438 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | 439 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
439 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | 440 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= |
440 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | 441 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= |
@@ -494,8 +495,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc | |||
494 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | 495 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
495 | golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | 496 | golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
496 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | 497 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
497 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= | ||
498 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | 498 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
499 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= | ||
500 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
499 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | 501 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
500 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | 502 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
501 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | 503 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
@@ -503,8 +505,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 | |||
503 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | 505 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
504 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | 506 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
505 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | 507 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
506 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | ||
507 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | 508 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |
509 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= | ||
510 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||
508 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | 511 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
509 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | 512 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
510 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | 513 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
diff --git a/echo/middleware/recover_middleware.go b/echo/middleware/recover_middleware.go index 310e146..af4259a 100644 --- a/echo/middleware/recover_middleware.go +++ b/echo/middleware/recover_middleware.go | |||
@@ -1,9 +1,11 @@ | |||
1 | package middleware | 1 | package middleware |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | ||
4 | "fmt" | 5 | "fmt" |
5 | "net/http" | 6 | "net/http" |
6 | "runtime" | 7 | "runtime" |
8 | "text/template" | ||
7 | 9 | ||
8 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" |
9 | "github.com/labstack/echo/v4/middleware" | 11 | "github.com/labstack/echo/v4/middleware" |
@@ -49,6 +51,16 @@ var ( | |||
49 | DisablePrintStack: false, | 51 | DisablePrintStack: false, |
50 | LogLevel: 0, | 52 | LogLevel: 0, |
51 | } | 53 | } |
54 | defaultErrorTemplate = `<html> | ||
55 | <head> | ||
56 | <title>Error</title> | ||
57 | <style type="text/css"> | ||
58 | pre { border: 1px solid black; padding: 1em; background: #f0f0f0; font-size: 1.5em; } | ||
59 | </style> | ||
60 | </head> | ||
61 | <body><h1>Error</h1><pre>{{ . }}</pre></body> | ||
62 | </html> | ||
63 | ` | ||
52 | ) | 64 | ) |
53 | 65 | ||
54 | // Recover returns a middleware which recovers from panics anywhere in the chain | 66 | // Recover returns a middleware which recovers from panics anywhere in the chain |
@@ -68,6 +80,11 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { | |||
68 | config.StackSize = DefaultRecoverConfig.StackSize | 80 | config.StackSize = DefaultRecoverConfig.StackSize |
69 | } | 81 | } |
70 | 82 | ||
83 | errorTemplate, err := template.New("").Parse(defaultErrorTemplate) | ||
84 | if err != nil { | ||
85 | panic("RecoverWithConfig: error parsing html template") | ||
86 | } | ||
87 | |||
71 | return func(next echo.HandlerFunc) echo.HandlerFunc { | 88 | return func(next echo.HandlerFunc) echo.HandlerFunc { |
72 | return func(c echo.Context) error { | 89 | return func(c echo.Context) error { |
73 | if config.Skipper(c) { | 90 | if config.Skipper(c) { |
@@ -100,8 +117,14 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { | |||
100 | } | 117 | } |
101 | } | 118 | } |
102 | if c.Echo().Debug { | 119 | if c.Echo().Debug { |
103 | msg := fmt.Sprintf("%v %s\n", err, stack[:length]) | 120 | buf := bytes.Buffer{} |
104 | c.String(http.StatusInternalServerError, msg) | 121 | msg := fmt.Sprintf("%v\n\n%s", err, stack[:length]) |
122 | if err := errorTemplate.Execute(&buf, msg); err != nil { | ||
123 | c.Logger().Errorf("Error rendering HTML error page: %s", err) | ||
124 | c.String(http.StatusInternalServerError, msg) | ||
125 | return | ||
126 | } | ||
127 | c.HTMLBlob(http.StatusInternalServerError, buf.Bytes()) | ||
105 | } else { | 128 | } else { |
106 | c.Error(err) | 129 | c.Error(err) |
107 | } | 130 | } |
diff --git a/echo/template_renderer.go b/echo/template_renderer.go index 7bfd006..8515249 100644 --- a/echo/template_renderer.go +++ b/echo/template_renderer.go | |||
@@ -11,7 +11,7 @@ import ( | |||
11 | ) | 11 | ) |
12 | 12 | ||
13 | type TemplateChecker interface { | 13 | type TemplateChecker interface { |
14 | HaveTemplate(echo.Context, string) bool | 14 | HaveTemplate(string) bool |
15 | } | 15 | } |
16 | 16 | ||
17 | type TemplateRenderer struct { | 17 | type TemplateRenderer struct { |
@@ -19,12 +19,19 @@ type TemplateRenderer struct { | |||
19 | glob string | 19 | glob string |
20 | funcs template.FuncMap | 20 | funcs template.FuncMap |
21 | cache *template.Template | 21 | cache *template.Template |
22 | debug bool | ||
22 | } | 23 | } |
23 | 24 | ||
24 | func NewTemplateRenderer(templates fs.FS, glob string, funcs template.FuncMap) (*TemplateRenderer, error) { | 25 | func NewTemplateRenderer(templates fs.FS, glob string, funcs template.FuncMap, debug bool) (*TemplateRenderer, error) { |
25 | var err error | 26 | var err error |
26 | 27 | ||
27 | r := &TemplateRenderer{templates: templates, glob: glob, funcs: funcs} | 28 | r := &TemplateRenderer{ |
29 | templates: templates, | ||
30 | glob: glob, | ||
31 | funcs: funcs, | ||
32 | debug: debug, | ||
33 | } | ||
34 | |||
28 | r.cache, err = r.loadTemplates() | 35 | r.cache, err = r.loadTemplates() |
29 | if err != nil { | 36 | if err != nil { |
30 | return nil, err | 37 | return nil, err |
@@ -65,22 +72,21 @@ func (r *TemplateRenderer) loadTemplates() (*template.Template, error) { | |||
65 | return t, nil | 72 | return t, nil |
66 | } | 73 | } |
67 | 74 | ||
68 | func (r *TemplateRenderer) getTemplateCache(ctx echo.Context) (*template.Template, error) { | 75 | func (r *TemplateRenderer) getTemplateCache() (*template.Template, error) { |
69 | var err error | 76 | if !r.debug { |
77 | return r.cache, nil | ||
78 | } | ||
70 | 79 | ||
71 | tc := r.cache | 80 | tc, err := r.loadTemplates() |
72 | if ctx.Echo().Debug { | 81 | if err != nil { |
73 | tc, err = r.loadTemplates() | 82 | return nil, err |
74 | if err != nil { | ||
75 | return nil, err | ||
76 | } | ||
77 | } | 83 | } |
78 | 84 | ||
79 | return tc, nil | 85 | return tc, nil |
80 | } | 86 | } |
81 | 87 | ||
82 | func (r *TemplateRenderer) Render(w io.Writer, name string, data interface{}, ctx echo.Context) error { | 88 | func (r *TemplateRenderer) Render(w io.Writer, name string, data interface{}, ctx echo.Context) error { |
83 | tc, err := r.getTemplateCache(ctx) | 89 | tc, err := r.getTemplateCache() |
84 | if err != nil { | 90 | if err != nil { |
85 | return err | 91 | return err |
86 | } | 92 | } |
@@ -96,8 +102,8 @@ func (r *TemplateRenderer) Render(w io.Writer, name string, data interface{}, ct | |||
96 | return nil | 102 | return nil |
97 | } | 103 | } |
98 | 104 | ||
99 | func (r *TemplateRenderer) HaveTemplate(ctx echo.Context, name string) bool { | 105 | func (r *TemplateRenderer) HaveTemplate(name string) bool { |
100 | tc, err := r.getTemplateCache(ctx) | 106 | tc, err := r.getTemplateCache() |
101 | if err != nil { | 107 | if err != nil { |
102 | return false | 108 | return false |
103 | } | 109 | } |