github.com/caddyserver/caddy/v2/modules/caddyhttp/templates
No package summary is available.
Package
Files: 4. Third party imports: 11. Imports from organisation: 0. Tests: 0. Benchmarks: 0.
Constants
const recursionPreventionHeader = "Caddy-Templates-Include"
Vars
var (
_ caddy.Provisioner = (*Templates)(nil)
_ caddy.Validator = (*Templates)(nil)
_ caddyhttp.MiddlewareHandler = (*Templates)(nil)
)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var defaultMIMETypes = []string{
"text/html",
"text/plain",
"text/markdown",
}
at time of writing, sprig.FuncMap() makes a copy, thus involves iterating the whole map, so do it just once
var sprigFuncMap = sprig.TxtFuncMap()
var supportedFrontMatterTypes = []frontMatterType{
{
FenceOpen: "---",
FenceClose: []string{"---", "..."},
ParseFunc: yamlFrontMatter,
},
{
FenceOpen: "+++",
FenceClose: []string{"+++"},
ParseFunc: tomlFrontMatter,
},
{
FenceOpen: "{",
FenceClose: []string{"}"},
ParseFunc: jsonFrontMatter,
},
}
Types
CustomFunctions
CustomFunctions is the interface for registering custom template functions.
type CustomFunctions interface {
// CustomTemplateFunctions should return the mapping from custom function names to implementations.
CustomTemplateFunctions() template.FuncMap
}
TemplateContext
TemplateContext is the TemplateContext with which HTTP templates are executed.
type TemplateContext struct {
Root http.FileSystem
Req *http.Request
Args []any // defined by arguments to funcInclude
RespHeader WrappedHeader
CustomFuncs []template.FuncMap // functions added by plugins
config *Templates
tpl *template.Template
}
Templates
Templates is a middleware which executes response bodies as Go templates. The syntax is documented in the Go standard library's text/template package.
⚠️ Template functions/actions are still experimental, so they are subject to change.
Custom template functions can be registered by creating a plugin module under the http.handlers.templates.functions.*
namespace that implements the CustomFunctions
interface.
All Sprig functions are supported.
In addition to the standard functions and the Sprig library, Caddy adds extra functions and data that are available to a template:
.Args
A slice of arguments passed to this page/context, for example
as the result of a include
.
{{index .Args 0}} // first argument
.Cookie
Gets the value of a cookie by name.
{{.Cookie "cookiename"}}
env
Gets an environment variable.
{{env "VAR_NAME"}}
placeholder
Gets an placeholder variable.
The braces ({}
) have to be omitted.
{{placeholder "http.request.uri.path"}}
{{placeholder "http.error.status_code"}}
As a shortcut, ph
is an alias for placeholder
.
{{ph "http.request.method"}}
.Host
Returns the hostname portion (no port) of the Host header of the HTTP request.
{{.Host}}
httpInclude
Includes the contents of another file, and renders it in-place, by making a virtual HTTP request (also known as a sub-request). The URI path must exist on the same virtual server because the request does not use sockets; instead, the request is crafted in memory and the handler is invoked directly for increased efficiency.
{{httpInclude "/foo/bar?q=val"}}
import
Reads and returns the contents of another file, and parses it
as a template, adding any template definitions to the template
stack. If there are no definitions, the filepath will be the
definition name. Any {{ define }}
blocks will be accessible by
{{ template }}
or {{ block }}
. Imports must happen before the
template or block action is called. Note that the contents are
NOT escaped, so you should only import trusted template files.
filename.html
{{ define "main" }}
content
{{ end }}
index.html
{{ import "/path/to/filename.html" }}
{{ template "main" }}
include
Includes the contents of another file, rendering it in-place.
Optionally can pass key-value pairs as arguments to be accessed
by the included file. Use .Args N
to access the N-th
argument, 0-indexed. Note that the contents are NOT escaped, so
you should only include trusted template files.
{{include "path/to/file.html"}} // no arguments
{{include "path/to/file.html" "arg0" 1 "value 2"}} // with arguments
readFile
Reads and returns the contents of another file, as-is. Note that the contents are NOT escaped, so you should only read trusted files.
{{readFile "path/to/file.html"}}
listFiles
Returns a list of the files in the given directory, which is relative to the template context's file root.
{{listFiles "/mydir"}}
markdown
Renders the given Markdown text as HTML and returns it. This uses the Goldmark library, which is CommonMark compliant. It also has these extensions enabled: GitHub Flavored Markdown, Footnote, and syntax highlighting provided by Chroma.
{{markdown "My _markdown_ text"}}
.RemoteIP
Returns the connection's IP address.
{{.RemoteIP}}
.ClientIP
Returns the real client's IP address, if trusted_proxies
was configured,
otherwise returns the connection's IP address.
{{.ClientIP}}
.Req
Accesses the current HTTP request, which has various fields, including:
-
.Method
- the method -
.URL
- the URL, which in turn has component fields (Scheme, Host, Path, etc.) -
.Header
- the header fields -
.Host
- the Host or :authority header of the request
{{.Req.Header.Get "User-Agent"}}
.OriginalReq
Like .Req
, except it accesses the original HTTP
request before rewrites or other internal modifications.
.RespHeader.Add
Adds a header field to the HTTP response.
{{.RespHeader.Add "Field-Name" "val"}}
.RespHeader.Del
Deletes a header field on the HTTP response.
{{.RespHeader.Del "Field-Name"}}
.RespHeader.Set
Sets a header field on the HTTP response, replacing any existing value.
{{.RespHeader.Set "Field-Name" "val"}}
httpError
Returns an error with the given status code to the HTTP handler chain.
{{if not (fileExists $includedFile)}}{{httpError 404}}{{end}}
splitFrontMatter
Splits front matter out from the body. Front matter is metadata that appears at the very beginning of a file or string. Front matter can be in YAML, TOML, or JSON formats:
TOML front matter starts and ends with +++
:
+++
template = "blog"
title = "Blog Homepage"
sitename = "A Caddy site"
+++
YAML is surrounded by ---
:
---
template: blog
title: Blog Homepage
sitename: A Caddy site
---
JSON is simply {
and }
:
{
"template": "blog",
"title": "Blog Homepage",
"sitename": "A Caddy site"
}
The resulting front matter will be made available like so:
-
.Meta
to access the metadata fields, for example:{{$parsed.Meta.title}}
-
.Body
to access the body after the front matter, for example:{{markdown $parsed.Body}}
stripHTML
Removes HTML from a string.
{{stripHTML "Shows <b>only</b> text content"}}
humanize
Transforms size and time inputs to a human readable format. This uses the go-humanize library.
The first argument must be a format type, and the last argument is the input, or the input can be piped in. The supported format types are:
-
size which turns an integer amount of bytes into a string like
2.3 MB
-
time which turns a time string into a relative time string like
2 weeks ago
For the time
format, the layout for parsing the input can be configured
by appending a colon :
followed by the desired time layout. You can
find the documentation on time layouts in Go's docs.
The default time layout is RFC1123Z
, i.e. Mon, 02 Jan 2006 15:04:05 -0700
.
pathEscape
Passes a string through url.PathEscape
, replacing characters that have
special meaning in URL path parameters (?
, &
, %
).
Useful e.g. to include filenames containing these characters in URL path
parameters, or use them as an img
element's src
attribute.
{{pathEscape "50%_valid_filename?.jpg"}}
{{humanize "size" "2048000"}}
{{placeholder "http.response.header.Content-Length" | humanize "size"}}
{{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
{{humanize "time:2006-Jan-02" "2022-May-05"}}
type Templates struct {
// The root path from which to load files. Required if template functions
// accessing the file system are used (such as include). Default is
// `{http.vars.root}` if set, or current working directory otherwise.
FileRoot string `json:"file_root,omitempty"`
// The MIME types for which to render templates. It is important to use
// this if the route matchers do not exclude images or other binary files.
// Default is text/plain, text/markdown, and text/html.
MIMETypes []string `json:"mime_types,omitempty"`
// The template action delimiters. If set, must be precisely two elements:
// the opening and closing delimiters. Default: `["{{", "}}"]`
Delimiters []string `json:"delimiters,omitempty"`
// Extensions adds functions to the template's func map. These often
// act as components on web pages, for example.
ExtensionsRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=http.handlers.templates.functions"`
customFuncs []template.FuncMap
logger *zap.Logger
}
WrappedHeader
WrappedHeader wraps niladic functions so that they can be used in templates. (Template functions must return a value.)
type WrappedHeader struct{ http.Header }
frontMatterType
This type doesn't have documentation.
type frontMatterType struct {
FenceOpen string
FenceClose []string
ParseFunc func(input []byte) (map[string]any, error)
}
parsedMarkdownDoc
This type doesn't have documentation.
type parsedMarkdownDoc struct {
Meta map[string]any `json:"meta,omitempty"`
Body string `json:"body,omitempty"`
}
virtualResponseWriter
virtualResponseWriter is used in virtualized HTTP requests that templates may execute.
type virtualResponseWriter struct {
status int
header http.Header
body *bytes.Buffer
}
Functions
func (*TemplateContext) NewTemplate
NewTemplate returns a new template intended to be evaluated with this context, as it is initialized with configuration from this context.
func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
c.tpl = template.New(tplName).Option("missingkey=zero")
// customize delimiters, if applicable
if c.config != nil && len(c.config.Delimiters) == 2 {
c.tpl.Delims(c.config.Delimiters[0], c.config.Delimiters[1])
}
// add sprig library
c.tpl.Funcs(sprigFuncMap)
// add all custom functions
for _, funcMap := range c.CustomFuncs {
c.tpl.Funcs(funcMap)
}
// add our own library
c.tpl.Funcs(template.FuncMap{
"include": c.funcInclude,
"readFile": c.funcReadFile,
"import": c.funcImport,
"httpInclude": c.funcHTTPInclude,
"stripHTML": c.funcStripHTML,
"markdown": c.funcMarkdown,
"splitFrontMatter": c.funcSplitFrontMatter,
"listFiles": c.funcListFiles,
"fileStat": c.funcFileStat,
"env": c.funcEnv,
"placeholder": c.funcPlaceholder,
"ph": c.funcPlaceholder, // shortcut
"fileExists": c.funcFileExists,
"httpError": c.funcHTTPError,
"humanize": c.funcHumanize,
"maybe": c.funcMaybe,
"pathEscape": url.PathEscape,
})
return c.tpl
}
Cognitive complexity: 6
, Cyclomatic complexity: 4
func (*Templates) Provision
Provision provisions t.
func (t *Templates) Provision(ctx caddy.Context) error {
t.logger = ctx.Logger()
mods, err := ctx.LoadModule(t, "ExtensionsRaw")
if err != nil {
return fmt.Errorf("loading template extensions: %v", err)
}
for _, modIface := range mods.(map[string]any) {
t.customFuncs = append(t.customFuncs, modIface.(CustomFunctions).CustomTemplateFunctions())
}
if t.MIMETypes == nil {
t.MIMETypes = defaultMIMETypes
}
if t.FileRoot == "" {
t.FileRoot = "{http.vars.root}"
}
return nil
}
Cognitive complexity: 9
, Cyclomatic complexity: 5
func (*Templates) ServeHTTP
func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
// shouldBuf determines whether to execute templates on this response,
// since generally we will not want to execute for images or CSS, etc.
shouldBuf := func(status int, header http.Header) bool {
ct := header.Get("Content-Type")
for _, mt := range t.MIMETypes {
if strings.Contains(ct, mt) {
return true
}
}
return false
}
rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf)
err := next.ServeHTTP(rec, r)
if err != nil {
return err
}
if !rec.Buffered() {
return nil
}
err = t.executeTemplate(rec, r)
if err != nil {
return err
}
rec.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
rec.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content
rec.Header().Del("Last-Modified") // useless for dynamic content since it's always changing
// we don't know a way to quickly generate etag for dynamic content,
// and weak etags still cause browsers to rely on it even after a
// refresh, so disable them until we find a better way to do this
rec.Header().Del("Etag")
return rec.WriteResponse()
}
Cognitive complexity: 12
, Cyclomatic complexity: 6
func (*Templates) Validate
Validate ensures t has a valid configuration.
func (t *Templates) Validate() error {
if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 {
return fmt.Errorf("delimiters must consist of exactly two elements: opening and closing")
}
return nil
}
Cognitive complexity: 2
, Cyclomatic complexity: 3
func (*virtualResponseWriter) Header
func (vrw *virtualResponseWriter) Header() http.Header {
return vrw.header
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (*virtualResponseWriter) Write
func (vrw *virtualResponseWriter) Write(data []byte) (int, error) {
return vrw.body.Write(data)
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (*virtualResponseWriter) WriteHeader
func (vrw *virtualResponseWriter) WriteHeader(statusCode int) {
vrw.status = statusCode
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (TemplateContext) ClientIP
ClientIP gets the IP address of the real client making the request if the request is trusted (see trusted_proxies), otherwise returns the connection's remote IP.
func (c TemplateContext) ClientIP() string {
address := caddyhttp.GetVar(c.Req.Context(), caddyhttp.ClientIPVarKey).(string)
clientIP, _, err := net.SplitHostPort(address)
if err != nil {
clientIP = address // no port
}
return clientIP
}
Cognitive complexity: 2
, Cyclomatic complexity: 2
func (TemplateContext) Cookie
Cookie gets the value of a cookie with name.
func (c TemplateContext) Cookie(name string) string {
cookies := c.Req.Cookies()
for _, cookie := range cookies {
if cookie.Name == name {
return cookie.Value
}
}
return ""
}
Cognitive complexity: 5
, Cyclomatic complexity: 3
func (TemplateContext) Host
Host returns the hostname portion of the Host header from the HTTP request.
func (c TemplateContext) Host() (string, error) {
host, _, err := net.SplitHostPort(c.Req.Host)
if err != nil {
if !strings.Contains(c.Req.Host, ":") {
// common with sites served on the default port 80
return c.Req.Host, nil
}
return "", err
}
return host, nil
}
Cognitive complexity: 4
, Cyclomatic complexity: 3
func (TemplateContext) OriginalReq
OriginalReq returns the original, unmodified, un-rewritten request as it originally came in over the wire.
func (c TemplateContext) OriginalReq() http.Request {
or, _ := c.Req.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
return or
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (TemplateContext) RemoteIP
RemoteIP gets the IP address of the connection's remote IP.
func (c TemplateContext) RemoteIP() string {
ip, _, err := net.SplitHostPort(c.Req.RemoteAddr)
if err != nil {
return c.Req.RemoteAddr
}
return ip
}
Cognitive complexity: 2
, Cyclomatic complexity: 2
func (Templates) CaddyModule
CaddyModule returns the Caddy module information.
func (Templates) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.templates",
New: func() caddy.Module { return new(Templates) },
}
}
Cognitive complexity: 2
, Cyclomatic complexity: 1
func (WrappedHeader) Add
Add adds a header field value, appending val to existing values for that field. It returns an empty string.
func (h WrappedHeader) Add(field, val string) string {
h.Header.Add(field, val)
return ""
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (WrappedHeader) Del
Del deletes a header field. It returns an empty string.
func (h WrappedHeader) Del(field string) string {
h.Header.Del(field)
return ""
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (WrappedHeader) Set
Set sets a header field value, overwriting any other values for that field. It returns an empty string.
func (h WrappedHeader) Set(field, val string) string {
h.Header.Set(field, val)
return ""
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
Private functions
func extractFrontMatter
extractFrontMatter (input string) (map[string]any, string, error)
References: fmt.Errorf, strings.Index, strings.TrimSpace, unicode.IsSpace.
func init
init ()
References: httpcaddyfile.RegisterHandlerDirective.
func jsonFrontMatter
jsonFrontMatter (input []byte) (map[string]any, error)
References: json.Unmarshal.
func parseCaddyfile
parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
templates [<matcher>] {
mime <types...>
between <open_delim> <close_delim>
root <path>
}
parseCaddyfile (h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
References: caddyconfig.JSON, caddyfile.UnmarshalModule.
func tomlFrontMatter
tomlFrontMatter (input []byte) (map[string]any, error)
References: toml.Unmarshal.
func yamlFrontMatter
yamlFrontMatter (input []byte) (map[string]any, error)
func executeTemplateInBuffer
executeTemplateInBuffer (tplName string, buf *bytes.Buffer) error
func funcImport
funcImport parses the filename into the current template stack. The imported file will be rendered within the current template by calling {{ block }} or {{ template }} from the standard template library. If the imported file has no {{ define }} blocks, the name of the import will be the path
funcImport (filename string) (string, error)
References: bytes.Buffer.
func executeTemplate
executeTemplate executes the template contained in wb.buf and replaces it with the results.
executeTemplate (rr caddyhttp.ResponseRecorder, r *http.Request) error
References: caddyhttp.Error, caddyhttp.HandlerError, errors.As, http.Dir, http.FileSystem, http.StatusInternalServerError.
func funcEnv
funcEnv (varName string) string
References: os.Getenv.
func funcFileExists
funcFileExists returns true if filename can be opened successfully.
funcFileExists (filename string) (bool, error)
References: fmt.Errorf.
func funcFileStat
funcFileStat returns Stat of a filename
funcFileStat (filename string) (fs.FileInfo, error)
References: fmt.Errorf, path.Clean.
func funcHTTPError
funcHTTPError returns a structured HTTP handler error. EXPERIMENTAL; SUBJECT TO CHANGE.
Example usage: {{if not (fileExists $includeFile)}}{{httpError 404}}{{end}}
funcHTTPError (statusCode int) (bool, error)
References: caddyhttp.Error.
func funcHTTPInclude
funcHTTPInclude returns the body of a virtual (lightweight) request to the given URI on the same server. Note that included bodies are NOT escaped, so you should only include trusted resources. If it is not trusted, be sure to use escaping functions yourself.
funcHTTPInclude (uri string) (string, error)
References: bytes.Buffer, caddyhttp.ServerCtxKey, fmt.Errorf, http.Handler, http.Header, http.NewRequest, strconv.Atoi, strconv.Itoa.
func funcHumanize
funcHumanize transforms size and time inputs to a human readable format.
Size inputs are expected to be integers, and are formatted as a byte size, such as "83 MB".
Time inputs are parsed using the given layout (default layout is RFC1123Z) and are formatted as a relative time, such as "2 weeks ago". See https://pkg.go.dev/time#pkg-constants for time layout docs.
funcHumanize (formatType,data string) (string, error)
References: fmt.Errorf, strconv.ParseUint, strings.Split, time.Parse, time.RFC1123Z.
func funcInclude
funcInclude returns the contents of filename relative to the site root and renders it in place. Note that included files are NOT escaped, so you should only include trusted files. If it is not trusted, be sure to use escaping functions in your template.
funcInclude (filename string, args ...any) (string, error)
References: bytes.Buffer.
func funcListFiles
funcListFiles reads and returns a slice of names from the given directory relative to the root of c.
funcListFiles (name string) ([]string, error)
References: fmt.Errorf, path.Clean.
func funcMarkdown
funcMarkdown renders the markdown body as HTML. The resulting HTML is NOT escaped so that it can be rendered as HTML.
funcMarkdown (input any) (string, error)
References: bytes.Buffer, chromahtml.WithClasses, extension.Footnote, extension.GFM, gmhtml.WithUnsafe, goldmark.New, goldmark.WithExtensions, goldmark.WithParserOptions, goldmark.WithRendererOptions, highlighting.NewHighlighting, highlighting.WithFormatOptions, parser.WithAutoHeadingID.
func funcMaybe
funcMaybe invokes the plugged-in function named functionName if it is plugged in (is a module in the 'http.handlers.templates.functions' namespace). If it is not available, a log message is emitted.
The first argument is the function name, and the rest of the arguments are passed on to the actual function.
This function is useful for executing templates that use components that may be considered as optional in some cases (like during local development) where you do not want to require everyone to have a custom Caddy build to be able to execute your template.
NOTE: This function is EXPERIMENTAL and subject to change or removal.
funcMaybe (functionName string, args ...any) (any, error)
References: fmt.Errorf, reflect.Func, reflect.Value, reflect.ValueOf, zap.String.
func funcPlaceholder
funcPlaceholder (name string) string
func funcReadFile
funcReadFile returns the contents of a filename relative to the site root. Note that included files are NOT escaped, so you should only include trusted files. If it is not trusted, be sure to use escaping functions in your template.
funcReadFile (filename string) (string, error)
References: bytes.Buffer.
func funcSplitFrontMatter
splitFrontMatter parses front matter out from the beginning of input, and returns the separated key-value pairs and the body/content. input must be a "stringy" value.
funcSplitFrontMatter (input any) (parsedMarkdownDoc, error)
func funcStripHTML
funcStripHTML returns s without HTML tags. It is fairly naive but works with most valid HTML inputs.
funcStripHTML (s string) string
References: bytes.Buffer.
func readFileToBuffer
readFileToBuffer reads a file into a buffer
readFileToBuffer (filename string, bodyBuf *bytes.Buffer) error
References: fmt.Errorf, io.Copy.
Tests
Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 12. Benchmarks: 0.
Types
handle
This type doesn't have documentation.
type handle struct{}