aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-08-19 22:14:54 -0700
committerMike Crute <mike@crute.us>2023-08-19 22:14:54 -0700
commit41f2382f9baaf86be5420d9587d5f7c4d54a81b8 (patch)
tree862aa05254fbca08836244ef8a297c9743704d90
parente93af9c1bd2684898f5ebc31733df667ca0a8f12 (diff)
downloadgolib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.tar.bz2
golib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.tar.xz
golib-41f2382f9baaf86be5420d9587d5f7c4d54a81b8.zip
echo: remove mandatory templatesecho/v0.11.0
-rw-r--r--echo/controller/generic_template.go2
-rw-r--r--echo/echo_default.go25
-rw-r--r--echo/error_handler.go252
-rw-r--r--echo/go.mod8
-rw-r--r--echo/go.sum13
-rw-r--r--echo/middleware/recover_middleware.go27
-rw-r--r--echo/template_renderer.go34
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
29const defaultBodySizeLimit = "10M" 25const defaultBodySizeLimit = "10M"
30 26
31var mandatoryTemplates = []string{
32 "404.tpl", "40x.tpl", "50x.tpl",
33 "header.tpl", "footer.tpl",
34}
35
36func makeMiddlewareSkipper(c *prometheus.PrometheusConfig) middleware.Skipper { 27func 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
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}
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
370golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 370golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
371golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 371golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
372golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 372golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
373golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 373golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
374golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 374golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
375golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 375golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
376golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 376golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
377golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 377golang.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
433golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 433golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
434golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 434golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
435golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 435golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
436golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
437golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 436golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
437golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
438golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
438golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 439golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
439golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 440golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
440golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 441golang.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
494golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 495golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
495golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 496golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
496golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 497golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
497golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
498golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 498golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
499golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
500golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
499golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 501golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
500golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 502golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
501golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 503golang.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
503golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 505golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
504golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 506golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
505golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 507golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
506golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
507golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 508golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
509golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
510golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
508golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 511golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
509golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 512golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
510golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 513golang.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 @@
1package middleware 1package middleware
2 2
3import ( 3import (
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
13type TemplateChecker interface { 13type TemplateChecker interface {
14 HaveTemplate(echo.Context, string) bool 14 HaveTemplate(string) bool
15} 15}
16 16
17type TemplateRenderer struct { 17type 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
24func NewTemplateRenderer(templates fs.FS, glob string, funcs template.FuncMap) (*TemplateRenderer, error) { 25func 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
68func (r *TemplateRenderer) getTemplateCache(ctx echo.Context) (*template.Template, error) { 75func (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
82func (r *TemplateRenderer) Render(w io.Writer, name string, data interface{}, ctx echo.Context) error { 88func (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
99func (r *TemplateRenderer) HaveTemplate(ctx echo.Context, name string) bool { 105func (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 }