Golang学习笔记(08-1网络编程-HTTP协议)

103 阅读8分钟

1. 简单的http协议

1.1. server端

在项目实践中,如果是存在多个API接口,并且根据不同的HTTP方法进行功能区分时,通常我们使用 Gin 框架进行项目编写,其编程复杂度会低很多。但是如果服务只有个别的接口,如 GET /healthz 接口,或者 GET /metrics 接口,可以使用 http 包编写服务端,优势在于非常轻量级。
http server端工作原理如下图所示,使用 http.HandleFunc() 注册路由及处理器到 serverMux.es 列表(该列表会将以 / 结尾的pattern按照长度倒序排列)。 http.ListenAndServe()开启TCP端口监听,并使用for循环从监听端口取出请求,内部处理(生成request和response两个结构体)后,交给handler处理器,由处理器进行匹配,并处理 request 和 response。  尤其需要注意的是,handler处理器注册时,要使用 / 结尾,否则不会进入 serverMux.es 列表中。
image.png

1.1.1. 简单的server

func main()  {

	http.HandleFunc("/bbs/", bbs)
	http.HandleFunc("/", index)
	http.HandleFunc("/bbs/user/", user)
	err := http.ListenAndServe("127.0.0.1:9001", nil) // 该函数会阻塞(for循环)
	if err != nil {
		logger.Fatalf("listen failed, err:%s", err.Error())
		return
	}
}

func index(w http.ResponseWriter, r *http.Request)  {
	process(w,r,"index")
}

func bbs(w http.ResponseWriter, r *http.Request)  {
	process(w,r,"bbs")
}

func user(w http.ResponseWriter, r *http.Request)  {
	process(w,r,"user")
}

// 处理请求,返回请求的信息,以及被哪个handler函数处理了
func process(w http.ResponseWriter, r *http.Request, handleName string)  {
	method:= r.Method
	host := r.Host
	url := r.URL.String()
	content := fmt.Sprintf("handle name:%s; host:%s;url:%s;method:%s\n",handleName, host, url, method)
	logger.Infof("recv request,%s", content[:len(content)-1])
	_, err := io.WriteString(w, content)
	if err != nil {
		logger.Errorf("send response failed, err:%s", err.Error())
		return
	}
}
# 按照最长的 pattern 进行匹配的,在发起请求时,如果仅仅匹配到pattern,则需要以/结尾
[root@duduniao go_learn]# curl 127.0.0.1:9001/
handle name:index; host:127.0.0.1:9001;url:/;method:GET
[root@duduniao go_learn]# curl 127.0.0.1:9001/bbs/
handle name:bbs; host:127.0.0.1:9001;url:/bbs/;method:GET
[root@duduniao go_learn]# curl 127.0.0.1:9001/bbs/user/
handle name:user; host:127.0.0.1:9001;url:/bbs/user/;method:GET
[root@duduniao go_learn]# curl -X DELETE 127.0.0.1:9001/bbs/user/index.html
handle name:user; host:127.0.0.1:9001;url:/bbs/user/index.html;method:DELETE

1.1.2. 设置响应头部

在响应客户端请求过程中,一般除了写入响应体之外,还可能需要设置响应头,响应头是 map[string]string ,常用方法问 Set() 和 Get() 

func main()  {
	http.HandleFunc("/user/", user)
	err := http.ListenAndServe("127.0.0.1:9001", nil) // 该函数会阻塞(for循环)
	if err != nil {
		logger.Fatalf("listen failed, err:%s", err.Error())
		return
	}
}
func user(w http.ResponseWriter, r *http.Request)  {
	// 获取请求基本信息
	res := make(map[string]interface{}, 4)
	res["method"] = r.Method
	res["host"] = r.Host
	res["url"] = r.URL.String()
	res["content"] = fmt.Sprintf("welcome to /user/ location")
	marshal, _ := json.Marshal(res) // 响应体
	// 设置响应头信息
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	_, err := w.Write(marshal)
	if err != nil {
		logger.Errorf("send response failed, err:%s", err.Error())
		return
	}
}
[root@duduniao go_learn]# curl -v -X DELETE 127.0.0.1:9001/user/
*   Trying 127.0.0.1:9001...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 9001 (#0)
> DELETE /user/ HTTP/1.1
> Host: 127.0.0.1:9001
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Sat, 09 Jan 2021 01:22:37 GMT
< Content-Length: 97
<
* Connection #0 to host 127.0.0.1 left intact
{"content":"welcome to /user/ location","host":"127.0.0.1:9001","method":"DELETE","url":"/user/"}

1.1.3. 获取请求信息

一般获取请求的信息分为三种:

  • 获取请求头参数
  • 获取请求中URL参数
  • 获取请求体
    • json格式的请求体
    • 表单格式的请求体
func main() {
	http.HandleFunc("/user/", user)
	http.HandleFunc("/comment/", comment)
	http.HandleFunc("/deploy/", deploy)
	err := http.ListenAndServe("127.0.0.1:9001", nil) // 该函数会阻塞(for循环)
	if err != nil {
		logger.Fatalf("listen failed, err:%s", err.Error())
		return
	}
}

// 读取header和URL数据
func user(w http.ResponseWriter, r *http.Request) {
	// 获取请求基本信息, 常见获取认证信息,比如jwt认证的信息
	token := r.Header.Get("Authorization") // 获取认证信息, 校验操作此处省略
	if token == "" {
		logger.Errorf("parse Authorization header failed,err:%s", "Authorization is null")
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, header does not has Authorization")
		return
	}
	// 获取请求URL参数
	query := r.URL.Query() // 获取请求参数的url.Values{"id":[]string{"1"}, "name":[]string{"zhangsan"}}
	fmt.Printf("%#v\n", query)
	id := query.Get("id") // 如果对于的value长度为零,取切片的第一个元素
	name := query.Get("name")
	res, _ := json.Marshal(map[string]string{"id": id, "name": name})
	// 设置响应头信息
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	_, err := w.Write(res)
	if err != nil {
		logger.Errorf("send response failed, err:%s", err.Error())
		return
	}
}

// 读取表单的数据
func comment(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("Authorization") // 获取认证信息, 校验操作此处省略
	if token == "" {
		logger.Errorf("parse Authorization header failed,err:%s", "Authorization is null")
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, header does not has Authorization")
		return
	}
	// r.ParseForm() 会做两个操作:
	// 1. POST PUT PATCH 请求,则将表单参数和URL参数解析到 r.Form和r.PostForm
	// 2. 其它类型的请求,会跳过表单数据解析,仅解析URL参数到 r.Form
	// 请求的 Content-Type: application/x-www-form-urlencoded
	if err := r.ParseForm(); err != nil {
		logger.Errorf("parse form failed,err:%s", err.Error())
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, parse form failed")
		return
	}
	fmt.Printf("form: %#v\n", r.Form)
	fmt.Printf("formdata: %#v\n", r.PostForm)
	_, _ = io.WriteString(w, "ok\n")
}

// 读取请求体中的json数据
func deploy(w http.ResponseWriter, r *http.Request)  {
	token := r.Header.Get("Authorization") // 获取认证信息, 校验操作此处省略
	if token == "" {
		logger.Errorf("parse Authorization header failed,err:%s", "Authorization is null")
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, header does not has Authorization")
		return
	}
	// 如果不是 POST 和 PUT 请求则返回
	if r.Method != http.MethodPost && r.Method != http.MethodPut {
		logger.Errorf("request method invalid current:%s, the handler need %s,%s", r.Method, http.MethodPut, http.MethodPost)
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, request method is not supported")
		return
	}
	content, err := ioutil.ReadAll(r.Body) // request中的body不需要手动关闭,程序会自动关闭
	// 读取body失败则返回
	if err != nil {
		logger.Errorf("read body failed, err:%s", err.Error())
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, request body error")
		return
	}
	var requestBody map[string]interface{}
	err = json.Unmarshal(content, &requestBody)
	if err != nil {
		logger.Errorf("unmarshal body failed, err:%s", err.Error())
		w.WriteHeader(401)
		_, _ = io.WriteString(w, "invalid request, request body error")
		return
	}
	fmt.Printf("%#v\n", requestBody)
	_, _ = w.Write(content)
}
[root@duduniao ~]# curl http://127.0.0.1:9001/user/  # 必须要添加 Authorization 的header
invalid request, header does not has Authorization

[root@duduniao ~]# curl -H "Authorization: 123456" "http://127.0.0.1:9001/user/?name=zhangsan&id=001" # 能正常解析并返回URL参数
{"id":"001","name":"zhangsan"}

[root@duduniao ~]# curl -H "Authorization: 123456" -d "year=2020&day=09"  "http://127.0.0.1:9001/comment/?name=zhangsan&id=001"
ok
[root@duduniao ~]# curl -H "Authorization: 123456" -d '{"id":"001","name":"zhangsan"}' "http://127.0.0.1:9001/deploy/" # 解析json
{"id":"001","name":"zhangsan"}

1.2. client端

http client 通常使用Http包进行编写,其中比较重要的是在处理完毕请求后,需要关闭 resopnse.Body,否则会导致内存泄露!server 端采用上 1.1.3 中的代码!

1.2.1. 简单的get请求

func main()  {
	simpleGet("user/")
}

func simpleGet(uri string)  {
	resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:9001/%s", uri))
	if err != nil {
		logger.Errorf("send request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
		return
	}
	defer func() { _ = resp.Body.Close() }()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logger.Errorf("read response body failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
		return
	}
	fmt.Println(string(respBody))
}
[root@duduniao http]# go run client.go
invalid request, header does not has Authorization

1.2.2. 复杂的Post请求

其它类型的请求,都是类似的操作,区别在于 Method 不同!

func main() {
	complexPost("deploy/")

}

// 如果不涉及头部信息的添加,可以直接使用 http.Post() 函数简化流程, 和 simpleGet() 类似
func complexPost(uri string) {
	// 创建http客户端
	client := http.Client{
		Timeout: time.Second * 3,
	}
	data := `{"id":"001","name":"zhangsan"}`
	// 定义http请求
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://127.0.0.1:9001/%s", uri), bytes.NewBufferString(data))
	if err != nil {
		logger.Errorf("new request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
		return
	}
	// 添加http头部
	req.Header.Add("Authorization", "123456")
	req.Header.Add("Content-Type", "application/json")
	response, err := client.Do(req)
	if err != nil {
		logger.Errorf("send request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
		return
	}
	defer func() { _ = response.Body.Close() }()
	// 读取响应,如果响应是Json,正常进行json解析即可
	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		logger.Errorf("read response body failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
		return
	}
	logger.Infof("response code:%d, body:%s", response.StatusCode, string(content))
}
[root@duduniao http]# go run client.go
2021-01-09 13:09:37.521|response code:200, body:{"id":"001","name":"zhangsan"}

2. websocket协议

普通的http协议中,必须是client向server发起一次 request ,然后server回应一个response。取法实现server主动向client推送实时数据,而websocket主要就是解决这个问题的。其将http协议升级到websocket协议,并维持长连接,此时 server 和 client 可以互发数据。

2.1. websocket发送[]byte

// server.go
var msg = make(chan string, 50)

func main() {
	http.HandleFunc("/ws/", pushMessage)
	go createMsg()
	err := http.ListenAndServe("127.0.0.1:9001", nil)
	if err != nil {
		logger.Errorf("listen 127.0.0.1:9001 failed , err:%s",err.Error())
		return
	}
}

func createMsg() { // 通过管道不断发送消息
	for {
		now := time.Now().Format("2006-01-02 15:04:05")
		str := random.String(8, random.Alphabetic)
		msg <- fmt.Sprintf("time:%s,str:%s", now, str)
		time.Sleep(time.Second)
	}
}

func pushMessage(w http.ResponseWriter, r *http.Request) {
	upgrade := websocket.Upgrader{}         // 定义一个 upgrade
	conn, err := upgrade.Upgrade(w, r, nil) // 升级http协议到websocket
	if err != nil {
		logger.Errorf("upgrade to websocket failed, err:%s", err.Error())
		return
	}
	defer func() { _ = conn.Close() }()
	for {
		select {
		case msgStr := <-msg:
			if err := conn.WriteMessage(1, []byte(msgStr)); err != nil {
				logger.Errorf("send message failed,err:%s",err.Error())
				return
			}
		}
	}
}
// client.go
func main()  {
	dialer := websocket.Dialer{}
	connect, _, err := dialer.Dial("ws://127.0.0.1:9001/ws/", nil) // 拨号
	if err != nil {
		fmt.Printf("connect to server error:%s\n", err.Error())
		return
	}
	defer func() {  _ = connect.Close() }()
	for {
		_, msg, err := connect.ReadMessage()  // 从 服务端接受消息
		if err != nil {
			logger.Errorf("recv message failed, err:%s", err.Error())
			break // 不能用continue,当服务端关闭后,再次取值会panic
		}
		logger.Infof("recv msg:%s", string(msg))
	}
}
# 当client断开,server不会受影响。因为会退出循环,关闭连接。
[root@duduniao websocket]# go run client.go
2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:23,str:cwlOglvQ
2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:24,str:FAPoLJBO
2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:25,str:DgEUsGLY
2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:26,str:hvXjPuqJ
2021-01-09 23:06:27.204|recv msg:time:2021-01-09 23:06:27,str:CQjvmsEY
2021-01-09 23:06:28.205|recv msg:time:2021-01-09 23:06:28,str:QAFnzGtg
2021-01-09 23:06:29.205|recv msg:time:2021-01-09 23:06:29,str:TzpGuSjG
^Csignal: interrupt
[root@duduniao websocket]# go run server.go
2021-01-09 23:06:31.205|send message failed,err:write tcp 127.0.0.1:9001->127.0.0.1:59630: write: broken pipe

# 当server端口,client可以正常退出
[root@duduniao websocket]# go run client.go
2021-01-09 23:10:32.275|recv msg:time:2021-01-09 23:10:32,str:lNrfEjre
2021-01-09 23:10:33.275|recv msg:time:2021-01-09 23:10:33,str:JicBHAPm
2021-01-09 23:10:34.275|recv msg:time:2021-01-09 23:10:34,str:iujSOelc
2021-01-09 23:10:35.275|recv msg:time:2021-01-09 23:10:35,str:xeSMvUsl
2021-01-09 23:10:36.276|recv msg:time:2021-01-09 23:10:36,str:OsXQAIwZ
2021-01-09 23:10:37.276|recv msg:time:2021-01-09 23:10:37,str:HNmHmwFx
2021-01-09 23:10:37.289|recv message failed, err:websocket: close 1006 (abnormal closure): unexpected EOF

2.2. websocket发送json数据

上述的websockt发送的是简单的 []byte 数据,实践中常常用到 json 数据转发,websocket 包自带了json读写功能。

// server.go
package main

import (
	"github.com/gorilla/websocket"
	"github.com/labstack/gommon/random"
	"go_learn/logger"
	"net/http"
	"time"
)

//var msg = make(chan string, 50)
var msg = make(chan map[string]string, 50)

func main() {
	http.HandleFunc("/ws/", pushMessage)
	go createMsg()
	err := http.ListenAndServe("127.0.0.1:9001", nil)
	if err != nil {
		logger.Errorf("listen 127.0.0.1:9001 failed , err:%s", err.Error())
		return
	}
}

func createMsg() {
	for {
		now := time.Now().Format("2006-01-02 15:04:05")
		str := random.String(8, random.Alphabetic)
		//msg <- fmt.Sprintf("time:%s,str:%s", now, str)
		msg <- map[string]string{"time": now, "random string": str}
		time.Sleep(time.Second)
	}
}

func pushMessage(w http.ResponseWriter, r *http.Request) {
	upgrade := websocket.Upgrader{}         // 定义一个 upgrade
	conn, err := upgrade.Upgrade(w, r, nil) // 升级http协议到websocket
	if err != nil {
		logger.Errorf("upgrade to websocket failed, err:%s", err.Error())
		return
	}
	defer func() { _ = conn.Close() }()
	for {
		select {
		case msgBody := <-msg:
			if err := conn.WriteJSON(msgBody); err != nil {
				logger.Errorf("send message failed,err:%s", err.Error())
				return
			}
		}
	}
}
// client.go
package main

import (
	"fmt"
	"github.com/gorilla/websocket"
	"go_learn/logger"
)

func main()  {
	dialer := websocket.Dialer{}
	connect, _, err := dialer.Dial("ws://127.0.0.1:9001/ws/", nil) // 拨号
	if err != nil {
		fmt.Printf("connect to server error:%s\n", err.Error())
		return
	}
	defer func() {  _ = connect.Close() }()
	var respBody map[string]string
	for {
		err := connect.ReadJSON(&respBody)  // 从 服务端接受消息,用ReadMessage接收到的是base64格式
		if err != nil {
			logger.Errorf("recv message failed, err:%s", err.Error())
			break // 不能用continue,当服务端关闭后,再次取值会panic
		}
		logger.Infof("recv msg:%#v", respBody)
	}
}
[root@duduniao websocket]# go run client.go
2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"khsWFkuq", "time":"2021-01-09 23:15:49"}
2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"ArRNXQgB", "time":"2021-01-09 23:15:50"}
2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"PsdjJtID", "time":"2021-01-09 23:15:51"}
2021-01-09 23:15:52.685|recv msg:map[string]string{"random string":"jKRUJawe", "time":"2021-01-09 23:15:52"}
2021-01-09 23:15:53.685|recv msg:map[string]string{"random string":"tIZPHrqz", "time":"2021-01-09 23:15:53"}
2021-01-09 23:15:54.685|recv msg:map[string]string{"random string":"bsaZryne", "time":"2021-01-09 23:15:54"}