📅 2024年6月18日
📦 使用版本为 1.21.5
ps: 临近期末各种大作业期末考试课设蜂拥而致,在忙完一部分后,准备开始学习Go的后端以便后续提升
HTTP 库
⭐️ 在 go
中 net/http
是一个十分优秀的标准库,提供非常完善的HTTP客户端与服务端的实习,只需要通过几行代码就可以搭建一个HTTP服务器,甚至几乎所有 go
的 web
框架,都是它的封装
🌟发起简单请求
1️⃣ Get
直接使用 http.Get
方法去请求,返回一个 Response
对象和错误,这个对象中包含了响应的状态码、头部信息以及响应体(Body
)。
func main() {
//创建Get请求
r, err := http.Get("https://baidu.com")
if err != nil {
fmt.Println(err)
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
fmt.Println(string(body))
}
和打开文件一样需要关闭,只不过需要执行 r.Body.Close()
关闭,因为 r.Body
是一个 io.ReadCloser
接口,它实现了 io.Reader
和 io.Closer
两个接口。io.Reader
用于读取数据,而 io.Closer
则用于关闭底层的资源
//Response对象
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
Header Header
Body io.ReadCloser //Body
.....
}
另一方面,r.Close()
并不存在,因为 *http.Response
类型并没有定义一个 Close
方法。http.Response
只定义了 Body
属性,你需要调用 r.Body.Close()
来关闭它
//Body
type ReadCloser interface {
Reader
Closer
}
2️⃣ Post
⭐️ 和 Get
使用方法一样,只不过http.post需要接收另外两个参数,一个是请求格式,还有是请求数据
// Data 定义结构体
type Data struct {
username string
password string
}
func main() {
//Post示例
//请求数据
data := Data{
username: "XXX",
password: "XXX",
}
//转换为json格式
json, _ := json2.Marshal(data)
//读取json格式,bytes包中实现了io.Reader接口
jsonReader := bytes.NewReader(json)
r, err := http.Post("https://XXXXXX/loginUser", "application/json", jsonReader)
if err != nil {
fmt.Println(err)
}
Body, _ := io.ReadAll(r.Body)
fmt.Println(string(Body))
defer r.Body.Close()
}
3️⃣ Put
⭐️ 使用 Put
方法和之前两种方法不一样,应为 http
包中并没有直接提供一个 Put
的方法
⭐️ 先查看一下 Post
方法在内部是如何实现的,是调用了 DefaultClient
的 Post
方法,DefaultClient
相当于创建了一个客服端,发起请求
func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
查看 DefaultClient
的 Post
方法,这样就一目了然,调用了 NewRequest
创建了一个新的请求,然后通过 Do
方法发送数据,如果你不理解 Do
,你就可以理解为你在浏览器中输入网址之后的那个回车
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
⭐️ 然后就可以照猫画虎的写了
func Put() {
//Put示例
//新建一个请求
r, _ := http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil)
//将请求通过Client的Do方法发送出去
put, err := http.DefaultClient.Do(r)
if err != nil {
fmt.Println(err)
}
defer put.Body.Close()
Body, err := io.ReadAll(put.Body)
fmt.Println(string(Body))
}
之后还有 Delete
等等一些请求也可以使用Go来实现
4️⃣ delete方法
⭐️ 和put一样
func Delete() {
//新建一个请求
r, _ := http.NewRequest(http.MethodDelete, "http://httpbin.org/delete", nil)
//将请求通过Client的Do方法发送出去
put, err := http.DefaultClient.Do(r)
if err != nil {
fmt.Println(err)
}
defer put.Body.Close()
Body, err := io.ReadAll(put.Body)
fmt.Println(string(Body))
}
其实所有方法都可以向这样实现,但是
http
库为了方便快捷就提供了Post
和Get
请求
🌟 请求设置
⭐️ 以上几种请求主要是由三个结构体构成分别是 client
、request
和 response
1️⃣ request
⭐️ request包含参数和请求头
⭐️ 请求参数使用 url.Values
来构建,然后使用 URL.RawQuery
来添加到请求后
⚠️ 这里的
printBody
是封装好的一个输出请求体的内容func printBody(r *http.Response) { Body, err := io.ReadAll(r.Body) if err != nil { fmt.Println(err) } defer r.Body.Close() fmt.Printf("%s", Body) }
func Params() {
request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil {
fmt.Println(err)
}
//url.Values 类型可以用来组织Get请求的参数,它是一个map String类型的别名
params := make(url.Values)
//这里就相当于get?name=tanc&password=123455
params.Add("name", "tanc")
params.Add("password", "123455")
request.URL.RawQuery = params.Encode()
//发起请求
r, err := http.DefaultClient.Do(request)
if err != nil {
fmt.Println(err)
}
//打印Body
printBody(r)
}
⭐️ 添加请求头可以通过 Header.Add
来直接添加
func Header() {
request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil {
fmt.Println(err)
}
//添加请求头,可以通过User-agent进行反爬虫
request.Header.Add("user-agent", "chrome")
//发起请求
r, err := http.DefaultClient.Do(request)
if err != nil {
fmt.Println(err)
}
//打印Body
printBody(r)
}
2️⃣ response
⭐️ response是响应,响应也有响应体、响应头,还有状态码、编码
⭐️ 请求和之前一样
func main() {
r, err := http.Get("http://httpbin.org/get")
if err != nil {
fmt.Println(err)
}
defer func() { _ = r.Body.Close() }()
body(r)
status(r)
header(r)
}
⭐️ 响应体使用 io.ReadAll
来读取,在处理 HTTP
响应体时,使用 io.ReadAll
可以方便地将整个响应体读取到内存中,然后进行进一步的处理,例如解析 JSON
数据、提取文本内容等。这样可以避免手动处理数据的读取和拼接,提高代码的简洁性和可读性
// 响应体
func body(r *http.Response) {
//获取响应体
Body, _ := io.ReadAll(r.Body)
fmt.Println(string(Body))
}
⭐️ 响应头可以通过 Header.Get
直接获取
// 响应头
func header(r *http.Response) {
//获取响应头信息
fmt.Println(r.Header.Get("Content-Type"))
//r.Header底层类型是一个map
//不推荐这种方法因为Get会忽略大小写,用map则不用
fmt.Println(r.Header["Content-Type"])
}
⭐️ 状态码也可以直接获取
// 状态码
func status(r *http.Response) {
//状态码
fmt.Println(r.StatusCode)
//状态描述信息
fmt.Println(r.Status)
}
⭐️ 响应编码信息,一般情况下获取编码信息的方式有三种:
content-type
会提供编码信息- 可以通过
html head mate
获取编码信息 - 可以通过网页头部猜测编码信息
也有一些封装 http
包的库会直接提供获取编码信息的,但是原生 http
并没有提供这个功能
⭐️ go
中提供了一个方便猜网页的编码的包 golang.org/x/net/html
包下面
// 编码
func encoding(r *http.Response) {
// content-type 会提供编码信息
// 可以通过html head mate 获取编码信息
// 可以通过网页头部猜测编码信息
//使用DetermineEncoding
bufReader := bufio.NewReader(r.Body)
//使用这个缓冲读取器从响应体中预读(不移动读取位置)最多 1024 个字节的数据,并将这些数据存储到 body 字节切片中。这样可以在不实际消耗这些数据的情况下提前查看一部分响应体的内容。
body, _ := bufReader.Peek(1024)
//它返回三个参数分别是:文件编码结构体、编码名和是否确定是这个编码,它接收两个个参数:字节切片(就是这个响应体的1024个字节)和响应头信息。
e, _, _ := charset.DetermineEncoding(body, r.Header.Get("Content-Type"))
fmt.Println(e)
}
⭐️ 看一下 DetermineEncoding
代码,可以它也是只读取1024个字节
func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) {
if len(content) > 1024 {
content = content[:1024]
}
....
}
⭐️ 但是现在还有个问题就是,默认编码就 utf-8
,网页时 utf-8
就会显示,其他的就需要转码,需要使用到 text/transform
这个包
go get golang.org/x/text/transform
//如果编码不同需要执行编码转换
reader := transform.NewReader(bufReader, e.NewDecoder())
all, _ := io.ReadAll(reader)
fmt.Println(string(all))
🌟 文件的下载
⭐️ 可以直接使用 Get
方法获取数据,然后通过 copy
将数据保存在本地
// 普通下载下载
func download(url, filename string) {
// 使用Get请求获取数据
r, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
defer func() { _ = r.Body.Close() }()
// 创建文件,以便后面拷贝数据
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
//将获取倒的数据拷贝到文件中
n, err := io.Copy(f, r.Body)
if err != nil {
panic(err)
}
fmt.Println(n)
}
⭐️ 在调用函数中需要输入下载连接和文件名
func main() {
//自动文件下载、图片下载、压缩包下载
//测试下载go安装包
const url = "https://golang.google.cn/dl/go1.22.4.windows-amd64.msi"
//下载到的文件命名
const filename = "go1.22.4.msi"
download(url, filename)
}
⭐️ 拓展进度条的下载
1️⃣ 需要自定义一个 Reader
类型,通过里面定义总大小的当前下载的小可以显示进度条
// Reader 定义一个自己的Reader
type Reader struct {
//实现Reader接口
io.Reader
// 总大小
Total int64
// 已经下载的大小
Current int64
}
2️⃣ 实现 Reader
接口,在 Read
方法中打印进度条
r.Current * 10000
:这步操作将当前进度值乘以 10000,避免精度丢失,r.Current * 10000 / r.Total
:接下来,将上一步的结果除以 r.Total
。这个操作计算了当前进度值相对于总进度的比例,得到的结果是一个小数,表示当前进度的万分比,float64(...)
:将上一步的结果转换为浮点数(float64
)。在 Go
语言中,如果参与运算的变量已经是浮点数类型,则不需要显式转换。这里可能是为了强调结果是一个浮点数,或者因为之前的变量类型不是浮点数,/ 100
:最后,将上一步的结果除以 100,将万分比转换为百分比。因为之前乘以了 10000,所以这里除以 100 可以将结果转换回百分比的形式
// 实现Reader接口
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
r.Current += int64(n)
// 格式化进度条
fmt.Printf("\r 进度: %.2f%% \n", float64(r.Current*10000/r.Total)/100)
return
}
3️⃣ 使用实现 Reader
的 Reader
类,在 io.Copy
会自动调用 Read
方法
// 带进度条的下载
func downloadProgress(url, filename string) {
// 使用Get请求获取数据
r, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
defer func() { _ = r.Body.Close() }()
// 创建文件,以便后面拷贝数据
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
reader := &Reader{
// 通过r.Body获取数据
Reader: r.Body,
// 通过r.ContentLength获取文件大小
Total: r.ContentLength,
}
//将获取倒的数据拷贝到文件中
n, err := io.Copy(f, reader)
if err != nil {
panic(err)
}
fmt.Println(n)
}
🌟 发送复杂的 Post
请求
⭐️ post请求分为: post form
,post json
,post文件
⭐️ post 请求的本质,它是 request body
提交,相对于get请求(urlencoded
提交查询参数,提交内容有大小限制)
⭐️ post的不同的形式,也就是Body的格式不同:
- post form表单是
urlencoded
形式, - post json提交的
json
格式 - post 提交文件
1️⃣ post from请求
⭐️ 直接使用 Values
来添加和 get
差不多
⭐️ 其实也有它的封装好的方法那就是 PostForm
func postFrom() {
//form data形式和query string 一样
data := make(url.Values)
data.Add("name", "tanc")
data.Add("age", "18")
encode := data.Encode()
//strings可以使用NewReader创建一个reader,因为strings包中定义了NewReader方法
resp, _ := http.Post("http://httpbin.org/post", "application/x-www-form-urlencoded", strings.NewReader(encode))
body, _ := io.ReadAll(resp.Body)
defer func() { _ = resp.Body.Close() }()
fmt.Printf("%s", body)
}
2️⃣ post json请求
在上面的Post简单请求也演示过了
func postJson() {
u := struct {
Name string `json:"name"`
Age int `json:"age"`
}{
Name: "tanc",
Age: 18,
}
load, _ := json.Marshal(u)
//strings可以使用NewReader创建一个reader,因为strings包中定义了NewReader方法
resp, _ := http.Post("http://httpbin.org/post", "application/json", bytes.NewReader(load))
body, _ := io.ReadAll(resp.Body)
defer func() { _ = resp.Body.Close() }()
fmt.Printf("%s", body)
}
3️⃣ post文件上传
请参照https://juejin.cn/post/6844904017466753032?searchId=202406191616185C5FD717A1CCB9584DFF 这篇文章
🌟 重定向
⭐️ 在请求页面时会标识一个状态码去重定向到另外一个页面,3xx
等
⭐️ 重定向的问题:
- 重定向次数过多
- 信息丢失
⭐️ 在前面几部分主要时围绕着 request
和 response
,现在开始说 Client
1️⃣ 限制请求次数
⭐️ 创建和初始化一个 http.Client
实例,然后时实现 CheckRedirect
方法,它接收一个 request
请求和 via
请求次数,在内部可以判断请求次数如果过多久报错
⭐️ 这里通过 httpbin
网页的 http://httpbin.org/redirect/20
测试,20
表示重定向次数
func redirectLimit() {
//限制重定向次数
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) > 10 {
return errors.New("重定次数太多")
}
return nil
},
}
r, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/redirect/20", nil)
_, err := client.Do(r)
if err != nil {
panic(err)
}
}
2️⃣ 禁止重定向
⭐️ 还是使用 CheckRedirect
,只要发生重定向就不让他重定向 http.ErrUseLastResponse
报错
⭐️ 使用 httpbin
的 http://httpbin.org/cookies/set?name=tanc
,如果正常重定向会重定向到 http://httpbin.org/cookies
,这里会被拦截,最后一行会输出请求的 Url
func redirectForbidden() {
//禁止重定向
// 登录请求,防止重定向到首页
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
r, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies/set?name=tanc", nil)
//rtwo, err := http.DefaultClient.Do(r)
rtwo, err := client.Do(r)
if err != nil {
panic(err)
}
defer func() { _ = rtwo.Body.Close() }()
fmt.Println(rtwo.Request.URL)
}
🌟 Cookie
⭐️ Cookie 是一种存储在用户本地浏览器上的小型数据存储对象,它用于维护状态信息。以下是 Cookie 的基本流程:
- 设置 Cookie: 当用户首次访问网站时,服务器可以在 HTTP 响应头中通过
Set-Cookie
指令设置 Cookie,并将数据发送到用户的浏览器。 - 存储 Cookie: 用户的浏览器接收到 Cookie 后,会将其存储在本地。
- 发送 Cookie: 每当用户发起请求到同一服务器时,浏览器会自动在请求头中通过
Cookie
字段发送之前存储的 Cookie。 - 使用 Cookie: 服务器接收到请求后,可以从请求头中读取 Cookie 信息,以此来识别用户状态或偏好设置。
Cookie 主要用于以下目的:
- 会话管理: 识别用户身份,维持用户登录状态。
- 个性化设置: 存储用户偏好,如语言选择、主题设置等。
- 追踪用户行为: 记录用户的浏览习惯,用于分析用户行为或定向广告。
简而言之,Cookie 是一种客户端存储机制,用于在无状态的 HTTP 协议下保持用户状态和偏好。
⭐️ Cookie分类
- 会话型
Cookie
- 持久型
Cookie
1️⃣ 第一种添加Cookie方法
⭐️ 这种方法比较繁琐,需要创建第一次的请求拿取到Cookie,创建第二次请求将Cookie加进去:
- 创建 HTTP 客户端: 定义了一个
http.Client
实例,其中CheckRedirect
被设置为总是使用最后一个响应,这通常用于处理重定向。 - 发起第一次请求: 使用
http.NewRequest
创建一个 GET 请求,目标是http://httpbin.org/cookies/set?name=tanc
。这个请求会设置一个名为tanc
的 Cookie。 - 执行第一次请求: 使用
client.Do
发起第一次请求,并接收响应。 - 关闭响应体: 使用延迟函数(
defer
)确保在函数返回前关闭响应体。 - 创建第二次请求: 再次使用
http.NewRequest
创建一个 GET 请求,目标是http://httpbin.org/cookies
。这个请求的目的是获取当前的 Cookie。 - 从第一次响应中获取 Cookie: 从第一次请求的响应中获取所有的 Cookie,并添加到第二次请求中。
- 执行第二次请求: 使用
client.Do
发起第二次请求,这次请求会携带上一次请求设置的 Cookie。 - 处理第二次响应: 检查第二次请求是否有错误发生,如果有错误则使用
panic
处理。 - 读取并打印第二次响应的内容: 使用
io.ReadAll
读取第二次请求的响应体,并使用fmt.Printf
打印出来。
func rrCookie() {
// 模拟一个登录请求
// 请求一个页面,床底基本登录信息,将响应的Cookie添加到下一次请求上
// 使用http://httpbin.org/cookies/set?name=tanc 接口它会返回Cookie,再一次请求就会携带上cookie,通过body就会打印出设置的Cookie
// http://httpbin.org/cookies/set?name=tanc 是response
// request 是http://httpbin.org/cookies
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
first, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies/set?name=tanc", nil)
firstResponse, _ := client.Do(first)
defer func() { _ = firstResponse.Body.Close() }()
secondRequest, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies", nil)
for _, cookies := range firstResponse.Cookies() {
secondRequest.AddCookie(cookies)
}
secondResponse, err := client.Do(secondRequest)
if err != nil {
panic(err)
}
defer func() { _ = secondResponse.Body.Close() }()
allContent , _ := io.ReadAll(secondResponse.Body)
fmt.Printf("%s", allContent )
}
2️⃣ 第二中添加Cookie方法持久化
⭐️ 通过创建 CoockieJar
func jarCookie() {
//创建一个Cookiejar来自动添加Cookie
jar, _ := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
}
r, _ := client.Get("http://httpbin.org/cookies/set?name=tanc")
defer func() { _ = r.Body.Close() }()
_, _ = io.Copy(os.Stdout, r.Body)
}
⭐️ 但是原生自带的 Cookie
是会话型 Cookie
,如果想要持久性 Cookie
,就需要调包,使用方法原生 Cookiejar
差不多,只需要吧 Cookiejar
改为 Cookiejar2
go get github.com/juju/persistent-cookiejar
🌟 超时时间
这段代码演示了如何在 Go 语言中配置 http.Client
的超时机制。以下是代码中每一行的作用和说明:
-
创建
http.Client
实例:client := &http.Client{}
这行代码初始化了一个
http.Client
实例,并将其地址赋给变量client
。 -
设置总体超时时间:
Timeout: 10 * time.Second,
Timeout
字段设置了http.Client
的总体超时时间,这里是 10 秒。如果在这段时间内请求没有完成,请求将被取消。 -
配置
http.Transport
:Transport: &http.Transport{
Transport
字段允许你自定义http.Client
的底层网络传输行为。 -
自定义拨号超时:
DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) { return net.DialTimeout(network, addr, 2*time.Second) },
DialContext
方法允许你自定义拨号过程。这里设置了拨号超时为 2 秒。 -
设置响应头超时:
ResponseHeaderTimeout: 5 * time.Second,
ResponseHeaderTimeout
字段设置了等待服务器响应头的超时时间,这里是 5 秒。如果在这段时间内没有收到响应头,请求将被取消。 -
设置 TLS 握手超时:
TLSHandshakeTimeout: 2 * time.Second,
TLSHandshakeTimeout
字段设置了 TLS 握手的超时时间,这里是 2 秒。 -
设置空闲连接超时:
IdleConnTimeout: 60 * time.Second,
IdleConnTimeout
字段设置了连接保持空闲状态的最大时间,这里是 60 秒。超过这个时间,空闲连接将被关闭。 -
发起 GET 请求:
r, _ := client.Get("http://httpbin.org/delay/10")
使用配置好的
http.Client
实例发起一个 GET 请求到http://httpbin.org/delay/10
。这个 URL 会模拟一个 10 秒的延迟响应。这里忽略了可能发生的错误。
func main() {
// https://colobu.com/2016/07/01/the-complete-guide-to-golang-net-http-timeouts/
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
return net.DialTimeout(network, addr, 2*time.Second)
},
ResponseHeaderTimeout: 5 * time.Second,
TLSHandshakeTimeout: 2 * time.Second,
IdleConnTimeout: 60 * time.Second,
},
}
r, _ := client.Get("http://httpbin.org/delay/10")
}
🌟 服务端
⭐️ 对于 go
而言,创建一个 http
服务器只需要一行代码即可
⭐️第一个参数是监听的地址,第二个参数 handler
是一个 Handler
接口也可以称为路由处理器,用于处理传入的 HTTP
请求,那么 Go 会使用默认的多路复用器 http.DefaultServeMux
作为处理器。大多数情况下使用默认的处理器 DefaultServeMux
即可
http.ListenAndServe("localhost:8080", nil)
⭐️ 放入你的 main
函数启动,打开浏览器访问,只不过限制还没有做请求路由的处理,你的浏览器上应该会显示 404
1️⃣ 自定义
⭐️ 可以自定义配置一个服务端
func main() {
// 创建并配置一个新的 http.Server 实例
server := &http.Server{
// 监听端口,设置为 ":8080" 表示服务器将在所有可用的网络接口的 8080 端口上监听
Addr: ":8080",
// 路由处理器,设置为 nil 表示将使用 http.DefaultServeMux 作为默认的处理器
Handler: nil,
// TLS 配置,用于 HTTPS,这里设置为 nil 表示不使用 HTTPS
TLSConfig: nil,
// 读取超时时间,0 表示没有超时限制
ReadTimeout: 0,
// 读取请求头部的超时时间,0 表示没有超时限制
ReadHeaderTimeout: 0,
// 写入超时时间,0 表示没有超时限制
WriteTimeout: 0,
// 空闲超时时间,0 表示没有超时限制
IdleTimeout: 0,
// 最大请求头的大小,0 表示使用默认值
MaxHeaderBytes: 0,
// 用于配置 TLS 协议的 NextProto 选项,这里设置为 nil 表示不进行特殊配置
TLSNextProto: nil,
// 钩子函数,用于跟踪连接状态,这里设置为 nil 表示不使用
ConnState: nil,
// 日志记录器,这里设置为 nil 表示使用标准库的默认日志记录器
ErrorLog: nil,
// 用于创建每个连接的 base context,这里设置为 nil 表示使用默认实现
BaseContext: nil,
// 用于创建每个连接的 context,这里设置为 nil 表示使用默认实现
ConnContext: nil,
}
// 启动服务器,ListenAndServe 方法将阻塞执行直到服务器停止
server.ListenAndServe()
}
2️⃣ 路由
⭐️ 需要先定义一个结构体实现 Handler
接口中的 ServeHTTP(ResponseWriter, *Request)
方法,再调用 http.handle()
⭐️ 在 Go 语言中,Handler
是一个接口,它定义了一个 ServeHTTP
方法。这个方法接受两个参数:
- 第一个参数
ResponseWriter
是一个接口,它提供了一些方法来构造HTTP
响应,例如写入响应头、状态码和响应体。 - 第二个参数
*Request
是一个指向Request
结构体的指针,Request
包含了有关 HTTP 请求的信息,如方法、URL、头信息、请求体等。
所以,ServeHTTP
方法传入一个接口和一个结构体指针。 Handler
接口源代码:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ResponseWriter
接口定义如下:
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(Status)
}
ResponseWriter
提供了构造 HTTP 响应所需的基本功能。
Request
结构体定义如下:
type Request struct {
Method string
URL *url.URL
Header Header
Body io.ReadCloser
// 还有其他字段...
}
Request
结构体包含了处理 HTTP 请求所需的所有信息。
⌨️ 代码:
func main() {
//注册路由
http.Handle("/index", &MyHandler{})
//监听端口
http.ListenAndServe(":8080", nil)
}
// MyHandler 创建自定义Handler
type MyHandler struct {
}
// 实现Handler接口
func (h *MyHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
//在控制台输出
fmt.Println("my implement")
}
⭐️ 也可以顺带看一看 http.Handle
的源码,它是直接调用了 DefaultServeMux
来处理
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
⭐️ 通过 Handle
选择执行的处理器,这里很明显就是执行的我们自定义的处理器,在选择好处理器之后,调用 ListenAndServe
,就会执行 ServeHTTP
方法,在源代码中体现在下面这几个代码,一步一步来
//ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
//ListenAndServe返回的ListenAndServe
func (srv *Server) ListenAndServe() error {
.....
return srv.Serve(ln)
}
//Serve
func (srv *Server) Serve(l net.Listener) error {
....
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
....
go c.serve(connCtx)
}
}
//serve
func (c *conn) serve(ctx context.Context) {
...
serverHandler{c.server}.ServeHTTP(w, w.req)
}
⭐️ 向这种每一次一都需要自定义一个结构体就会非常麻烦,可以直接使用 http.handlerFunc
函数
func main() {
http.HandleFunc("/index", func(responseWriter http.ResponseWriter, request *http.Request) {
fmt.Println(responseWriter, "index")
})
http.ListenAndServe(":8080", nil)
}
⭐️ 其内部是使用了适配器类型 HandlerFunc
,HandlerFunc
类型是一个适配器,允许将普通函数用作 HTTP
的处理器。如果f是具有适当签名的函数,HandlerFunc
(f)是调用f的Handler
//HandleFunc结构体
type HandlerFunc func(ResponseWriter, *Request)
⭐️ 如果你有一个函数 f
,它的签名与 HandlerFunc
匹配,你可以使用 HandlerFunc(f)
来创建一个 http.Handler
。这是通过类型转换实现的,Go 语言标准库会为 HandlerFunc
类型自动实现 ServeHTTP
方法。
当 HandlerFunc(f)
被用作处理器时,ServeHTTP
方法会简单地调用函数 f
⭐️ 在这个例子中,myHandler
是一个普通的函数,它与 HandlerFunc
的签名匹配。通过 http.HandlerFunc(myHandler)
,我们将其转换为一个 http.Handler
,然后注册到 "/"
路径
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
http.Handle("/", http.HandlerFunc(myHandler))
3️⃣ 反向代理
⭐️ 这里代码会转发到https://golang.org/index
func main() {
http.HandleFunc("/forward", func(writer http.ResponseWriter, request *http.Request) {
director := func(request *http.Request) {
request.URL.Scheme = "https"
request.URL.Host = "golang.org"
request.URL.Path = "index"
}
proxy := httputil.ReverseProxy{Director: director}
proxy.ServeHTTP(writer, request)
})
http.ListenAndServe(":8080", nil)
}