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

相关文章
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
263 4
|
3月前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
2月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
201 1
|
4月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
397 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
261 0
|
4月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
230 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
332 0
|
10月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
10月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
4月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
297 1