go提供的标准库net/http,实现一个简单的http server非常容易,只需要短短几行代码。本篇文章将会对go标准库net/http实现http服务的原理进行较为深入的探究

快速搭建http server服务

搭建http server的大概步骤包括:

  • 编写handler处理函数
  • 注册路由
  • 创建服务并开启监听 ```go package main

import ( “io” “log” “net/http” )

// 请求处理函数 func indexHandler(w http.ResponseWriter, r *http.Request) { , = io.WriteString(w, “hello, world!\n”) }

func main() { // 注册路由 http.HandleFunc(“/“, indexHandler) // 创建服务并开启监听 err := http.ListenAndServe(“:8001”, nil) if err != nil { log.Fatal(“ListenAndServe: “, err) } }

  1. <a name="y0OdR"></a>
  2. ## http服务处理流程
  3. - 请求会先进入路由
  4. - 路由为请求找到合适的handler
  5. - handler对request进行处理,并构建response
  6. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/357813/1628830524536-ffab7874-dad2-425a-bc22-14dda8aa1352.png#clientId=ua5453e5b-3fce-4&from=paste&id=ub6ea537d&originHeight=411&originWidth=916&originalType=binary&ratio=1&size=17011&status=done&style=none&taskId=u737d4899-c4ad-4307-9346-28bd3c9ed0d)
  7. <a name="yD19U"></a>
  8. ## Golang的http包处理流程
  9. - 路由处理的核心对象是ServeMux
  10. - ServeMux内部维护一个map属性,保存了路由路径和路由处理函数的映射关系
  11. - 注册路由时,往map中写入数据
  12. - 匹配路由时,从map中找到合适的handler处理
  13. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/357813/1628830544416-71ee3e7a-b429-46db-a25a-f912238aa9cf.png#clientId=ua5453e5b-3fce-4&from=paste&id=ua60fb0d1&originHeight=432&originWidth=884&originalType=binary&ratio=1&size=39757&status=done&style=none&taskId=u129a7ed8-8550-45e6-bc86-4d1b223d34f)
  14. <a name="WvBBT"></a>
  15. ## 关键源码逻辑
  16. 下图展示的源码中的关键逻辑:<br />[高清地址](https://www.processon.com/view/link/5f6384b35653bb28eb44fbcc)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/357813/1628830595741-c9bb16c2-bb67-4b56-b2f9-b209f5f341dc.png#clientId=ua5453e5b-3fce-4&from=paste&height=337&id=ue51978f2&originHeight=674&originWidth=1952&originalType=binary&ratio=1&size=121803&status=done&style=none&taskId=uf886eb46-3a69-421f-9ff6-c77ae735c8b&width=976)
  17. <a name="NvXZr"></a>
  18. ## 路由注册接口
  19. 共有两个函数可以用于路由注册,底层都调用的是DefaultServeMux<br />源码位置:src/net/http/server.go
  20. ```go
  21. type Handler interface {
  22. ServeHTTP(ResponseWriter, *Request)
  23. }
  24. func Handle(pattern string, handler Handler) {
  25. DefaultServeMux.Handle(pattern, handler)
  26. }
  27. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  28. DefaultServeMux.HandleFunc(pattern, handler)
  29. }

路由实现

go中的路由基于ServeMux结构实现

  1. type ServeMux struct {
  2. mu sync.RWMutex
  3. // 存储路由和handler的对应关系
  4. m map[string]muxEntry
  5. // 将muxEntry排序存放,排序按照路由表达式由长到短排序
  6. es []muxEntry
  7. // 路由表达式是否包含主机名
  8. hosts bool
  9. }
  10. type muxEntry struct {
  11. // 路由处理函数
  12. h Handler
  13. // 路由表达式
  14. pattern string
  15. }

路由注册逻辑

  • go提供了默认的路由实例DefaultServeMux,如果用户没有自定义路由,就用这个默认的路由
  • 添加路由函数的核心逻辑:将表达式作为key,路由处理函数和表达式组成的muxEntry作为value保存到map中 ```go // 服务启动后的默认路由实例 var DefaultServeMux = &defaultServeMux

// 前面demo中调用handle的内部逻辑 func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }

// HandleFunc func (mux ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, Request)) { … mux.Handle(pattern, HandlerFunc(handler)) }

// Handle func (mux *ServeMux) Handle(pattern string, handler Handler) { … // 创建ServeMux的m实例 if mux.m == nil { mux.m = make(map[string]muxEntry) } // 根据路由表达式和路由处理函数,构造muxEntry对象 e := muxEntry{h: handler, pattern: pattern} // muxEntry保存到map中 mux.m[pattern] = e

// 如果表达式以 ‘/‘ 结尾,加入到排序列表中 if pattern[len(pattern)-1] == ‘/‘ { mux.es = appendSorted(mux.es, e) }

if pattern[0] != ‘/‘ { mux.hosts = true } }

  1. <a name="M9geA"></a>
  2. ## 开启服务
  3. 核心逻辑包括:监听端口、等待连接、创建连接、处理请求
  4. ```go
  5. // 开启服务的入口
  6. func ListenAndServe(addr string, handler Handler) error {
  7. // 创建一个Server,传入handler
  8. // 我们的例子中handler为空
  9. server := &Server{Addr: addr, Handler: handler}
  10. // 调用ListenAndServe真正监听
  11. return server.ListenAndServe()
  12. }
  13. // ListenAndServe
  14. func (srv *Server) ListenAndServe() error {
  15. ...
  16. ln, err := net.Listen("tcp", addr)
  17. return srv.Serve(ln)
  18. }
  19. func (srv *Server) Serve(l net.Listener) error {
  20. ...
  21. // for循环
  22. for {
  23. // 创建上下文对象
  24. ctx := context.WithValue(baseCtx, ServerContextKey, srv)
  25. // 等待新的连接建立
  26. rw, err := l.Accept()
  27. ...
  28. // 连接建立时,创建连接对象
  29. c := srv.newConn(rw)
  30. c.setState(c.rwc, StateNew) // before Serve can return
  31. // 创建协程处理请求
  32. go c.serve(connCtx)
  33. }
  34. }

处理请求

处理请求的逻辑主要是:根据路由请求去和ServeMux的m做匹配,找到合适的handler

  1. func (c *conn) serve(ctx context.Context) {
  2. ...
  3. for {
  4. // 读取下一个请求进行处理(所有的请求都在该协程中进行)
  5. w, err := c.readRequest(ctx)
  6. ...
  7. // 内部转调ServeHTTP函数
  8. serverHandler{c.server}.ServeHTTP(w, w.req)
  9. ...
  10. }
  11. }
  12. // ServeHTTP
  13. func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  14. // sh.srv.Handler是前面的http.ListenAndServe(":8001", nil)传入的handler
  15. handler := sh.srv.Handler
  16. // 如果handler为空,就用默认的DefaultServeMux
  17. if handler == nil {
  18. handler = DefaultServeMux
  19. }
  20. if req.RequestURI == "*" && req.Method == "OPTIONS" {
  21. handler = globalOptionsHandler{}
  22. }
  23. // 这里就是调用ServeMux的ServeHTTP
  24. handler.ServeHTTP(rw, req)
  25. }
  26. // ServeHTTP
  27. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
  28. ...
  29. h, _ := mux.Handler(r)
  30. h.ServeHTTP(w, r)
  31. }
  32. // Handler
  33. func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
  34. ...
  35. return mux.handler(host, r.URL.Path)
  36. }
  37. // handler
  38. func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
  39. ...
  40. if mux.hosts {
  41. h, pattern = mux.match(host + path)
  42. }
  43. if h == nil {
  44. h, pattern = mux.match(path)
  45. }
  46. if h == nil {
  47. h, pattern = NotFoundHandler(), ""
  48. }
  49. return
  50. }
  51. // 匹配路由函数
  52. func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  53. // 先从前面介绍的ServeMux的m中精确查找路由表达式
  54. v, ok := mux.m[path]
  55. // 如果找到,直接返回handler
  56. if ok {
  57. return v.h, v.pattern
  58. }
  59. // 如果不能精确匹配,就去列表中找到最接近的路由
  60. // mux.es中的路由是按照从长到短排序的
  61. for _, e := range mux.es {
  62. if strings.HasPrefix(path, e.pattern) {
  63. return e.h, e.pattern
  64. }
  65. }
  66. return nil, ""
  67. }


原文:https://cloud.tencent.com/developer/article/1717408