[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))
}
gin01.png

1.2 Postman工具

一款支持http协议的接口调试与测试工具

开发RESTful API时通常使用Postman来作为客户端的测试工具。

下载链接

2. Go template(模板与渲染)

遇事不决,先写注释。

这一部分主要是用Go语言标准库的html/template包。

两个概念模板和渲染,模板就是一个框架,而渲染就是在这个框架中添加上自己需要的内容。

2.1 模板引擎的使用(单个数据)

可以分为三个部分:定义模板文件、解析模板文件和模板渲染。

2.1.1 定义模板

新建一个文件*.tmpl(后缀一般是tmpl或者tpl但是goland默认的是gohtml),如果发现创建的文件没有格式,可以自行设置:

goland64_Sf04KwtpsW.png

//	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
	}
}

Postman_WsLVOal5jT.png

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,
	})
}

运行结果:

Postman_HmrRxAtj9R.png

也就是说如果有多组复杂类型数据,可以再通过一个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一个字符串数组,返回两个参数,第一个是索引(即下标),第二个是对应的值。

Postman_PgODMdi44V.png

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明显不同);
    该函数类型值必须有12个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的errornil,模板执行会中断并返回给调用模板执行者该错误;

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}}

首先要嵌套根模板,之后再定义这些“窗口”,用来显示自己独特的部分。

Postman_HXXIn1zr8i.png

Postman_zD8irX0IDk.png

最后看一下主函数是怎么写:

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)
}

chrome_x5klRC2fCG.png

按这个意思,从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>


posted @   WtcSky  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示