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

  1. func main() {
  2. http.HandleFunc("/bbs/", bbs)
  3. http.HandleFunc("/", index)
  4. http.HandleFunc("/bbs/user/", user)
  5. err := http.ListenAndServe("127.0.0.1:9001", nil) // 该函数会阻塞(for循环)
  6. if err != nil {
  7. logger.Fatalf("listen failed, err:%s", err.Error())
  8. return
  9. }
  10. }
  11. func index(w http.ResponseWriter, r *http.Request) {
  12. process(w,r,"index")
  13. }
  14. func bbs(w http.ResponseWriter, r *http.Request) {
  15. process(w,r,"bbs")
  16. }
  17. func user(w http.ResponseWriter, r *http.Request) {
  18. process(w,r,"user")
  19. }
  20. // 处理请求,返回请求的信息,以及被哪个handler函数处理了
  21. func process(w http.ResponseWriter, r *http.Request, handleName string) {
  22. method:= r.Method
  23. host := r.Host
  24. url := r.URL.String()
  25. content := fmt.Sprintf("handle name:%s; host:%s;url:%s;method:%s\n",handleName, host, url, method)
  26. logger.Infof("recv request,%s", content[:len(content)-1])
  27. _, err := io.WriteString(w, content)
  28. if err != nil {
  29. logger.Errorf("send response failed, err:%s", err.Error())
  30. return
  31. }
  32. }
  1. # 按照最长的 pattern 进行匹配的,在发起请求时,如果仅仅匹配到pattern,则需要以/结尾
  2. [root@duduniao go_learn]# curl 127.0.0.1:9001/
  3. handle name:index; host:127.0.0.1:9001;url:/;method:GET
  4. [root@duduniao go_learn]# curl 127.0.0.1:9001/bbs/
  5. handle name:bbs; host:127.0.0.1:9001;url:/bbs/;method:GET
  6. [root@duduniao go_learn]# curl 127.0.0.1:9001/bbs/user/
  7. handle name:user; host:127.0.0.1:9001;url:/bbs/user/;method:GET
  8. [root@duduniao go_learn]# curl -X DELETE 127.0.0.1:9001/bbs/user/index.html
  9. handle name:user; host:127.0.0.1:9001;url:/bbs/user/index.html;method:DELETE

1.1.2. 设置响应头部

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

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

1.1.3. 获取请求信息

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

  • 获取请求头参数
  • 获取请求中URL参数
  • 获取请求体
    • json格式的请求体
    • 表单格式的请求体 ```go 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) }

  1. ```
  2. [root@duduniao ~]# curl http://127.0.0.1:9001/user/ # 必须要添加 Authorization 的header
  3. invalid request, header does not has Authorization
  4. [root@duduniao ~]# curl -H "Authorization: 123456" "http://127.0.0.1:9001/user/?name=zhangsan&id=001" # 能正常解析并返回URL参数
  5. {"id":"001","name":"zhangsan"}
  6. [root@duduniao ~]# curl -H "Authorization: 123456" -d "year=2020&day=09" "http://127.0.0.1:9001/comment/?name=zhangsan&id=001"
  7. ok
  8. [root@duduniao ~]# curl -H "Authorization: 123456" -d '{"id":"001","name":"zhangsan"}' "http://127.0.0.1:9001/deploy/" # 解析json
  9. {"id":"001","name":"zhangsan"}

1.2. client端

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

1.2.1. 简单的get请求

  1. func main() {
  2. simpleGet("user/")
  3. }
  4. func simpleGet(uri string) {
  5. resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:9001/%s", uri))
  6. if err != nil {
  7. logger.Errorf("send request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
  8. return
  9. }
  10. defer func() { _ = resp.Body.Close() }()
  11. respBody, err := ioutil.ReadAll(resp.Body)
  12. if err != nil {
  13. logger.Errorf("read response body failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
  14. return
  15. }
  16. fmt.Println(string(respBody))
  17. }
  1. [root@duduniao http]# go run client.go
  2. invalid request, header does not has Authorization

1.2.2. 复杂的Post请求

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

  1. func main() {
  2. complexPost("deploy/")
  3. }
  4. // 如果不涉及头部信息的添加,可以直接使用 http.Post() 函数简化流程, 和 simpleGet() 类似
  5. func complexPost(uri string) {
  6. // 创建http客户端
  7. client := http.Client{
  8. Timeout: time.Second * 3,
  9. }
  10. data := `{"id":"001","name":"zhangsan"}`
  11. // 定义http请求
  12. req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://127.0.0.1:9001/%s", uri), bytes.NewBufferString(data))
  13. if err != nil {
  14. logger.Errorf("new request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
  15. return
  16. }
  17. // 添加http头部
  18. req.Header.Add("Authorization", "123456")
  19. req.Header.Add("Content-Type", "application/json")
  20. response, err := client.Do(req)
  21. if err != nil {
  22. logger.Errorf("send request failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
  23. return
  24. }
  25. defer func() { _ = response.Body.Close() }()
  26. // 读取响应,如果响应是Json,正常进行json解析即可
  27. content, err := ioutil.ReadAll(response.Body)
  28. if err != nil {
  29. logger.Errorf("read response body failed, url:http://127.0.0.1:9001/%s, error:%s", uri, err.Error())
  30. return
  31. }
  32. logger.Infof("response code:%d, body:%s", response.StatusCode, string(content))
  33. }
  1. [root@duduniao http]# go run client.go
  2. 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

  1. // server.go
  2. var msg = make(chan string, 50)
  3. func main() {
  4. http.HandleFunc("/ws/", pushMessage)
  5. go createMsg()
  6. err := http.ListenAndServe("127.0.0.1:9001", nil)
  7. if err != nil {
  8. logger.Errorf("listen 127.0.0.1:9001 failed , err:%s",err.Error())
  9. return
  10. }
  11. }
  12. func createMsg() { // 通过管道不断发送消息
  13. for {
  14. now := time.Now().Format("2006-01-02 15:04:05")
  15. str := random.String(8, random.Alphabetic)
  16. msg <- fmt.Sprintf("time:%s,str:%s", now, str)
  17. time.Sleep(time.Second)
  18. }
  19. }
  20. func pushMessage(w http.ResponseWriter, r *http.Request) {
  21. upgrade := websocket.Upgrader{} // 定义一个 upgrade
  22. conn, err := upgrade.Upgrade(w, r, nil) // 升级http协议到websocket
  23. if err != nil {
  24. logger.Errorf("upgrade to websocket failed, err:%s", err.Error())
  25. return
  26. }
  27. defer func() { _ = conn.Close() }()
  28. for {
  29. select {
  30. case msgStr := <-msg:
  31. if err := conn.WriteMessage(1, []byte(msgStr)); err != nil {
  32. logger.Errorf("send message failed,err:%s",err.Error())
  33. return
  34. }
  35. }
  36. }
  37. }
  1. // client.go
  2. func main() {
  3. dialer := websocket.Dialer{}
  4. connect, _, err := dialer.Dial("ws://127.0.0.1:9001/ws/", nil) // 拨号
  5. if err != nil {
  6. fmt.Printf("connect to server error:%s\n", err.Error())
  7. return
  8. }
  9. defer func() { _ = connect.Close() }()
  10. for {
  11. _, msg, err := connect.ReadMessage() // 从 服务端接受消息
  12. if err != nil {
  13. logger.Errorf("recv message failed, err:%s", err.Error())
  14. break // 不能用continue,当服务端关闭后,再次取值会panic
  15. }
  16. logger.Infof("recv msg:%s", string(msg))
  17. }
  18. }
  1. # 当client断开,server不会受影响。因为会退出循环,关闭连接。
  2. [root@duduniao websocket]# go run client.go
  3. 2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:23,str:cwlOglvQ
  4. 2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:24,str:FAPoLJBO
  5. 2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:25,str:DgEUsGLY
  6. 2021-01-09 23:06:26.846|recv msg:time:2021-01-09 23:06:26,str:hvXjPuqJ
  7. 2021-01-09 23:06:27.204|recv msg:time:2021-01-09 23:06:27,str:CQjvmsEY
  8. 2021-01-09 23:06:28.205|recv msg:time:2021-01-09 23:06:28,str:QAFnzGtg
  9. 2021-01-09 23:06:29.205|recv msg:time:2021-01-09 23:06:29,str:TzpGuSjG
  10. ^Csignal: interrupt
  11. [root@duduniao websocket]# go run server.go
  12. 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
  13. # 当server端口,client可以正常退出
  14. [root@duduniao websocket]# go run client.go
  15. 2021-01-09 23:10:32.275|recv msg:time:2021-01-09 23:10:32,str:lNrfEjre
  16. 2021-01-09 23:10:33.275|recv msg:time:2021-01-09 23:10:33,str:JicBHAPm
  17. 2021-01-09 23:10:34.275|recv msg:time:2021-01-09 23:10:34,str:iujSOelc
  18. 2021-01-09 23:10:35.275|recv msg:time:2021-01-09 23:10:35,str:xeSMvUsl
  19. 2021-01-09 23:10:36.276|recv msg:time:2021-01-09 23:10:36,str:OsXQAIwZ
  20. 2021-01-09 23:10:37.276|recv msg:time:2021-01-09 23:10:37,str:HNmHmwFx
  21. 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读写功能。

  1. // server.go
  2. package main
  3. import (
  4. "github.com/gorilla/websocket"
  5. "github.com/labstack/gommon/random"
  6. "go_learn/logger"
  7. "net/http"
  8. "time"
  9. )
  10. //var msg = make(chan string, 50)
  11. var msg = make(chan map[string]string, 50)
  12. func main() {
  13. http.HandleFunc("/ws/", pushMessage)
  14. go createMsg()
  15. err := http.ListenAndServe("127.0.0.1:9001", nil)
  16. if err != nil {
  17. logger.Errorf("listen 127.0.0.1:9001 failed , err:%s", err.Error())
  18. return
  19. }
  20. }
  21. func createMsg() {
  22. for {
  23. now := time.Now().Format("2006-01-02 15:04:05")
  24. str := random.String(8, random.Alphabetic)
  25. //msg <- fmt.Sprintf("time:%s,str:%s", now, str)
  26. msg <- map[string]string{"time": now, "random string": str}
  27. time.Sleep(time.Second)
  28. }
  29. }
  30. func pushMessage(w http.ResponseWriter, r *http.Request) {
  31. upgrade := websocket.Upgrader{} // 定义一个 upgrade
  32. conn, err := upgrade.Upgrade(w, r, nil) // 升级http协议到websocket
  33. if err != nil {
  34. logger.Errorf("upgrade to websocket failed, err:%s", err.Error())
  35. return
  36. }
  37. defer func() { _ = conn.Close() }()
  38. for {
  39. select {
  40. case msgBody := <-msg:
  41. if err := conn.WriteJSON(msgBody); err != nil {
  42. logger.Errorf("send message failed,err:%s", err.Error())
  43. return
  44. }
  45. }
  46. }
  47. }
  1. // client.go
  2. package main
  3. import (
  4. "fmt"
  5. "github.com/gorilla/websocket"
  6. "go_learn/logger"
  7. )
  8. func main() {
  9. dialer := websocket.Dialer{}
  10. connect, _, err := dialer.Dial("ws://127.0.0.1:9001/ws/", nil) // 拨号
  11. if err != nil {
  12. fmt.Printf("connect to server error:%s\n", err.Error())
  13. return
  14. }
  15. defer func() { _ = connect.Close() }()
  16. var respBody map[string]string
  17. for {
  18. err := connect.ReadJSON(&respBody) // 从 服务端接受消息,用ReadMessage接收到的是base64格式
  19. if err != nil {
  20. logger.Errorf("recv message failed, err:%s", err.Error())
  21. break // 不能用continue,当服务端关闭后,再次取值会panic
  22. }
  23. logger.Infof("recv msg:%#v", respBody)
  24. }
  25. }
  1. [root@duduniao websocket]# go run client.go
  2. 2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"khsWFkuq", "time":"2021-01-09 23:15:49"}
  3. 2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"ArRNXQgB", "time":"2021-01-09 23:15:50"}
  4. 2021-01-09 23:15:52.339|recv msg:map[string]string{"random string":"PsdjJtID", "time":"2021-01-09 23:15:51"}
  5. 2021-01-09 23:15:52.685|recv msg:map[string]string{"random string":"jKRUJawe", "time":"2021-01-09 23:15:52"}
  6. 2021-01-09 23:15:53.685|recv msg:map[string]string{"random string":"tIZPHrqz", "time":"2021-01-09 23:15:53"}
  7. 2021-01-09 23:15:54.685|recv msg:map[string]string{"random string":"bsaZryne", "time":"2021-01-09 23:15:54"}