[Go语言Web01]Go语言标准库Web开发
1. Web开发引导
1.1 访问网页返回文件
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
http.HandleFunc("/hello", sayHello)
err := http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
return
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
b, _ := os.ReadFile("./hello.txt")
_, _ = fmt.Fprintln(w, string(b))
}

1.2 Postman工具
一款支持http协议的接口调试与测试工具
开发RESTful API时通常使用Postman来作为客户端的测试工具。
2. Go template(模板与渲染)
遇事不决,先写注释。
这一部分主要是用Go语言标准库的html/template包。
两个概念模板和渲染,模板就是一个框架,而渲染就是在这个框架中添加上自己需要的内容。
2.1 模板引擎的使用(单个数据)
可以分为三个部分:定义模板文件、解析模板文件和模板渲染。
2.1.1 定义模板
新建一个文件*.tmpl
(后缀一般是tmpl或者tpl但是goland默认的是gohtml),如果发现创建的文件没有格式,可以自行设置:
// hello.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>Hello {{.}}</p>
</body>
</html>
以上就是一个go template,其中最精彩的部分就是{{.}}
了!{{
和}}
之间的内容就相当于表格里让你填写的内容,而要获取到这里到底填写了什么,.
就是来访问的。
比方说一个用户界面,每个人的用户名、id不同但其他地方相同,此时只需要用一个html的模板,在获取数据的时候直接就能得到相应的html界面。
2.1.2 解析模板和渲染模板
下面测试一下:
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 2. 解析模板
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println(err)
return
}
// 3. 渲染模板
name := "火焰车"
err = t.Execute(w, name)
if err != nil {
fmt.Println(err)
return
}
}
2.2 复杂类型数据
2.2.1 结构体
go template如下所示:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>Hello {{ .Name }}</p>
<p>年龄:{{ .Age }}</p>
<p>性别:{{ .gender }}</p>
</body>
</html>
main.go如下所示:
package main
import (
"fmt"
"html/template"
"net/http"
)
type User struct {
Name string
Gender string
Age int
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println(err)
return
}
// 模板渲染
u1 := User{
"火焰CAR", "男", 18,
}
t.Execute(w, u1)
}
结构体中的成员名首字母如果是小写,就不能在其他文件中访问!
2.2.2 Map
go template如下所示:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>Hello {{ .name }}</p>
<p>年龄:{{ .age }}</p>
<p>性别:{{ .gender }}</p>
</body>
</html>
main.go如下所示:
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println(err)
return
}
// 模板渲染
m1 := map[string]any{
"name": "FIRE车", "gender": "女", "age": 18,
}
t.Execute(w, m1)
}
用map就不需要考虑首字母大小写了,因为是靠key值来寻找。
2.3.3 结构体 + Map
此时的go template:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>u1</p>
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
<p>m1</p>
<p>Hello {{ .m1.name }}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
</body>
</html>
此时的main.go:
package main
import (
"fmt"
"html/template"
"net/http"
)
type User struct {
Name string
Gender string
Age int
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println(err)
return
}
// 模板渲染
u1 := User{
"火焰CAR", "男", 20,
}
m1 := map[string]any{
"name": "FIRE车", "gender": "女", "age": 18,
}
t.Execute(w, map[string]any{
"u1": u1, "m1": m1,
})
}
运行结果:
也就是说如果有多组复杂类型数据,可以再通过一个map来导入进去。
调用的方法就是在.
(传进去的新map)的基础上再去调用其他成员。
2.3 模板中的操作
2.3.1 注释
在文件中的注释格式为:{{/* xxx */}}
,可以换行。
2.3.2 变量
在go template中定义一个变量的格式:{{ $a := 100 }}
或者{{ $age := .age}}
,就是在{{}}
中使用$
符号。
2.3.3 移除空格
把数据前或数据后的空格忽略掉格式为{{- xxx -}}
,注意这里的{{-
是一个整体表示移除左侧所有空格,同理-}}
是一个整体表示移除右侧所有空格。
2.3.4 判断
在go template中可以使用if来进行判断输出,如下:
{{ if $a}}
{{ $a }}
{{ else }}
什么都没有
{{ end }}
其中必须要有{{ end }}
来作为结尾。
2.3.5 range
直接演示一下:
{{range $index, $hobby := .hobbys}}
index - {{ $index }} : {{ $hobby }}
{{end}}
就和直接写go有异曲同工之妙,range一个字符串数组,返回两个参数,第一个是索引(即下标),第二个是对应的值。
2.3.6 with
with就是创建一个作用域来简化代码,直接举个例子:
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
{{with .u1 }}
<p>Hello {{ .Name }}</p>
<p>年龄:{{ .Age }}</p>
<p>性别:{{ .Gender }}</p>
{{end}}
1-3行和5-9行实际代表的意思是一样的,只不过在with的作用域中.
代表的是.u1
。
2.3.7 预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。
预定义的全局函数如下:
and
函数返回它的第一个empty参数或者最后一个参数;
就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
返回第一个非empty参数或者最后一个参数;
亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
返回它的单个参数的布尔值的否定
len
返回它的参数的整数类型长度
index
执行结果为第一个参数以剩下的参数为索引/键指向的值;
如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
即fmt.Sprint
printf
即fmt.Sprintf
println
即fmt.Sprintln
html
返回与其参数的文本表示形式等效的转义HTML。
这个函数在html/template中不可用。
urlquery
以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
这个函数在html/template中不可用。
js
返回与其参数的文本表示形式等效的转义JavaScript。
call
执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
其中Y是函数类型的字段或者字典的值,或者其他类似情况;
call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
2.3.8 比较函数
在这里面是不能直接使用逻辑判断符的("==" 、“!=”……),因此要使用特殊的一些函数(标识)来进行逻辑判断。
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
如{{ if lt $age 18}}
,如果age小于18就返回true。
以上函数有且只有eq可以进行多个比较{{ eq $a $b $c}}
,会依次进行a和b的比较以及a和c的比较。
3. 自定义模板
template文件:
<!DOCTYPE html>
<html>
<title>自定义模板函数</title>
<body>
{{kua . }}
</body>
</html>
可以看到这里是调用了一个kua函数,是我们自己定义来的。
main文件:
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/", f1)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func f1(w http.ResponseWriter, r *http.Request) {
// 定义一个函数kua
// 要么只有一个返回值,要么两个返回值,第二个是error
kua := func(name string) string {
return name + "真是个大好人!"
}
// 定义模板
t := template.New("f.tmpl")
t.Funcs(template.FuncMap{
"kua": kua,
})
// 解析模板
_, err := t.ParseFiles("./f.tmpl")
if err != nil {
fmt.Println(err)
}
name := "火焰车"
// 渲染模板
t.Execute(w, name)
}
之前是直接t, err := template.ParseFile("f.tmpl")
直接把这个模板给解析了。
现在如果要自定义那么就要先定义模板之后再解析模板,t := template.New("f.tmpl")
就是定义了一个模板此时还没有解析,我就可以在这个模板里添加自己创造的函数,如上述代码中的kua
。
添加函数的方法就是:
t.Funcs(template.FuncMap{
"kua" : kua,
"abc" : x,
})
在这个map中:
前的是在模板中使用时的名字(标识),后面的是自己创造的函数的函数名。
注意!解析后就不能添加自定义的函数了,因此需要自定义函数的时候要把定义模板和解析模板分开才可以。
4. 嵌套模板
是指在模板里再嵌套模板,一般来说嵌套的都是一些网页上的元素(表格、导航栏……),而不是一个完整的网页。
嵌套有两种方式,第一种是被嵌套的模板是单独的文件,另一种是被嵌套的模板是在模板内define定义的。
下面是f.tmpl,我们在其中嵌套两个模板:
<!DOCTYPE html>
<html>
<title>嵌套模板</title>
<body>
{{/* 嵌套了一个单独的模板文件 */}}
{{template "x.tmpl"}}
<hr>
{{/* 嵌套了一个define定义的模板文件*/}}
{{template "o.templ"}}
<div>你好,{{ . }}</div>
</body>
</html>
{{/* define定义一个模板文件 */}}
{{define "o.templ"}}
<ol>
<li>唱</li>
<li>跳</li>
<li>rap</li>
</ol>
{{end}}
可以看到,嵌套模板是通过{{template "xxx.tmpl"}}
来实现的,其中o.tmpl是在f.tmpl里通过define定义来的。
x.tmpl:
<ul>
<li>aa</li>
<li>bb</li>
<li>cc</li>
</ul>
最后是主函数:
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/", f1)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
func f1(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./f.tmpl", "./x.tmpl")
if err != nil {
fmt.Println(err)
}
name := "火焰车"
// 渲染模板
t.Execute(w, name)
}
如果被嵌套的模板(x.tmpl)不是在嵌套的模板(f.tmpl)中通过define来定义的,那么记得要在解析模板的时候将它放入其中,嵌套模板在前,被嵌套进去的模板在后。
5. 模板继承
模块继承应该就是和嵌套模板经常一起使用的东西,一般来说,网页的大多数界面布局是一样的,因此做一个根模板就可以减少代码的重复,提高代码的可读性。
5.1 根模板
我们首先定义一个根模板base.tmpl来作为网页布局的一个通用框架:
<!DOCTYPE html>
<html>
<head>
<title>模板继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
}
.menu {
width: 20%;
height: 100%;
position: fixed;
left: 0;
background-color: cornflowerblue;
}
.center {
text-align: center;
}
</style>
</head>
<body>
<div class="nav"></div>
<div class="main">
<div class="menu"></div>
<div class="content center">
{{block "content" .}}{{end}}
</div>
</div>
</body>
</html>
在上面的代码中{{block "content" .}}{{end}}
就是可以放入每个页面独特部分的一个”窗口“。
5.2 继承根模板
下面的看一下两个继承了base.tmpl的模板:
{{/*继承根模板*/}}
{{template "base.tmpl" .}}
{{/*重新定义块模板*/}}
{{define "content"}}
<h1>这是home页面</h1>
<p>Hello {{ . }}</p>
{{end}}
{{/*继承根模板*/}}
{{template "base.tmpl" .}}
{{/*重新定义块模板*/}}
{{define "content"}}
<h1>这是index页面</h1>
<p>Hello {{ . }}</p>
{{end}}
首先要嵌套根模板,之后再定义这些“窗口”,用来显示自己独特的部分。
最后看一下主函数是怎么写:
package main
import (
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/home", home)
err := http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
func index(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./templates/base.tmpl", "./templates/index.tmpl")
if err != nil {
return
}
msg := "firecar"
// 渲染模板
t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.ParseFiles("./templates/base.tmpl", "./templates/home.tmpl")
if err != nil {
return
}
msg := "firecar"
// 渲染模板
t.Execute(w, msg)
}
这次是把tmpl都放到了一个文件夹(templates)里,所以在解析模板的时候用到了相对路径。
5.3 正则解析多个模板
通过t, err := template.ParseGlob("./templates/*.tmpl")
可以导入多个模板,但是!!!但是!!!如果这些模板不是互通的请不要这样使用。导致的一个结果就是网页之间的错乱。
package main
import (
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/home", home)
err := http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
func index(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
//t, err := template.ParseFiles("./templates/base.tmpl", "./templates/index.tmpl")
t, err := template.ParseGlob("./templates/*.tmpl")
if err != nil {
return
}
msg := "index"
// 渲染模板
err = t.ExecuteTemplate(w, "index.tmpl", msg)
if err != nil {
return
}
//t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
//t, err := template.ParseFiles("./templates/base.tmpl", "./templates/home.tmpl")
t, err := template.ParseGlob("./templates/*.tmpl")
if err != nil {
return
}
msg := "home"
// 渲染模板
err = t.ExecuteTemplate(w, "home.tmpl", msg)
if err != nil {
return
}
//t.Execute(w, msg)
}
按这个意思,从main.go带着msg参数进入了base.tmpl,然后base.tmpl带着msg继承给了index,出现了这么个情况。
6. 模板补充内容
6.1 修改默认标识符
Go模板引擎里默认{{}}
为标识符,但是同时在一些前端框架中同样也是以此为标识符(如Vue、AngularJS),这里就演示一下如何修改Go模块引擎中默认标识符。
template.New("index.tmpl").
Delims("<<",">>").
ParseFiles("./index.tmpl")
在解析之前先Delims。
6.2 text/template 与 html/template的区别
html/template
会转义接收到的内容使其原文输出。如果不转义,就有遭受xss攻击的风险(传入一段script代码写一个死循环),会导致网站崩溃或信息泄露。
类似这样:
func xss(w http.ResponseWriter, r *http.Request) {
// 定义
// 解析
t, err := template.ParseFiles("./xss.tmpl")
if err != nil {
return
}
// 渲染
str := "<script>alert(123);</script>"
t.Execute(w, str)
}
当然,这只是基于不信任用户输入,开发者本身还是可以通过自定义函数的方式来进行带格式的传输。
package main
import (
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/xss", xss)
err := http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
func xss(w http.ResponseWriter, r *http.Request) {
// 定义
// 解析
t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
}).ParseFiles("./xss.tmpl")
if err != nil {
return
}
// 渲染
str1 := "<script>alert(123);</script>"
str2 := "<h1>!FireCar!</h1>"
t.Execute(w, map[string]string{
"str1": str1,
"str2": str2,
})
}
以下代码就是定义一个自定义函数,作用是把字符串转成html格式。
t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
}).ParseFiles("./xss.tmpl")
在模板中有两种方法可以调用这个函数:
<!DOCTYPE html>
<html>
<body>
<div>
{{safe .str1}}
{{.str2|safe}}
</div>
</body>
</html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现