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

举报
宇宙之一粟 发表于 2022/02/23 23:50:40 2022/02/23
1.6k+ 0 0
【摘要】 在上一篇文章 《Go 语言 Web 开发很简单:使用模板将视图与逻辑分离》中,我们看到了模板的些许影子。 那么,什么是模板呢?简而言之,模块即可用于创建动态内容的文本文件。例如,您可能有一个用于网站导航栏的模板,部分动态内容可能是根据当前用户是否登录,是否显示登录或注销按钮。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>

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。