写点什么

Go 语言快速入门指南:Go 模板介绍

作者:宇宙之一粟
  • 2022 年 2 月 23 日
  • 本文字数:3792 字

    阅读完需:约 12 分钟

Go 语言快速入门指南:Go 模板介绍

前言

本来想介绍一下 Go 模板,然后就发现了一篇很好的文章,故此机器翻译过来,让大家一起学习。


在上一篇文章 《Go 语言 Web 开发很简单:使用模板将视图与逻辑分离》中,我们看到了模板的些许影子。 那么,什么是模板呢?


简而言之,模块即可用于创建动态内容的文本文件。例如,您可能有一个用于网站导航栏的模板,部分动态内容可能是根据当前用户是否登录,是否显示登录或注销按钮。

Go 提供了两个非常棒的模板库—— text/templatehtml/template。这两个模板的使用方式相同,但是在区别在于:html/template 包会做一些编码来帮助防止代码注入。这种编码方式最酷的部分是它是上下文相关的,这意味着它可以发生在 HTML、CSS、JavaScript 甚至 URL 中,模板库将确定如何正确编码文本。

什么是代码注入?

代码注入是一种常见的利用方式,用户试图让您的服务器或页面的访问者执行他们编写并输入到您的应用程序中的代码。当您不处理或转义输入以使其无效时,这通常会成为一个问题。


例如,如果有人使用用户名 <script>alert("Hi!");</script> 注册你的 Web 应用程序,而你只是在他们的个人资料页面上插入了他们的用户名,这可能会导致代码注入。

在这种情况下,任何时候有人访问该用户的个人资料页面,他们都会看到一个带有文本“Hi!”的 javascript 警报。这变得很危险,因为 javascript 通常附加到用户的会话中,因此如果您要运行一些访问 /delete-my-account 页面的代码,它可能会删除访问注入用户个人资料页面的任何用户的帐户。


Go 通过在 html 模板中编码文本使其无法执行来解决这个问题。例如,用户名 <script>alert("Hi!");</script> 将被编码为:

&lt;script&gt;alert(&quot;Hi!&quot;);&lt;/script&gt; 它将呈现为原始用户名,但不会被访问页面的任何人作为实际代码执行。


由于两个模板库使用相同的接口,本文中介绍的所有内容都可以用于任何一个包,但大多数示例将使用 html/template 包来生成 HTML 代码。


创建模板


首先让我们继续创建一个执行一个非常简单的模板的 main 函数,以便我们可以看到它的运行情况。打开您的编辑器并导航到您计划编写 Go 代码和存储模板的位置。路径应该是这样的: $GOPATH/src/???/ ??? 可以是你想要的任何东西)。


我们将使用 .gohtml 扩展名来命名我们的文件,因为编辑器通常使用它来指示您想要 Go HTML 模板语法突出显示。 Atom 和 Sublime Text 都有默认识别这个扩展的 Go 插件。也就是说,您可以使用 .html 或您想要的任何其他扩展名。


创建文件 hello.gohtmlmain.go,然后将以下代码添加到 hello.gohtml

<h1>Hello, {{.Name}}!</h1>
复制代码

然后在 main.go 中添加如下代码:

package main
import ( "html/template" "os")
func main() {
t, err := template.ParseFiles("hello.gohtml") if err != nil { panic(err) }
data := struct { Name string }{"John Smith"}
err = t.Execute(os.Stdout, data) if err != nil { panic(err) }}
复制代码

然后继续使用 go run main.go 运行您的代码。你可以应该看到以下输出:

<h1>Hello, John Smith!</h1>
复制代码

您已成功创建您的第一个模板!现在让我们探索 Go 的模板库如何处理编码。


上下文编码

我之前提到过 Go 的 html/template 包会根据代码的上下文进行编码,在本节中,我们将演示该编码在不同上下文中的实际样子。创建另一个名为 context.gohtml 的模板并将以下代码添加到其中。

{{.Title}}{{.HTML}}{{.SafeHTML}}{{.}}

<a title="{{.Title}}"><a title="{{.HTML}}">
<a href="{{.HTML}}"><a href="?q={{.HTML}}"><a href="{{.Path}}"><a href="?q={{.Path}}">
<!-- Encoding even works on non-string values! --><script> var dog = {{.Dog}}; var map = {{.Map}}; doWork({{.Title}});</script>
复制代码

然后更新 main.go 以获得以下代码:

package main
import ( "html/template" "os")
type Test struct { HTML string SafeHTML template.HTML Title string Path string Dog Dog Map map[string]string}
type Dog struct { Name string Age int}
func main() { t, err := template.ParseFiles("context.gohtml") if err != nil { panic(err) }
data := Test{ HTML: "<h1>A header!</h1>", SafeHTML: template.HTML("<h1>A Safe header</h1>"), Title: "Backslash! An in depth look at the \"\\\" character.", Path: "/dashboard/settings", Dog: Dog{"Fido", 6}, Map: map[string]string{ "key": "value", "other_key": "other_value", }, }
err = t.Execute(os.Stdout, data) if err != nil { panic(err) }}
复制代码

然后继续使用 go run main.go 运行您的代码。您应该看到如下所示的输出:

Backslash! An in depth look at the &#34;\&#34; character.&lt;h1&gt;A header!&lt;/h1&gt;<h1>A Safe header</h1>{&lt;h1&gt;A header!&lt;/h1&gt; &lt;h1&gt;A Safe header&lt;/h1&gt; Backslash! An in depth look at the &#34;\&#34; character. /dashboard/settings {Fido 6} map[key:value other_key:other_value]}
<a title="Backslash! An in depth look at the &#34;\&#34; character."><a title="&lt;h1&gt;A header!&lt;/h1&gt;">
<a href="%3ch1%3eA%20header!%3c/h1%3e"><a href="?q=%3ch1%3eA%20header%21%3c%2fh1%3e"><a href="/dashboard/settings"><a href="?q=%2fdashboard%2fsettings">
<script> var dog = {"Name":"Fido","Age":6}; var map = {"key":"value","other_key":"other_value"}; doWork("Backslash! An in depth look at the \"\\\" character.");</script>
复制代码

这里发生了很多事情,所以让我们花一点时间来看看它。我们将从前四行开始。

Backslash! An in depth look at the &#34;\&#34; character.&lt;h1&gt;A header!&lt;/h1&gt;<h1>A Safe header</h1>{&lt;h1&gt;A header!&lt;/h1&gt; &lt;h1&gt;A Safe header&lt;/h1&gt; Backslash! An in depth look at the &#34;\&#34; character. /dashboard/settings {Fido 6} map[key:value other_key:other_value]}
复制代码

在前几行中,我们在 HTML 上下文中编码值。因此,html/template 对需要编码以正确呈现的任何字符进行编码。具体来说, <> 字符被编码。


在第三行,我们输出了一个 template.HTML 类型的值,这是告诉 html/template 包该字符串可以安全地跳过编码的方式。这意味着在处理用户输入时不应该使用这种类型,因为它可能导致代码注入。


接下来的两行 (6-7) 是锚标记,显示将值放入属性(如 title)时如何编码。这主要是为了证明 html/template 包知道该属性,并且您将在接下来的几行中看到,当值在 href 属性内时,它们的编码方式不同。

<a title="Backslash! An in depth look at the &#34;\&#34; character."><a title="&lt;h1&gt;A header!&lt;/h1&gt;">
复制代码

第 9 到 12 行演示了值被编码为查询参数或 href 属性中的原始路径。这里有两个例子,因为我想证明这里编码的值与 HTML 上下文中不同,但两者有区别。例如,查询参数对正斜杠 (/) 字符进行了编码,但是当值作为路径插入时,斜杠未编码。

<a href="%3ch1%3eA%20header!%3c/h1%3e"><a href="?q=%3ch1%3eA%20header%21%3c%2fh1%3e"><a href="/dashboard/settings"><a href="?q=%2fdashboard%2fsettings">
复制代码

接下来我们会期待注释行(在 context.gohtml 中查找),但正如您所见,html/template 包会为我们删除任何 HTML 注释。

删除评论在大多数情况下很有用,但有时您希望评论保留。如果您想了解这是如何完成的,我建议您查看第三篇文章“在 Go 模板中使用函数”的 HTML 安全字符串和 HTML 注释部分。在该部分中,我们讨论了在 HTML 模板中保留注释的方法。

最后,我们有了 JavaScript 代码,第 14 行到第 18 行。在我看来,这是最酷的部分。


虽然大多数模板库在这里并没有做太多花哨的事情,但 Go 的 html/template 在确定正确的上下文和您可能想要的内容方面做得非常出色。

例如,在前两行中,struct 和 map 将被扩展为 JSON 对象,这很可能是开发人员打算做的。在最后一个示例中,我们插入了一个字符串,正如您所看到的,它也被编码为它的 JSON 等价物,它只是一个带有引号的字符串。因此,我们不必像 doWork("{{.Title}}") 将变量用引号括起来。


<script>  var dog = {"Name":"Fido","Age":6};  var map = {"key":"value","other_key":"other_value"};  doWork("Backslash! An in depth look at the \"\\\" character.");</script>
复制代码


接下来

这篇文章应该让您对 html/template 包可以处理的不同上下文以及如何在模板中使用变量有一个很好的概述。在以后的文章中,随着我们学习在 Go 中使用模板库的更多功能,我们将对此进行扩展。


在下一篇文章中,Go 中的模板操作和嵌套模板,我们将从简单的模板开始,然后使用操作构建更复杂的模板。第三篇文章,在 Go 模板中使用函数,着眼于如何在 Go 模板中使用内置函数和自定义函数。第四篇也是最后一篇文章,在 MVC 中创建 V,将把我们学到的大部分知识与新概念结合在一起,以构建可重用的视图,从而简化应用程序的其余部分呈现 HTML 页面的方式。即使你不使用 MVC,这也可能是一本有见地的读物。


原文链接:An Intro to Templates in Go - Contextual Encoding - Calhoun.io

发布于: 刚刚阅读数: 2
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020.05.07 加入

🏆InfoQ写作平台-第二季签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于软件工程师,弹性不加班 热衷分享,执着于阅读写作,佛系不水文

评论

发布
暂无评论
Go 语言快速入门指南:Go 模板介绍