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

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

前言

什么是模板呢?


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

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

什么是代码注入?

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


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

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


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

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


由于两个模板库使用相同的接口,本文中介绍的所有内容都可以用于任何一个包,但大多数示例将使用 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,这也可能是一本有见地的读物。

相关文章
|
3天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
6天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
2天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
7 1
|
3天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1
|
3天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
4天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
11 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
4天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
18 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
5天前
|
程序员 Go API
【Go语言快速上手(二)】 分支与循环&函数讲解
【Go语言快速上手(二)】 分支与循环&函数讲解
|
5天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
19 0
|
5天前
|
编译器 Go 开发者
Go语言入门|包、关键字和标识符
Go语言入门|包、关键字和标识符
22 0