做一些文章详情页的时候,因为用到了富文本编辑器,因此数据库中存储的是HTML格式的内容,当从数据库中读取显示到页面时需要进行转换,否则显示就是实际的字符串内容,因此需要进行转换,转换的代码很简单,就是通过关键函数template.HTML
示例如下:
"content": template.HTML(a.Content),
另外可以自定义模板函数,在模板文件中使用:
package main
import (
"html/template"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
router.LoadHTMLFiles("./index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.tmpl", "<a href='lizhouwen.com'>1232</a>")
})
router.Run(":9999")
}
html代码如下:
tml/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用text/template包。
在基于MVC的Web架构中,我们通常需要在后端渲染一些数据到HTML文件中,从而实现动态的网页效果。
模板示例
通过将模板应用于一个数据结构(即该数据结构作为模板的参数)来执行,来获得输出。模板中的注释引用数据接口的元素(一般如结构体的字段或者字典的键)来控制执行过程和获取需要呈现的值。模板执行时会遍历结构并将指针表示为’.‘(称之为”dot”)指向运行过程中数据结构的当前位置的值。
用作模板的输入文本必须是utf-8编码的文本。”Action”—数据运算和控制单位—由”{{“和”}}“界定;在Action之外的所有文本都不做修改的拷贝到输出中。Action内部不能有换行,但注释可以有换行。
HTML文件代码如下:
我们的HTTP server端代码如下:
模板语法
{{.}}
模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。
当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
HTML文件代码如下:
同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。
注释
pipeline
pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意:并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。
变量
Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:
其中$variable是变量的名字。声明变量的action不会产生任何输出。
条件判断
Go模板语法中的条件判断有以下几种:
range
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
with
预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。
预定义的全局函数如下:
比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
{{eq arg1 arg2 arg3}}
比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。
自定义函数
Go的模板支持自定义函数。
我们可以在模板文件hello.html中使用我们自定义的kua函数了。
{{kua .Name}}
嵌套template
我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。
举个例子: t.html文件内容如下:
ul.html文件内容如下:
我们注册一个templDemo路由处理函数.
tmplDemo函数的具体内容如下:
面我们实现了一个最简单的Web服务器软件。这个服务器软件很傻,无论你的URL是什么,所有请求都只返回“Hello World!”字符串。而且这个字符串是写死在代码中的。
接下来我们继续完善这个Web服务器软件,今天我们增加对普通静态文件的支持。也就是当用户通过浏览器向该服务器发送请求的时候该软件会从磁盘上读取相应的文件,然后发送给浏览器。本次的完善也是很有限的,只对上次的代码做了很小的改动。这里实现的主要功能是从磁盘读取一个文件的内容,然后封装http响应头后发生给浏览器。因此,这里主要涉及到Go语言文件访问相关的库的使用。最后实现的效果就是在浏览器上显示html文件渲染后的内容:
图1 实现效果图
由于涉及到文件的访问,因此这里先介绍一下文件相关的包。Go语言文件相关的操作在os包里面,其中os.File封装了文件读写相关的操作。如下是os包及File涉及的主要函数和方法,由于篇幅问题,这里只保留的必要的内容:
type File
File代表一个打开的文件对象。
func Open(name string) (file *File, err error)
Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
func (f *File) Read(b []byte) (n int, err error)
Read方法从f中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。
func (f *File) Write(b []byte) (n int, err error)
Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func (f *File) Close() error
Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。
下面是本文实现的从文件读取数据的方法,在Web服务的主程序中通过调用该方法来从磁盘读取数据。这个方法的输入是文件路径,输出是文件的内容。
图2 文件读取方法实现
细心的同学可能已经发现,我们这里读取数据并没有用File的Read方法,而是用了另外一个包ioutil的ReadAll方法。这里使用ioutil包的方法也是作为一个引子,期望大家更多的了解Go语言提供的库函数。该方法与File的Read方法功能是一样的,但性能上要好一些。
ioutil包是一个io相关的包,提供了对常见io操作的性能优化,该包提供的功能主要包括:
func ReadAll(r io.Reader) ([]byte, error)
从一个io.Reader读取所有数据,并返回一个字节数组
func ReadDir(dirname string) ([]os.FileInfo, error)
从一个目录读取数据,并得到这个目录里的文件对象列表
func ReadFile(filename string) ([]byte, error)
读取指定文件的内容,并返回一个字节数组
关于文件操作的相关介绍本文先到这里,更详细的介绍我们后面专门另起新文介绍。
Web服务端改动的地方也很少,除了上面介绍的读取文件内容的方法外,在主函数中调整了数据发送方面的代码,其它地方均没有做改动。下面是涉及到的代码片段。
图3 服务端代码修改
如图3所示,这里一共修改了3个地方:
测试验证
完成上述修改后,我们就可以通过浏览器进行验证了。这里需要具备一个html文件,该文件保存在与服务端相同的路径下面,名称为index.html。该文件的内容如下:
图4 HTML文件内容
如果熟悉html的同学,很清楚是做什么的,就是现实一个红色的“Hello World!”字符串,不了解html的同学也没关系,了解到这里就行。
在服务端运行程序, 浏览器数据地址后回车,可以看到如下内容:
图5 最终效果图
好了,今天先到这,后面文章我们将增加对图片的支持。
*请认真填写需求信息,我们会在24小时内与您取得联系。