-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
This proposal is about providing a way to pass a context.Context
to template execution.
Summary
Reasons:
- Template execution cannot be gracefully interrupted, and it is a rather expensive and potentially long-running task.
- Passing values to templates can only be achieved through the
data interface{}
parameter, and this makes the package not flexible enough for some purposes (expecially for html web nonces).
Advantages:
- Provide an idiomatic way to cancel template execution.
- Provade an idiomatic way to pass values that are not related to data being rendered, but to the context it is rendered in.
Detailed rationale
Cancellation
Templates might implement a rather complex logic, and they might import each other. It could be possible that given some specific conditions a template might run for a long time, even after the result output is not needed anymore.
Currently the only way to address this is to have a separate goroutine that closes the output io.Writer of the template.
This has three main downsides:
- Requires a separate goroutine to run, which costs and must not be leaked
io.WriteCloser
does not document that callingClose
concurrently withWrite
is safe. This means that to cancel a template execution the programmer needs to wrap theirio.WriteCloser
in a safe one, and only then they can abort a template execution safely- If the programmer has only an
io.Writer
they need to implement a custom closing mechanism on it.
Contextual values
Sometimes data needs to be rendered differently based on the context it is rendered in.
In the following examples I'm referring to two html/template
examples I struggled with:
- If I want to render data in a
<form>
, I also need to add an anti-CSRF token. This means that the template might need a value in some cases, and might not need it in others. If a toolkit or framework wants to provide protection against CSRF attacks, it must require the users to add the CSRF field to all their data structures being rendered. If the programmer misses one, that becomes a vulnerability. - If an html template contains a
<script>
or<style>
tag, they need to have a nonce in order to be executed when a strict Content Security Policy is enforced. This means that coders will have to manually pass the nonce value to all templates executing in their handlers. If a coder misses one, that breaks the service with a runtime error in the browser.
It would be nice to be able to protect an entire http.Handler
in a http.ServeMux
from CSRF and XSS and just add security tokens to the context. It would make it very hard for programmers to forget about security.
Example
Adding context values will make HTML templates usage from requiring this:
func handle(w http.ResponseWriter, r *http.Request) {
data := struct {
MyField string
MyOtherField string
Nonce string
CSRFtok string
}{
"Field",
"OtherField",
}
// Retrieve nonce from r.Context()
data.Nonce = nonce
// Retrieve CSRFtok from r.Context()
data.CSRFtok = csrftok
err := tmpl.Execute(w, data)
// Handle err
}
to this (which also handles cancellation):
func handle(w http.ResponseWriter, r *http.Request) {
data := struct {
MyField string
MyOtherField string
}{
"Field",
"OtherField",
}
err := tmpl.ExecuteContext(r.Context(), w, data)
// Handle err
}
Toolkits and frameworks could then provide ways to pre-parse and transform templates and add values to requests context.
Backward compatibility
This change would be backward compatible as it is about adding two func
in each package:
(*Template).ExecuteTemplateContext(context.Context, io.Writer, string, interface{})
(*Template).ExecuteContext(context.Context, io.Writer, interface{})
That could add a context
or ctx
func to the default FuncMap
. This would also be backward compatible because no-one is using ExecuteContext
at the moment, and they could change their func names when migrating to this new API (only if they are using "context" as a name).
Alternatively a forbidden name (like !
or $_
) could be used, but I'm not a fan of this option.
What do you think?
/cc @mvdan @esseks @mikesamuel @robpike @bradfitz @lweichselbaum