前言
Web是基于HTTP协议进行交互的应用网络,通过使用浏览器/APP访问各种资源
1 基础知识
1.1 简易web程序
- 创建好项目的空文件之后,初始化一个模块
go mod init github.com/solenovex/web
/Users/qinjianquan/goweb/go.mod //在go mod 文件中查看
module github.com/solenovex/web //初始化时声明的模块
go 1.17
2.创建一个简单的web应用程序
/Users/qinjianquan/goweb/main.go
func main() {
//1.注册一个函数,让其可以对web请求进行响应
//处理web请求,我们一般用http包的下列函数,第一个参数相当于路由地址,"/"是根地址,表示响应所有的请求
//第二个参数是个函数,一个是接口:用来写响应,一个是结构体的指针:包括传入请求的所有信息
//这个函数是个回调函数,当请求到达时,函数就会执行(函数本身是参数)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
//接口w有write方法,所以可以调用
w.Write([]byte("hello world"))
})
//2.设置web服务器,启动这个server
//监听访问localhost:8080的请求,第二个参数默认调用Handler接口里面的方法(可以理解为一个路由器)
//意思就是收到请求后,将所访问的路径分别匹配
http.ListenAndServe("localhost:8080", nil) //DefaultServeMux
}
3.运行
go run main.go //终端运行
http://localhost:8080/ //web访问
hello world //结果页面
1.2 Handle请求
1.2.1 创建webserver的两种方式
在web编程中,处理请求的单词有两个Handle和Process,前者更多指的是响应,后者更多的是指处理过程
go语言使用Handler来接受web请求,每接收到一个请求,go语言就会创建一个goroutine,具体而言是由DefaultServeMux来完成的
go语言中创建web server的方式有很多中,使用http.ListenAndServe()是其中的一种,当第一个参数为空时,就是所有网络接口的80端口,第二个参数如果是nil,那就是DefaultServeMux
func main() {
//第一种方式
//http.ListenAndServe("localhost:8080", nil)
//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
server.ListenAndServe()
}
但是上述web应用只能走http,不能走https,要走要使用配套的两个函数,我们后面再讲
server.ListenAndServeTLS()
http.ListenAndServeTLS()
1.2.2 Handler
handler
handler 是一个接口,定义如下,也就是说任何类型,只要实现了ServeHTTP(w ResponseWriter, r *Request)这个方法,它就是一个handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
var defaultServeMux ServeMux //DefaultServeMux其实也是一个handler
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //它也是实现了ServeHTTP(w ResponseWriter, r *Request)这个方法
实现一个自定义的handler,所有请求都用打印“hello world”来处理
type myHandler struct{}
func (mh *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func main() {
mh := myHandler{}
//第一种方式
//http.ListenAndServe("localhost:8080", nil)
//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点
server := http.Server{
Addr: "localhost:8080",
Handler: &mh,
}
server.ListenAndServe()
}
Handler函数 - http.Handle
handler函数指的是行为与handler类似的函数,也就是函数签名和ServeHTTP方法的签名一样
事实上,在一个web应用中,使用多个handler对访问不同路径的请求予以处理,才是合理的操作
如何实现这一点?
首先不指定Server struct里面的Handler 字段值,这样就可以使用DefaultServeMux
接着使用http.Handle将某个Handler附加到DefaultServeMux
DefaultServeMux是ServeMux的指针变量,ServeMux有一个Handle方法,http包还有一个Handle函数,调用它实际上就是调用的是Handle方法
func Handle(pattern string, handler Handler)
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
先让http.Serve参数中handler 为nil
接着使用http.Handle向DefaultServeMux注册
//先自定义一个类型
type helloHandler struct{}
//为它实现ServeHTTP方法后就会将它变成一个handler
func (mh *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
//再自定义一个类型
type aboutHandler struct{}
//同样的,实现ServeHTTP方法
func (mh *aboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("About!"))
}
func main() {
mh := helloHandler{} //初始化变量
a := aboutHandler{} //初始化变量
//第一种方式
//http.ListenAndServe("localhost:8080", nil)
//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
//向DefaultServeMux注册,此时再过来符合条件的请求就会经由DefaultServeMux分发给该注册过的Handle
//其实就是把我们自定义的handler加入到DefaultServeMux分配的备选handler集合之中
http.Handle("/hello", &mh) //注册
http.Handle("/about", &a) //同上也是注册
server.ListenAndServe()
}
现在我们就能在浏览器访问指定的路径(http://localhost:8080/about 和 http://localhost:8080/hello),并可以获取写入的内容
Handler函数 - http.HandleFunc
http.HandleFunc 原理:它是一个函数类型,它可以将某个具有适当签名的函数f适配成一个Handler,而这个Handler具有方法f
//先自定义一个类型
type helloHandler struct{}
//为它实现ServeHTTP方法后就会将它变成一个handler
func (mh *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
//再自定义一个类型
type aboutHandler struct{}
//同样的,实现ServeHTTP方法
func (mh *aboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("About!"))
}
func welcome(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome!"))
}
func main() {
mh := helloHandler{} //初始化变量
a := aboutHandler{} //初始化变量
//第一种方式
//http.ListenAndServe("localhost:8080", nil)
//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
//1.使用Handle向DefaultServeMux注册,此时再过来符合条件的请求就会经由DefaultServeMux分发给该注册过的Handle
//其实就是把我们自定义的handler加入到DefaultServeMux分配的备选handler集合之中
http.Handle("/hello", &mh) //注册
http.Handle("/about", &a) //同上也是注册
//2.使用HandleFunc向DefaultServeMux注册
//在签名中定义func
http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Home!"))
})
//在外面定义,直接传函数名
http.HandleFunc("/welcome", welcome)
//3.也可以使用http.HandlerFunc()将函数转换为为一个handler,其实就是将函数名称变为ServeHTTP
//type HandlerFunc func(ResponseWriter, *Request)
//
ServeHTTP calls f(w, r).
//func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
// f(w, r)
//}
http.Handle("/welcome", http.HandlerFunc(welcome))
server.ListenAndServe()
}
为什么要使用两种注册方式呢?因为我们看到这样就不用修改原来的函数了,直接把它转换为接口里面的方法
1.3 内置的handlers
Go语言可以自定义handler以及将现有符合条件的函数转换为handler来处里访问特定路径的web请求。另外Go语言也内置了五种类型的handler
func main() {
//1. func NotFoundHandler() Handler,给每个请求都返回 "404 page not found"
http.NotFoundHandler()
//2. func RedirectHandler(url string, code int) Handler,将要访问的url页面通过跳转码跳转到另一个url页面
//常见的有StatusMovedPermanently, StatusFound or StatusSeeOther,分别是301,302,303
http.RedirectHandler("/welcome", 301)
//3. func StripPrefix(prefix string, h Handler) Handler,将请求的url去掉指定的前缀,然后调用另一个handler,如果不符合,则404
//有点像中间件,修饰了另外一个handler
http.StripPrefix("/welcome", http.NotFoundHandler())
//4.func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
//time.Duration 实际上是int64的一个别名,这个handler也相当于一个修饰器
http.TimeoutHandler(http.NotFoundHandler(), 3, "timeout")
//5. func FileServer(root FileSystem) Handler,参数是一个接口,得传入一个实现了接口的类型
http.FileServer(http.Dir("/welcome"))
}
使用web server测试http.FileServer(),先准备一份html和css文件放入独立文件夹但与main函数放在同一目录下
func main() {
//第一种方式
//http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// http.ServeFile(w, r, "index"+r.URL.Path)
//})
//
//http.ListenAndServe(":8080", nil)
//第二种方式,使用 http.FileServer()
http.ListenAndServe(":8080", http.FileServer(http.Dir("index")))
}
1.4 request
本节讲述对http请求的处理过程
1.4.1 HTTP消息
HTTP消息
HTTP Request和HTTP Response,结构相同
请求(响应)行
0个或者多个header
空行
可选的消息体
1.请求行
格式:
请求方式 请求资源 请求协议/版本
GET 虚拟路径/login.html HTTP/1.1
请求方式:
HTTP中目前有9种请求方式,常用的有2种
GET:
1.请求参数在请求行中,在URL后面
2.请求的url长度有限制的
3.不太安全
POST:
1.请求参数封装在请求体中
2.请求的url长度没有限制
3.相对安全
2.请求头
这部分的作用就是浏览器告诉服务器自身的一些信息
Host:表示请求的主机
User-Agent:浏览器告诉服务器我访问你使用的浏览器版本信息
可以在服务器端获取该头的信息,解决浏览器的兼容性问题
Referer:告诉服务器我(当前的请求)从哪 里来的
用来防盗链和做统计工作
3.请求空行
就是用于分割POST请求的请求头和请求体的
4.请求体(真正的的内容所在)
封装POST请求消息的请求参数的
Request
go语言中,net/http包提供了用于表示HTTP消息的结构
type Request struct {
//几个重要的字段
URL //通用形式:scheme://[userinfo@]host/path[?query][#fragment] //[?query]查询字符串[#fragment]
Header
Body
Form,PsotForm,MultipartForm
...
}
另外,还可以使用Request的一些方法访问请求中的cookie,URL,User Agent等信息
request既可以用在服务端,也可以用在客户端
URL Fragment
URL Fragment
如果请求从浏览器发出,则无法从URL中提取Fragment字段值,因为浏览器会在发送时去掉它
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.URL.Fragment)
})
server.ListenAndServe()
}
访问http://localhost:8080/url#fragement无返回值
curl localhost:8080/url#frag //使用curl也无返回值
//curl 是一种命令行工具,作用是发出网络请求,然后获取数据,显示在"标准输出"(stdout)上面
Request Header
Request Header
header 是一个map,用来表述HTTP Header 里面的Key-Value对
key string,value []string,value里面第一个元素就是新的header值
使用append为指定的key增加一个header值
获取header
r.Header 返回的是map
r.Header["Accept-Encoding"] //返回的是[]string
r.Header.Get("Accept-Encoding") //返回的是string,使用逗号隔开
func main() {
server := http.Server{
Addr: "localhost:8080",
}
//http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintln(w, r.URL.Fragment)
//})
http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
//fmt.Fprintln(w, r.URL.Fragment)
fmt.Fprintln(w, r.Header)
fmt.Fprintln(w, r.Header["Accept-Encoding"])
fmt.Fprintln(w, r.Header.Get("Accept-Encoding"))
})
server.ListenAndServe()
}
不同方式获取header得到的结果,如下是使用vs code插件 REST Client发送的请求
map[Accept-Encoding:[gzip, deflate] Connection:[close] User-Agent:[vscode-restclient]]
[gzip, deflate]
gzip, deflate
Request Body
获取body得到的结果,如下是使用vs code插件 REST Client发送的请求
Request Body
Body io.ReadCloser body类型是一个接口,接口里面也是两个接口,使用Read方法读取body内容
type ReadCloser interface {
Reader
Closer
}
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
HTTP/1.1 200 OK
Date: Thu, 21 Apr 2022 05:30:07 GMT
Content-Length: 70
Content-Type: text/plain; charset=utf-8
Connection: close
{
"name": "Sample",
"time": "Wed,21 Oct 2015 18:27:50 UTC"
}
URL Query
URL Query
RawQuery 会提供实际查询的字符串,在问号后面表示,有两对值,如:id=123&thread_id=456
例如:http://www.example.com/post?id=123&thread_id=456
还可以通过Request的Form字段得到key-value对
http://www.example.com/post?id=123&thread_id=456
在URL中使用查询字符串向后端发送数据,这种通常是GET请求
r.URL.RawQuery 会提供实际查询的原始字符串,如id=123&thread_id=456
r.URL.Query会提供查询字符串对应的map[string][]string
func main() {
server := http.Server{
Addr: "localhost:8080",
}
//1.#fragment
//http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintln(w, r.URL.Fragment)
//})
//2.Header
// http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
// //fmt.Fprintln(w, r.URL.Fragment)
// fmt.Fprintln(w, r.Header)
// fmt.Fprintln(w, r.Header["Accept-Encoding"])
// fmt.Fprintln(w, r.Header.Get("Accept-Encoding"))
// })
//3.Body
//http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
// length := r.ContentLength //获取内容的长度
// body := make([]byte, length)
// r.Body.Read(body) //获取body, 并将其读到上述bytes中
//
// fmt.Fprintln(w, string(body))
//})
//4.URL
http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
url := r.URL
query := url.Query() //返回的是一个map
fmt.Println(query)
id := query["id"] //返回的是id []string
log.Println(id)
name := query.Get("name") //返回的是[]string中的第一个元素
log.Println(name)
})
server.ListenAndServe()
}
http://localhost:8080/home?id=123&name=Dave&id=456&name=Nick
1.5 Form
1. 使用表单发送POST请求
HTML表单
<form action = "/process" method = "post"> //action对应的是处理的路径,method 对应的是请求类型
<input type = "text" name="first_name"/> //input 是输入框
<input type = "text" name="last_name"/> //输入的数据是以name-value对形式存放的,name是现有值,value是输入值
<input type = "sumit"/> //当我们点击提交按钮之后,这个HTML表单里面的数据(两对数据)就会以name-value对的形式以post请求发送出去
//数据内容放在POST请求的Body里面
</form>
name-value对在Body里面的格式
通过POST发送的name-value数据对的格式可以通过表单的Content Type来指定,也就是enctype属性
enctype 属性的默认值:application/x-www-form-urlencoded
<form action = "/process" method = "post" enctype="application/x-www-form-urlencoded"> //action对应的是处理的路径,method 对应的是请求类型
<input type = "text" name="first_name"/> //input 是输入框
<