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,这也可能是一本有见地的读物。

相关文章
|
11天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
51 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
30天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
106 67
|
5天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
33 12
|
9天前
|
监控 算法 安全
解锁企业计算机监控的关键:基于 Go 语言的精准洞察算法
企业计算机监控在数字化浪潮下至关重要,旨在保障信息资产安全与高效运营。利用Go语言的并发编程和系统交互能力,通过进程监控、网络行为分析及应用程序使用记录等手段,实时掌握计算机运行状态。具体实现包括获取进程信息、解析网络数据包、记录应用使用时长等,确保企业信息安全合规,提升工作效率。本文转载自:[VIPShare](https://www.vipshare.com)。
19 0
|
22天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数
|
8月前
|
开发框架 安全 中间件
Go语言开发小技巧&易错点100例(十二)
Go语言开发小技巧&易错点100例(十二)
83 1
|
1月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
2月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
44 3
|
2月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
39 3
|
5月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
178 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库