go语言http服务入门详解(go http包详解)
当你在浏览器中输入URL时,实际上是在发送一个对Web页面的请求。该请求被发送到服务器。服务器的工作是获取适当的页面并将其作为响应发送回浏览器。
在Web的早期,服务器通常读取服务器硬盘上HTML文件的内容并将该HTML发送回浏览器。
但今天,服务器更常见的是与程序通信来完成请求,而不是读取文件。这个程序可以用任何你想要的语言编写,包括Go!
net/http介绍
Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用net/http包,我们可以很方便地编写HTTP客户端或服务端的程序。
服务端
默认的Server
首先,我们编写一个最简单的Web服务器。编写这个Web服务只需要两步:
- 注册一个处理器函数(注册到DefaultServeMux);
- 设置监听的TCP地址并启动服务;
对应到我们的代码里就是这样的:
package main import ( "log" "net/http" ) func sayHello(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("Hello World!")) } func main() { //1.注册一个给定模式的处理器函数到DefaultServeMux http.HandleFunc("/", sayHello) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux err := http.ListenAndServe("127.0.0.1:8080", nil) log.Fatal(err) } 运行该程序,通过浏览器访问,可以看到Hello World!显示在了页面上
ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。当处理器参数是nil时,这表示采用包变量DefaultServeMux作为处理器。
Handle和HandleFunc函数可以向DefaultServeMux添加任意多个处理器函数。
http.HandleFunc
使用Go语言中的net/http包来编写一个简单的接收HTTP请求的Server端示例,net/http包是对net包的进一步封装,专门用来处理HTTP协议的数据。具体的代码如下:
处理器函数的实现原理:
通过源码可知,这个函数实际上是调用了默认的DefaultServeMux的HandleFunc方法,这也解释了我们第一步里所说的默认的实际注册到DefaultServeMux。
既然说了http.ListenAndServe的第二个参数为nil时采用默认的DefaultServeMux,那么如果我们不想采用默认的,而是想自己创建一个ServerMux该怎么办呢,http给我们提供了方法,NewServeMux创建并返回一个新的*ServeMux
// NewServeMux分配并返回一个新的ServeMux。 func NewServeMux() *ServeMux { return new(ServeMux) } 如果是我们自己创建的ServeMux,我们只需要简单的更新一下代码:
package main import ( "log" "net/http" ) func sayHello(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("Hello World!")) } func main() { //1.创建一个ServeMux,将处理器函数注册到serverMux serveMux := http.NewServeMux() serveMux.HandleFunc("/", sayHello) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux,如果指定了则表示使用自定义ServeMux err := http.ListenAndServe("127.0.0.1:8080", serveMux) log.Fatal(err) } 运行修改后的代码,和采用默认ServeMux一样正常运行
http.Handle
如果是使用http的handle方法,则handle的第二个参数需要实现handler接口,要想实现这个接口,就得实现这个接口的serveHTTP方法
示例:
package main import ( "log" "net/http" ) type MyHandler struct {} func (m *MyHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("Hello World!")) } func main() { //1.创建一个ServeMux,将处理器函数注册到serverMux http.Handle("/", &MyHandler{}) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux err := http.ListenAndServe("127.0.0.1:8080", nil) log.Fatal(err) } http.Request
一个Web服务器最基本的工作就是接收请求,做出响应。http包帮助我们封装了一个Request结构体,我们通过这个结构体拿到很多用户的一次HTTP请求的所有信息。这个Request结构体定义如下:
type Request struct { //Method指定HTTP方法(GET、POST、PUT等) //Go的客户端不支持CONNECT方法 Method string //请求URL相关信息 URL *url.URL //接收到的请求的协议版本。HTTP客户端代码始终使用HTTP/1.1或HTTP2 Proto string // "HTTP/1.0" //请求头相关信息 Header Header //请求主体 Body io.ReadCloser //客户端IP地址 RemoteAddr string //请求URI RequestURI string ....... //内容上下文 ctx context.Context } 我这里列举的并不是完整的Request结构体定义,只是大致的说明一下。完整的定义以及这些字段的中文含义可以查看Go语言标准库文档。
我们通过通过浏览器可以发现,我们一次HTTP请求会携带很多信息
这些信息,我们可以通过http.Request来获取到
package main import ( "fmt" "log" "net/http" ) func HeaderInfo(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() fmt.Printf("Method: %#v\n", r.Method) fmt.Printf("URL: %#v\n", r.URL) fmt.Printf("Header: %#v\n", r.Header) fmt.Printf("Body: %#v\n", r.Body) fmt.Printf("RemoteAddr: %#v\n", r.RemoteAddr) _, _ = w.Write([]byte("请求成功,请在终端查看!")) } func main() { //1.注册一个给定模式的处理器函数到DefaultServeMux http.HandleFunc("/header", HeaderInfo) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux err := http.ListenAndServe("127.0.0.1:8080", nil) log.Fatal(err) } 自定义Server
要管理服务端的行为,可以创建一个自定义的Server:
import ( "fmt" "net/http" "time" ) type MyHandler struct {} func (h *MyHandler) ServeHTTP (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world!") } func main() { var handler MyHandler var server = http.Server{ Addr: ":8080", Handler: &handler, ReadTimeout: 2 * time.Second, MaxHeaderBytes: 1 << 20, } var err = server.ListenAndServe() if err != nil { fmt.Printf("http server failed, err: %v\n", err) return } } HTML模板
template包(html/template)实现了数据驱动的模板,以生成可防止代码注入的HTML输出。它提供了与text/template包相同的接口,并且只要实在输出HTML的场景,就应该使用html/template而不是text/template。
Go语言模板的使用可以分为三步:定义模板文件、解析模板文件和渲染模板文件。
定义模板文件
定义模板文件时需要我们按照相关语法规则去编写,后面会详细介绍。
解析模板文件
上面定义好了模板文件之后,可以使用下面额常用方法去解析模板文件,得到模板对象:
func ParseFS(fs fs.FS, patterns ...string) (*Template, error) func ParseFiles(filenames ...string) (*Template, error) func ParseGlob(pattern string) (*Template, error) 当然,你也可以使用func New(name string) *Template函数创建一个名为name的模板,然后对其调用上面的方法去解析模板字符串或模板文件。
模板渲染
渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。
func (t *Template) Execute(wr io.Writer, data interface{}) error func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error 基本示例
定义模板文件
我们按照Go模板语法定义一个index.html的模板文件,内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <h2>{{.}}</h2> <img src="./img/pyy.jpeg" style="width: 30%" alt="彭于晏"> </body> </html> 解析和渲染模板
创建一个main.go文件,服务端代码如下
package main import ( "html/template" "log" "net/http" ) func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } err = tmpl.Execute(w, "彭于晏") if err != nil { log.Fatal(err) } } func main() { //1.注册给定模式的处理器函数到DefaultServeMux files := http.FileServer(http.Dir("./public")) http.Handle("/", files) http.HandleFunc("/index", index) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux err := http.ListenAndServe("127.0.0.1:8080", nil) log.Fatal(err) } 项目结构如下:
. ├── go.mod ├── main.go ├── public ├── img │ └── pyy.jpeg └── index.html 将上面的main.go文件编译执行,然后使用浏览器访问http://127.0.0.1:8080就能看到页面上显示了“彭于晏”。 这就是一个最简单的模板渲染的示例,Go语言模板引擎详细用法请往下阅读。
{{.}}
模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。
当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
package main import ( "html/template" "log" "net/http" ) type Address struct { Province string City string } type User struct { Name string Age int Addr Address } func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } user := User{ Name: "彭于晏", Age: 28, Addr: Address{Province: "台湾省", City: "澎湖县"}, } //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, user) if err != nil { log.Fatal(err) } } func main() { //1.注册给定模式的处理器函数到DefaultServeMux files := http.FileServer(http.Dir("./public")) http.Handle("/", files) http.HandleFunc("/index", index) //2.设置监听的TCP地址并启动服务 //参数1:TCP地址(IP+Port) //参数2:当设置为nil时表示使用DefaultServeMux err := http.ListenAndServe("127.0.0.1:8080", nil) log.Fatal(err) } 模板文件index.html内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <p>姓名:{{.Name}}</p> <p>年龄:{{.Age}}</p> <p>地址:{{.Addr.Province}}-{{.Addr.City}}</p> <p>你好呀{{/* 这是注释 */}},彭于晏</p> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </body> </html> 显示效果如下图所示
同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。
注意:在模板中使用的变量应当是可导出的,否则的话对应的变量无法显示。
注释
//注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。 {{/* 这是注释 */}} pipeline
pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意:并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>姓名:{{.Name|len}}</p> <p>年龄:{{.Age}}</p> <p>地址:{{.Addr.Province}}-{{.Addr.City}}</p> <p>你好呀{{/* 这是注释 */}},彭于晏</p> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> 变量
我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
$obj := {{.}} 其中$obj是变量的名字,在后续的代码中就可以使用该变量了。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> {{$obj := .Name}} <p>姓名:{{ $obj }}</p> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> 移除空格
template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行。
有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。
注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>姓名:{{- .Name -}} -king</p> <!-- 彭于晏-king --> <p>姓名:{{ .Name }} -king</p> <!-- 彭于晏 -king --> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> if语句
Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}} {{if pipeline}} T1 {{else}} T0 {{end}} {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 需要注意的是,pipeline为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。
示例:
<!-- 由于.Name不是空字符串,所以可以渲染出彭于晏 --> <p>姓名:{{if .Name}} 彭于晏 {{end}}</p> range
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、map或者channel。
{{range pipeline}} T1 {{end}} 如果pipeline的值其长度为0,不会有任何输出 {{range pipeline}} T1 {{else}} T0 {{end}} 如果pipeline的值其长度为0,则会执行T0。 需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:
{{range $value := .}} {{range $key,$value := .}} 如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值(数组/切片是数值,map是key),第二个变量是当前正在迭代元素的值。
示例:
index.html文件
{{range $x := .}} <p>{{$x}}</p> {{else}} <p>这是一个空切片</p> {{end}} 也可以这样
{{range .}} <p>{{.}}</p> {{else}} <p>这是一个空切片</p> {{end}} main.go文件
func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } var arr []int //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, arr) if err != nil { log.Fatal(err) } } with
{{with pipeline}} T1 {{end}} 如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。 {{with pipeline}} T1 {{else}} T0 {{end}} 如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。 对于第一种格式,当pipeline不为0值的时候,点"."设置为pipeline运算的值,否则跳过。对于第二种格式,当pipeline为0值时,执行else语句块,否则"."设置为pipeline运算的值,并执行T1。
示例:
<p>{{with "彭于晏"}} {{.}} {{end}}</p> 上面将输出彭于晏,因为"."已经设置为"彭于晏"。
比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
eq 如果arg1 == arg2则返回真 ne 如果arg1 != arg2则返回真 lt 如果arg1 < arg2则返回真 le 如果arg1 <= arg2则返回真 gt 如果arg1 > arg2则返回真 ge 如果arg1 >= arg2则返回真 为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
{{eq arg1 arg2 arg3}} 等价于
arg1==arg2 || arg1==arg3 比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。
示例:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> {{if (eq .Name "彭于晏") }} <p>彭于晏</p> {{else}} <p>晏晏</p> {{end}} <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> 预定义函数
预定义的全局函数如下:
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,模板执行会中断并返回给调用模板执行者该错误; 示例:
<div align="center"> <!-- if (.Name == '彭于晏' && .Age == 28) --> {{if (and (eq .Name "彭于晏") (eq .Age 28)) }} <p>彭于晏</p> {{else}} <p>晏晏</p> {{end}} </div> 自定义函数
Go的模板支持自定义函数。需要注意的是自定义函数必须在解析模板之前。
main.go文件
func index(w http.ResponseWriter, r *http.Request) { fn := func(name string) string { return name + ",你好!" } //自定义函数必须在解析模板之前 tmpl := template.New("index.html").Funcs(template.FuncMap{"hello": fn}) //解析指定文件生成模板对象 tmpl, err := tmpl.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } var user = User{Name: "彭于晏", Age: 28} //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, user) if err != nil { log.Fatal(err) } } index.html文件
<p>{{hello .Name}}</p> 嵌套template
我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。
举个例子: index.html文件内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>嵌套template示例</p> <br> {{template "ul.html"}} <br> {{template "ol.tmpl"}} <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> {{define "ol.tmpl"}} <ol> <li>彭于晏</li> <li>高圆圆</li> <li>赵又廷</li> </ol> {{end}} ul.html文件内容如下:
<ul> <li>彭于晏</li> <li>高圆圆</li> <li>赵又廷</li> </ul> main.go文件
func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html", "./public/ul.html") if err != nil { log.Fatal(err) } var user = User{Name: "彭于晏", Age: 28} //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, user) if err != nil { log.Fatal(err) } } 注意:在解析模板时,被嵌套的模板一定要在后面解析,例如上面的示例中index.html模板中嵌套了ul.html,所以ul.html要在index.html后进行解析。
block
根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
{{block "name" pipeline}} T1 {{end}} 但应该注意,block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板。
例如:
{{block "T1" .}} one {{end}} 它首先表示{{template "T1" .}},也就是说先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个{{define "T1"}} one {{end}},并执行它。
示例:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>block用法示例</p> {{block "content" .}} <p style="color: blue;">彭于晏</p> {{end}} <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> red.html
{{define "content"}} <p style="color: red;">彭于晏</p> {{end}} main.go
func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 var tmpl *template.Template var err error if rand.Intn(2) > 0 { tmpl, err = template.ParseFiles("./public/index.html", "./public/red.html") } else { tmpl, err = template.ParseFiles("./public/index.html") } if err != nil { log.Fatal(err) } //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, nil) if err != nil { log.Fatal(err) } } 这样示例的效果就是刷新页面,彭于晏的名字颜色在红蓝之间反复变化
标识符
Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:
template.New("index.html").Delims("{[", "]}").ParseFiles("./public/index.html") 上下文感知
对于html/template包,有一个很好用的功能:上下文感知。text/template没有该功能。
上下文感知具体指的是根据所处环境css、js、html、url的path、url的query,自动进行不同格式的转义。
例如,一个handler函数的代码如下:
func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } str := `I asked: <i>"What's up?"</i>` //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, str) if err != nil { log.Fatal(err) } } 上面str是Execute的第二个参数,它的内容是包含了特殊符号的字符串。
下面是index.html文件的内容:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>html/template上下文感知示例</p> <div>{{ . }}</div> <div><a href="/{{ . }}">Path</a></div> <div><a href="/?q={{ . }}">Query</a></div> <div><a onclick="f('{{ . }}')">Onclick</a></div> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> 上面index.html中有4个不同的环境,分别是html环境、url的path环境、url的query环境以及js环境。虽然对象都是{{.}},但解析执行后的值是不一样的。如果使用curl获取源代码,结果将如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>html/template上下文感知示例</p> <div>I asked: <i>"What's up?"</i></div> <div><a href="/I%20asked:%20%3ci%3e%22What%27s%20up?%22%3c/i%3e">Path</a></div> <div><a href="/?q=I%20asked%3a%20%3ci%3e%22What%27s%20up%3f%22%3c%2fi%3e">Query</a></div> <div><a onclick="f('I asked: \u003ci\u003e\u0022What\u0027s up?\u0022\u003c\/i\u003e')">Onclick</a></div> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
例如,定义下面的模板文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index页面</title> </head> <body> <div align="center"> <p>html/template上下文感知示例</p> {{ . }} <br> <img src="./img/pyy.jpeg" style="width: 10%" alt="彭于晏"> </div> </body> </html> handler控制器代码如下:
func index(w http.ResponseWriter, r *http.Request) { //解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./public/index.html") if err != nil { log.Fatal(err) } str := `<script>alert('嘿嘿嘿')</script>` //利用给定数据渲染模板,并将结果写入 err = tmpl.Execute(w, str) if err != nil { log.Fatal(err) } } 运行代码显示如下
如果我们将包由html/template改为text/template,重新运行代码,可以发现被注入了js代码
客户端
http包提供了很多访问Web服务器的函数,比如http.Get()、http.Post()、http.Head()等,读到的响应报文数据被保存在 Response 结构体中。
我们通过查看http.Get()方法的源码可知,实际上是默认的DefaultClient调用自己的Get方法。
func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) } 真正用来发送请求的Client结构体定义如下:
type Client struct { // Transport指定执行独立、单次HTTP请求的机制。 // 如果Transport为nil,则使用DefaultTransport。 Transport RoundTripper // CheckRedirect指定处理重定向的策略。 // 如果CheckRedirect不为nil,客户端会在执行重定向之前调用本函数字段。 // 参数req和via是将要执行的请求和已经执行的请求(切片,越新的请求越靠后)。 // 如果CheckRedirect返回一个错误,本类型的Get方法不会发送请求req, // 而是返回之前得到的最后一个回复和该错误。(包装进url.Error类型里) // // 如果CheckRedirect为nil,会采用默认策略:连续10此请求后停止。 CheckRedirect func(req *Request, via []*Request) error // Jar指定cookie管理器。 // 如果Jar为nil,请求中不会发送cookie,响应的cookie会被忽略。 Jar CookieJar // Timeout指定本类型的值执行请求的时间限制。 // 该超时限制包括连接时间、重定向和读取回复主体的时间。 // 计时器会在Head、Get、Post或Do方法返回后继续运作并在超时后中断回复主体的读取。 // // Timeout为零值表示不设置超时。 // // Client实例的Transport字段必须支持CancelRequest方法, // 否则Client会在试图用Head、Get、Post或Do方法执行请求时返回错误。 // 本类型的Transport字段默认值(DefaultTransport)支持CancelRequest方法。 Timeout time.Duration } 基本的HTTP/HTTPS请求
Get、Head、Post和PostForm函数发出HTTP/HTTPS请求。
resp, err := http.Get("http://example.com/") resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}}) 程序在使用完response后必须关闭回复的主体。
resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) GET请求示例
使用net/http包编写一个简单的发送HTTP请求的Client端,代码如下:
package main import ( "fmt" "io" "log" "net/http" ) func main() { resp, err := http.Get("http://127.0.0.1:8080") if err != nil { log.Fatalf("http.Get()函数执行错误,错误为:%v\n", err) } defer resp.Body.Close() //由于ioutil包的歧义性,1.16版本之后不再建议使用ioutil包,包内方法的具体实现都改为了调用的其它包方法 //ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body) if err != nil { log.Fatalf("io.RedAll()函数执行出错,错误为:%v\n", err) } fmt.Println(string(body)) } 服务端代码如下:
package main import ( "log" "net/http" ) func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello world")) } func main() { http.HandleFunc("/", index) err := http.ListenAndServe(":8080", nil) log.Fatal(err) } 将上面的代码保存之后编译成可执行文件,执行之后就能在终端打印hello world网站首页的内容了,我们的浏览器其实就是一个发送和接收HTTP协议数据的客户端,我们平时通过浏览器访问网页其实就是从网站的服务器接收HTTP数据,然后浏览器会按照HTML、CSS等规则将网页渲染展示出来。
带参数的GET请求示例
关于GET请求的参数需要使用Go语言内置的net/url这个标准库来处理。
package main import ( "fmt" "io" "log" "net/http" "net/url" ) func main() { //1.处理请求参数 params := url.Values{} params.Set("name", "itbsl") params.Set("hobby", "fishing") //2.设置请求URL rawUrl := "http://127.0.0.1:8080" reqUrl, err := url.ParseRequestURI(rawUrl) if err != nil { fmt.Printf("url.ParseRequestURI()函数执行错误,错误为:%v\n", err) return } //3.整合请求URL和参数 //Encode方法将请求参数编码为url编码格式("bar=baz&foo=foz"),编码时会以键进行排序。 reqUrl.RawQuery = params.Encode() //4.发送HTTP请求 //说明:reqURL.String() String将URL重构为一个合法URL字符串。 resp, err := http.Get(reqUrl.String()) if err != nil { log.Fatalf("http.Get()函数执行错误,错误为:%v\n", err) } defer resp.Body.Close() //5.一次性读取响应的所有内容 body, err := io.ReadAll(resp.Body) if err != nil { log.Fatalf("io.RedAll()函数执行出错,错误为:%v\n", err) } fmt.Println(string(body)) } 对应的Server端代码如下:
package main import ( "fmt" "log" "net/http" ) func index(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := r.URL.Query() fmt.Printf("name: %v, hobby: %v\n", params.Get("name"), params.Get("nobody")) w.Write([]byte("hello world")) } func main() { http.HandleFunc("/", index) err := http.ListenAndServe(":8080", nil) log.Fatal(err) } POST请求示例
面演示了使用net/http包发送GET请求的示例,发送POST请求的示例代码如下:
package main import ( "fmt" "io" "net/http" "strings" ) func main() { url := "http://127.0.0.1:8080" // 表单数据 //contentType := "application/x-www-form-urlencoded" //data := "name=itbsl&age=18" // json contentType := "application/json" data := `{"name":"itbsl","age":18}` resp, err := http.Post(url, contentType, strings.NewReader(data)) if err != nil { fmt.Printf("post failed, err:%v\n", err) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("get resp failed,err:%v\n", err) return } fmt.Println(string(body)) } 对应的Server端HandlerFunc如下:
func index(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // 1. 请求类型是application/x-www-form-urlencoded时解析form数据 //r.ParseForm() //fmt.Println(r.PostForm) // 打印form数据 //fmt.Println(r.PostForm.Get("name"), r.PostForm.Get("age")) // 2. 请求类型是application/json时从r.Body读取数据 body, err := io.ReadAll(r.Body) if err != nil { fmt.Printf("read request.Body failed, err:%v\n", err) return } fmt.Println(string(body)) answer := `{"status": "ok"}` w.Write([]byte(answer)) } 自定义Client
要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client:
package main import ( "fmt" "io" "log" "net/http" ) func main() { client := &http.Client{} req, err := http.NewRequest("GET", "http://127.0.0.1:8080", nil) if err != nil { log.Fatal(err) } req.Header.Add("sign", "sign_aes") resp, err := client.Do(req) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) }
版权声明:
作者: FreeClashNode
链接: https://www.freeclashnode.com/news/article-1289.htm
来源: www.freeclashnode.com
文章版权归作者所有,未经允许请勿转载。
热门文章
- 10月1日免费节点分享|18M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费上网梯子每天更新
- 10月2日免费节点分享|22.8M/S,Singbox节点/Shadowrocket节点/Clash节点/V2ray节点|免费上网梯子每天更新
- 10月3日免费节点分享|20.1M/S,SSR节点/Singbox节点/V2ray节点/Clash节点|免费上网梯子每天更新
- 9月30日免费节点分享|19.1M/S,Clash节点/V2ray节点/SSR节点/Singbox节点|免费上网梯子每天更新
- 10月6日免费节点分享|19.2M/S,V2ray节点/Singbox节点/Shadowrocket节点/Clash节点|免费上网梯子每天更新
- 10月8日免费节点分享|22.5M/S,V2ray节点/Singbox节点/Clash节点/Shadowrocket节点|免费上网梯子每天更新
- 10月5日免费节点分享|19.9M/S,Singbox节点/Clash节点/V2ray节点/SSR节点|免费上网梯子每天更新
- 10月11日免费节点分享|18.6M/S,V2ray节点/Clash节点/SSR节点/Singbox节点|免费上网梯子每天更新
- 10月14日免费节点分享|22.5M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费上网梯子每天更新
- 10月7日免费节点分享|23M/S,Singbox节点/Clash节点/Shadowrocket节点/V2ray节点|免费上网梯子每天更新
最新文章
- 10月24日免费节点分享|20.5M/S,V2ray节点/Clash节点/Singbox节点/Shadowrocket节点|免费上网梯子每天更新
- 刘大猫科学上网全攻略:解锁全球网络的终极指南
- 10月23日免费节点分享|22.2M/S,Shadowrocket节点/Clash节点/Singbox节点/V2ray节点|免费上网梯子每天更新
- 全面解析Quantumult安卓版:功能、安装与配置教程
- 双重防护,畅享无界:AdGuard联手V2RayNG,打造极致安全的上网体验
- 10月22日免费节点分享|18.8M/S,V2ray节点/SSR节点/Singbox节点/Clash节点|免费上网梯子每天更新
- 掌握Clash白名单设置:精准控制代理流量的艺术
- 10月21日免费节点分享|22M/S,Clash节点/Singbox节点/V2ray节点/SSR节点|免费上网梯子每天更新
- 突破网络边界:技术科学上网的终极安全指南
- 10月20日免费节点分享|22.9M/S,SSR节点/V2ray节点/Clash节点/Singbox节点|免费上网梯子每天更新
- Shadowrocket 选购指南:从入门到精通,找到你的专属代理神器
- 10月19日免费节点分享|19.3M/S,Singbox节点/V2ray节点/Clash节点/Shadowrocket节点|免费上网梯子每天更新
- 深入解读:V2Ray为何比SSR更安全?一场网络隐私的全方位较量
- 10月18日免费节点分享|20.6M/S,V2ray节点/Clash节点/SSR节点/Singbox节点|免费上网梯子每天更新
- 深度指南:Shadowrocket从入门到精通——iOS用户的网络自由密钥
- 10月17日免费节点分享|18M/S,Clash节点/SSR节点/V2ray节点/Singbox节点|免费上网梯子每天更新
- 深入解析科学上网套:全方位使用指南与常见问题解答
- 10月16日免费节点分享|22M/S,Shadowrocket节点/V2ray节点/Singbox节点/Clash节点|免费上网梯子每天更新
- 10月15日免费节点分享|21.2M/S,Clash节点/SSR节点/V2ray节点/Singbox节点|免费上网梯子每天更新
- 全局掌控:安卓设备上 Clash 全局模式完整设置指南与深度解析
- 解锁全球游戏体验:育碧平台科学上网全攻略与深度解析
- 10月14日免费节点分享|22.5M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费上网梯子每天更新
- 华为Mate 10科学上网全攻略:突破限制,畅游全球网络
- 10月13日免费节点分享|22.1M/S,V2ray节点/Clash节点/Singbox节点/Shadowrocket节点|免费上网梯子每天更新
- 10月12日免费节点分享|22.7M/S,Shadowrocket节点/V2ray节点/Singbox节点/Clash节点|免费上网梯子每天更新
- 量子ULT全方位指南:从下载配置到高阶玩法一网打尽
- 10月11日免费节点分享|18.6M/S,V2ray节点/Clash节点/SSR节点/Singbox节点|免费上网梯子每天更新
- 极路由V2ray插件完全指南:打造家庭网络的安全屏障
- 10月10日免费节点分享|18.3M/S,V2ray节点/Singbox节点/Clash节点/SSR节点|免费上网梯子每天更新
- 极速狂飙与正义追击:《警车 Clash》深度解析与高阶攻略
- 10月9日免费节点分享|22.3M/S,Singbox节点/Clash节点/Shadowrocket节点/V2ray节点|免费上网梯子每天更新
- 用hosts文件科学上网:一份从入门到实战的详细指南
- 10月8日免费节点分享|22.5M/S,V2ray节点/Singbox节点/Clash节点/Shadowrocket节点|免费上网梯子每天更新
- iOS设备上SSR科学上网全教程:从入门到精通
- 10月7日免费节点分享|23M/S,Singbox节点/Clash节点/Shadowrocket节点/V2ray节点|免费上网梯子每天更新
- 深度剖析V2Ray订阅机制:从零搭建高效、安全的科学上网环境
- 10月6日免费节点分享|19.2M/S,V2ray节点/Singbox节点/Shadowrocket节点/Clash节点|免费上网梯子每天更新
- 全面解析蚕蛹生态冲突及其多元价值
- 10月5日免费节点分享|19.9M/S,Singbox节点/Clash节点/V2ray节点/SSR节点|免费上网梯子每天更新
- 从零开始:手把手教你刷梅林固件实现安全科学上网
- 10月4日免费节点分享|20M/S,SSR节点/V2ray节点/Clash节点/Singbox节点|免费上网梯子每天更新
- 跨越边界,自由探索:一加科学上网全面指南与深度解析
- 10月3日免费节点分享|20.1M/S,SSR节点/Singbox节点/V2ray节点/Clash节点|免费上网梯子每天更新
- 10月2日免费节点分享|22.8M/S,Singbox节点/Shadowrocket节点/Clash节点/V2ray节点|免费上网梯子每天更新
- 斐讯K2路由器刷机全攻略:解锁科学上网新境界
- 解锁网络自由:B站d手机科学上网全攻略
- 10月1日免费节点分享|18M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费上网梯子每天更新
- 洞悉网络脉络:一份关于Vmess节点测试的全景式实践手记
- 9月30日免费节点分享|19.1M/S,Clash节点/V2ray节点/SSR节点/Singbox节点|免费上网梯子每天更新
- 饭强浏览器:科学上网的最佳工具,还是被神化的上网利器?一篇深度体验与冷静点评
归档
- 2026-10 46
- 2026-09 58
- 2026-08 63
- 2026-07 61
- 2026-06 55
- 2026-05 56
- 2026-04 51
- 2026-03 60
- 2026-02 52
- 2026-01 56
- 2025-12 59
- 2025-11 55
- 2025-10 56
- 2025-09 55
- 2025-08 49
- 2025-07 31
- 2025-06 30
- 2025-05 31
- 2025-04 31
- 2025-03 383
- 2025-02 360
- 2025-01 403
- 2024-12 403
- 2024-11 390
- 2024-10 403
- 2024-09 388
- 2024-08 402
- 2024-07 424
- 2024-06 446
- 2024-05 184
- 2024-04 33
- 2024-03 32
- 2024-02 29
- 2024-01 50
- 2023-12 53
- 2023-11 32
- 2023-10 32
- 2023-09 3