diff options
author | Mike Crute <mike@crute.us> | 2021-11-09 17:43:17 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2021-11-09 17:43:17 -0800 |
commit | ef4444e6217592e2666b8a46e8c762eaf93fa8a6 (patch) | |
tree | 84c4f7891c21c90072590451dc088deeaca78eb7 | |
parent | 9f9e8fa552b945aad3d487b6dafd095f939fbfc4 (diff) | |
download | golib-ef4444e6217592e2666b8a46e8c762eaf93fa8a6.tar.bz2 golib-ef4444e6217592e2666b8a46e8c762eaf93fa8a6.tar.xz golib-ef4444e6217592e2666b8a46e8c762eaf93fa8a6.zip |
Convert templates to use FS interfaceecho/v0.2.0
-rw-r--r-- | echo/echo_default.go | 37 | ||||
-rw-r--r-- | echo/error_handler.go | 17 | ||||
-rw-r--r-- | echo/static_file.go | 71 | ||||
-rw-r--r-- | echo/template_renderer.go | 25 | ||||
-rw-r--r-- | tls/ocsp.go | 1 |
5 files changed, 119 insertions, 32 deletions
diff --git a/echo/echo_default.go b/echo/echo_default.go index ab163ff..895ee6e 100644 --- a/echo/echo_default.go +++ b/echo/echo_default.go | |||
@@ -5,7 +5,9 @@ import ( | |||
5 | "crypto/tls" | 5 | "crypto/tls" |
6 | "fmt" | 6 | "fmt" |
7 | "html/template" | 7 | "html/template" |
8 | "io/fs" | ||
8 | "net/http" | 9 | "net/http" |
10 | "path" | ||
9 | "sync" | 11 | "sync" |
10 | 12 | ||
11 | glmw "code.crute.us/mcrute/golib/echo/middleware" | 13 | glmw "code.crute.us/mcrute/golib/echo/middleware" |
@@ -20,10 +22,10 @@ type EchoConfig struct { | |||
20 | Debug bool | 22 | Debug bool |
21 | BindAddress string | 23 | BindAddress string |
22 | BindTLSAddress string | 24 | BindTLSAddress string |
23 | TLSCert string | 25 | TLSCacheDir string |
24 | TLSKey string | ||
25 | TrustedProxyIPRanges []string | 26 | TrustedProxyIPRanges []string |
26 | TemplatePath *string | 27 | EmbeddedTemplates fs.FS |
28 | DiskTemplates fs.FS | ||
27 | TemplateGlob *string | 29 | TemplateGlob *string |
28 | TemplateFunctions template.FuncMap | 30 | TemplateFunctions template.FuncMap |
29 | CombinedHostLogFile string | 31 | CombinedHostLogFile string |
@@ -34,6 +36,7 @@ type EchoConfig struct { | |||
34 | type EchoWrapper struct { | 36 | type EchoWrapper struct { |
35 | *echo.Echo | 37 | *echo.Echo |
36 | tlsServer http.Server | 38 | tlsServer http.Server |
39 | templateFS fs.FS | ||
37 | bindAddress string | 40 | bindAddress string |
38 | ocspErrors chan gltls.OcspError | 41 | ocspErrors chan gltls.OcspError |
39 | ocspManager *gltls.OcspManager | 42 | ocspManager *gltls.OcspManager |
@@ -51,7 +54,7 @@ func (w *EchoWrapper) Init() error { | |||
51 | } | 54 | } |
52 | 55 | ||
53 | func (w *EchoWrapper) CachedStaticRoute(prefix, path string) { | 56 | func (w *EchoWrapper) CachedStaticRoute(prefix, path string) { |
54 | w.Group(prefix, glmw.CacheOneMonthMiddleware).Static("/", path) | 57 | StaticFS(w.GET, w.templateFS, prefix, path, glmw.CacheOneMonthMiddleware) |
55 | } | 58 | } |
56 | 59 | ||
57 | func (w *EchoWrapper) OcspErrors() chan gltls.OcspError { | 60 | func (w *EchoWrapper) OcspErrors() chan gltls.OcspError { |
@@ -103,6 +106,10 @@ func (w *EchoWrapper) ServeTLS(ctx context.Context, wg *sync.WaitGroup) error { | |||
103 | ) | 106 | ) |
104 | } | 107 | } |
105 | 108 | ||
109 | func (w *EchoWrapper) GetTemplateFS() fs.FS { | ||
110 | return w.templateFS | ||
111 | } | ||
112 | |||
106 | // NewDefaultEchoWithConfig builds a wrapper around an Echo instance and | 113 | // NewDefaultEchoWithConfig builds a wrapper around an Echo instance and |
107 | // configures it in the default way that it should probably be configured in | 114 | // configures it in the default way that it should probably be configured in |
108 | // all cases. The struct returned from this function can be treated like a | 115 | // all cases. The struct returned from this function can be treated like a |
@@ -120,11 +127,22 @@ func NewDefaultEchoWithConfig(c EchoConfig) (*EchoWrapper, error) { | |||
120 | return nil, fmt.Errorf("Error building XFF IP extractor: %w", err) | 127 | return nil, fmt.Errorf("Error building XFF IP extractor: %w", err) |
121 | } | 128 | } |
122 | 129 | ||
130 | // Use templates from disk in debug mode and the embedded ones that are | ||
131 | // built-in to the binary for prod mode. | ||
132 | var templates fs.FS | ||
133 | if c.DiskTemplates != nil && c.Debug { // Debug Mode | ||
134 | templates = c.DiskTemplates | ||
135 | } else if c.EmbeddedTemplates != nil && !c.Debug { // Prod Mode | ||
136 | templates = c.EmbeddedTemplates | ||
137 | } else { | ||
138 | return nil, fmt.Errorf("No templates available for use") | ||
139 | } | ||
140 | |||
123 | // Only install template handlers if the path and glob are set | 141 | // Only install template handlers if the path and glob are set |
124 | if c.TemplatePath != nil && c.TemplateGlob != nil { | 142 | if templates != nil && c.TemplateGlob != nil { |
125 | e.HTTPErrorHandler = ErrorHandler(*c.TemplatePath, c.TemplateFunctions) | 143 | e.HTTPErrorHandler = ErrorHandler(templates, c.TemplateFunctions) |
126 | 144 | ||
127 | tr, err := NewTemplateRenderer(*c.TemplatePath, *c.TemplateGlob, c.TemplateFunctions) | 145 | tr, err := NewTemplateRenderer(templates, *c.TemplateGlob, c.TemplateFunctions) |
128 | if err != nil { | 146 | if err != nil { |
129 | return nil, fmt.Errorf("Error loading template renderer: %w", err) | 147 | return nil, fmt.Errorf("Error loading template renderer: %w", err) |
130 | } | 148 | } |
@@ -146,8 +164,8 @@ func NewDefaultEchoWithConfig(c EchoConfig) (*EchoWrapper, error) { | |||
146 | 164 | ||
147 | cmek := make(chan gltls.OcspError) | 165 | cmek := make(chan gltls.OcspError) |
148 | cm := &gltls.OcspManager{ | 166 | cm := &gltls.OcspManager{ |
149 | CertPath: c.TLSCert, | 167 | CertPath: path.Join(c.TLSCacheDir, "cert.pem"), |
150 | KeyPath: c.TLSKey, | 168 | KeyPath: path.Join(c.TLSCacheDir, "key.pem"), |
151 | Errors: cmek, | 169 | Errors: cmek, |
152 | } | 170 | } |
153 | 171 | ||
@@ -181,5 +199,6 @@ func NewDefaultEchoWithConfig(c EchoConfig) (*EchoWrapper, error) { | |||
181 | bindAddress: c.BindAddress, | 199 | bindAddress: c.BindAddress, |
182 | ocspErrors: cmek, | 200 | ocspErrors: cmek, |
183 | ocspManager: cm, | 201 | ocspManager: cm, |
202 | templateFS: templates, | ||
184 | }, nil | 203 | }, nil |
185 | } | 204 | } |
diff --git a/echo/error_handler.go b/echo/error_handler.go index c32fe86..bfbcfb6 100644 --- a/echo/error_handler.go +++ b/echo/error_handler.go | |||
@@ -4,14 +4,14 @@ import ( | |||
4 | "bytes" | 4 | "bytes" |
5 | "fmt" | 5 | "fmt" |
6 | "html/template" | 6 | "html/template" |
7 | "io/fs" | ||
7 | "net/http" | 8 | "net/http" |
8 | "path" | ||
9 | 9 | ||
10 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" |
11 | ) | 11 | ) |
12 | 12 | ||
13 | // Copied from echo and tweaked to make our errors nicer | 13 | // Copied from echo and tweaked to make our errors nicer |
14 | func ErrorHandler(templatePath string, funcs template.FuncMap) func(error, echo.Context) { | 14 | func ErrorHandler(templates fs.FS, funcs template.FuncMap) func(error, echo.Context) { |
15 | return func(err error, c echo.Context) { | 15 | return func(err error, c echo.Context) { |
16 | defer func() { | 16 | defer func() { |
17 | if r := recover(); r != nil { | 17 | if r := recover(); r != nil { |
@@ -37,12 +37,13 @@ func ErrorHandler(templatePath string, funcs template.FuncMap) func(error, echo. | |||
37 | } | 37 | } |
38 | } | 38 | } |
39 | 39 | ||
40 | t, err := template.New("").Funcs(funcs).ParseFiles( | 40 | t, err := template.New("").Funcs(funcs).ParseFS( |
41 | path.Join(templatePath, "404.tpl"), | 41 | templates, |
42 | path.Join(templatePath, "40x.tpl"), | 42 | "404.tpl", |
43 | path.Join(templatePath, "50x.tpl"), | 43 | "40x.tpl", |
44 | path.Join(templatePath, "header.tpl"), | 44 | "50x.tpl", |
45 | path.Join(templatePath, "footer.tpl"), | 45 | "header.tpl", |
46 | "footer.tpl", | ||
46 | ) | 47 | ) |
47 | if err != nil { | 48 | if err != nil { |
48 | he = &echo.HTTPError{ | 49 | he = &echo.HTTPError{ |
diff --git a/echo/static_file.go b/echo/static_file.go new file mode 100644 index 0000000..9723db7 --- /dev/null +++ b/echo/static_file.go | |||
@@ -0,0 +1,71 @@ | |||
1 | package echo | ||
2 | |||
3 | import ( | ||
4 | "io" | ||
5 | "io/fs" | ||
6 | "net/http" | ||
7 | "net/url" | ||
8 | "path/filepath" | ||
9 | |||
10 | "github.com/labstack/echo/v4" | ||
11 | ) | ||
12 | |||
13 | type routeFunc func(string, echo.HandlerFunc, ...echo.MiddlewareFunc) *echo.Route | ||
14 | |||
15 | func StaticFS(get routeFunc, f fs.FS, prefix, root string, m ...echo.MiddlewareFunc) *echo.Route { | ||
16 | if root == "" { | ||
17 | root = "." // For security we want to restrict to CWD. | ||
18 | } | ||
19 | |||
20 | h := func(c echo.Context) error { | ||
21 | p, err := url.PathUnescape(c.Param("*")) | ||
22 | if err != nil { | ||
23 | return err | ||
24 | } | ||
25 | |||
26 | name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security | ||
27 | fp, err := f.Open(name) | ||
28 | if err != nil { | ||
29 | // The access path does not exist | ||
30 | return echo.NotFoundHandler(c) | ||
31 | } | ||
32 | defer fp.Close() | ||
33 | |||
34 | fi, err := fp.Stat() | ||
35 | if err != nil { | ||
36 | // The access path does not exist | ||
37 | return echo.NotFoundHandler(c) | ||
38 | } | ||
39 | |||
40 | // If the request is for a directory and does not end with "/" | ||
41 | p = c.Request().URL.Path // path must not be empty. | ||
42 | if fi.IsDir() && p[len(p)-1] != '/' { | ||
43 | // Redirect to ends with "/" | ||
44 | // return c.Redirect(http.StatusMovedPermanently, p+"/") | ||
45 | // TODO: Serve an index.html if there is one for this dir | ||
46 | return echo.NotFoundHandler(c) | ||
47 | } | ||
48 | |||
49 | fs, ok := fp.(io.ReadSeeker) | ||
50 | if !ok { | ||
51 | c.Logger().Errorf("File %s is not a io.ReadSeeker", p) | ||
52 | return echo.ErrInternalServerError | ||
53 | } | ||
54 | |||
55 | http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), fs) | ||
56 | return nil | ||
57 | } | ||
58 | |||
59 | // Handle added routes based on trailing slash: | ||
60 | // /prefix => exact route "/prefix" + any route "/prefix/*" | ||
61 | // /prefix/ => only any route "/prefix/*" | ||
62 | if prefix != "" { | ||
63 | if prefix[len(prefix)-1] == '/' { | ||
64 | // Only add any route for intentional trailing slash | ||
65 | return get(prefix+"*", h, m...) | ||
66 | } | ||
67 | get(prefix, h, m...) | ||
68 | } | ||
69 | |||
70 | return get(prefix+"/*", h, m...) | ||
71 | } | ||
diff --git a/echo/template_renderer.go b/echo/template_renderer.go index 0bafd92..7bfd006 100644 --- a/echo/template_renderer.go +++ b/echo/template_renderer.go | |||
@@ -3,8 +3,8 @@ package echo | |||
3 | import ( | 3 | import ( |
4 | "html/template" | 4 | "html/template" |
5 | "io" | 5 | "io" |
6 | "io/fs" | ||
6 | "net/http" | 7 | "net/http" |
7 | "os" | ||
8 | "path/filepath" | 8 | "path/filepath" |
9 | 9 | ||
10 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" |
@@ -15,16 +15,16 @@ type TemplateChecker interface { | |||
15 | } | 15 | } |
16 | 16 | ||
17 | type TemplateRenderer struct { | 17 | type TemplateRenderer struct { |
18 | path string | 18 | templates fs.FS |
19 | glob string | 19 | glob string |
20 | funcs template.FuncMap | 20 | funcs template.FuncMap |
21 | cache *template.Template | 21 | cache *template.Template |
22 | } | 22 | } |
23 | 23 | ||
24 | func NewTemplateRenderer(path, glob string, funcs template.FuncMap) (*TemplateRenderer, error) { | 24 | func NewTemplateRenderer(templates fs.FS, glob string, funcs template.FuncMap) (*TemplateRenderer, error) { |
25 | var err error | 25 | var err error |
26 | 26 | ||
27 | r := &TemplateRenderer{path: path, glob: glob, funcs: funcs} | 27 | r := &TemplateRenderer{templates: templates, glob: glob, funcs: funcs} |
28 | r.cache, err = r.loadTemplates() | 28 | r.cache, err = r.loadTemplates() |
29 | if err != nil { | 29 | if err != nil { |
30 | return nil, err | 30 | return nil, err |
@@ -36,7 +36,7 @@ func NewTemplateRenderer(path, glob string, funcs template.FuncMap) (*TemplateRe | |||
36 | func (r *TemplateRenderer) loadTemplates() (*template.Template, error) { | 36 | func (r *TemplateRenderer) loadTemplates() (*template.Template, error) { |
37 | t := template.New("").Funcs(r.funcs) | 37 | t := template.New("").Funcs(r.funcs) |
38 | 38 | ||
39 | err := filepath.Walk(r.path, func(path string, info os.FileInfo, err error) error { | 39 | err := fs.WalkDir(r.templates, ".", func(path string, info fs.DirEntry, err error) error { |
40 | if err != nil { | 40 | if err != nil { |
41 | return err | 41 | return err |
42 | } | 42 | } |
@@ -44,17 +44,12 @@ func (r *TemplateRenderer) loadTemplates() (*template.Template, error) { | |||
44 | if !info.IsDir() { | 44 | if !info.IsDir() { |
45 | _, fn := filepath.Split(path) | 45 | _, fn := filepath.Split(path) |
46 | if ok, _ := filepath.Match(r.glob, fn); ok { | 46 | if ok, _ := filepath.Match(r.glob, fn); ok { |
47 | fc, err := os.ReadFile(path) | 47 | fc, err := fs.ReadFile(r.templates, path) |
48 | if err != nil { | 48 | if err != nil { |
49 | return err | 49 | return err |
50 | } | 50 | } |
51 | 51 | ||
52 | rp, err := filepath.Rel(r.path, path) | 52 | _, err = t.New(path).Parse(string(fc)) |
53 | if err != nil { | ||
54 | return err | ||
55 | } | ||
56 | |||
57 | _, err = t.New(rp).Parse(string(fc)) | ||
58 | if err != nil { | 53 | if err != nil { |
59 | return err | 54 | return err |
60 | } | 55 | } |
diff --git a/tls/ocsp.go b/tls/ocsp.go index 94f7f68..b80764f 100644 --- a/tls/ocsp.go +++ b/tls/ocsp.go | |||
@@ -125,6 +125,7 @@ func (m *OcspManager) Run(ctx context.Context, wg *sync.WaitGroup) error { | |||
125 | } | 125 | } |
126 | } | 126 | } |
127 | 127 | ||
128 | // TODO: TLS.GetCertificate for dyanmic certs for LE (cache these) | ||
128 | func (m *OcspManager) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { | 129 | func (m *OcspManager) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { |
129 | m.RLock() | 130 | m.RLock() |
130 | defer m.RUnlock() | 131 | defer m.RUnlock() |