Go API Documentation

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

Uses: template.FuncMap, template.New, url.PathEscape.

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

Uses: fmt.Errorf.

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

Uses: bytes.Buffer, caddyhttp.NewResponseRecorder, http.Header, strconv.Itoa, strings.Contains.

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

Uses: fmt.Errorf.
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

Uses: caddyhttp.ClientIPVarKey, caddyhttp.GetVar, net.SplitHostPort.

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

Uses: net.SplitHostPort, strings.Contains.

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

Uses: caddyhttp.OriginalRequestCtxKey, http.Request.

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

Uses: net.SplitHostPort.

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{}

Test functions

TestCookie

References: http.Cookie, time.Minute, time.Now.

TestCookieMultipleCookies

References: fmt.Sprintf, http.Cookie.

TestFileListing

References: errors.Is, filepath.Base, filepath.Join, filepath.ToSlash, fmt.Sprintf, fs.ErrNotExist, os.MkdirTemp, os.ModePerm, os.RemoveAll, os.WriteFile, reflect.DeepEqual, sort.Strings, strings.HasSuffix.

TestHTTPInclude

References: caddyhttp.ServerCtxKey, context.WithValue.

TestHumanize

References: strings.Contains, strings.HasPrefix, strings.HasSuffix.

TestIP

TestImport

References: errors.Is, filepath.Join, fmt.Sprintf, fs.ErrNotExist, os.ModePerm, os.Remove, os.WriteFile, strings.Contains.

TestInclude

References: errors.Is, filepath.Join, fmt.Sprintf, fs.ErrNotExist, os.ModePerm, os.Remove, os.WriteFile.

TestMarkdown

TestNestedInclude

References: bytes.Buffer, errors.Is, filepath.Join, fmt.Sprintf, fs.ErrNotExist, os.ModePerm, os.Remove, os.WriteFile.

TestSplitFrontMatter

TestStripHTML