Go 语言 HTTP 请求处理的底层机制

1.web 工作方式的几个概念

Request:用户请求的信息,用来解析用户的请求信息,包括 post、get、cookie、url 等信息

Response:服务器需要反馈给客户端的信息

Conn:用户的每次请求链接 

Handler:处理请求和生成返回信息的处理逻辑

2.工作流程

1.创建 Listen Socket, 监听指定的端口,等待客户端请求到来。(如何去监听端口)
2.Listen Socket 接受客户端的请求,得到 Client Socket, 接下来通过 Client Socket 与客户端通信。(如何接收客户端请求)
3.处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端。(如何分配路由)

3.分析

http.HandleFunc("/", sayHelloWorld)
err := http.ListenAndServe(":8199", nil)

1.net/http包下通过的ListenAndServe()方法,初始化一个 Server 对象。

2.调用该 Server 实例的 ListenAndServe 方法

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

3.调用 net.Listen("tcp", addr),也就是基于 TCP 协议创建 Listen Socket,并在传入的IP 地址和端口号上监听请求,在本例中,IP 地址为空,默认是本机地址,端口号是 8199

	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
   //ln  是ListenConfig实例
	ln, err := net.Listen("tcp", addr)       
	if err != nil {
		return err
	}

4.调用 Server 实例的 Serve(net.Listener) 方法,用来接收并处理客户端的请求信息。这个方法里面起了一个 for 循环,在循环体中首先通过 net.Listener实例的 Accept 方法接收客户端请求,接收到请求后根据请求信息创建一个 conn 连接实例,最后单独开了一个 goroutine,把这个请求的数据当做参数扔给这个 conn 去服务:

ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)

5.conn 首先会解析 request:c.readRequest(), 获取相应的 handler:handler := c.server.Handler,也就是我们刚才在调用函数 ListenAndServe 时候的第二个参数,我们前面例子传递的是 nil,也就是为空,那么默认获取 handler = DefaultServeMux,这个变量就是一个路由器,它用来匹配 url 跳转到其相应的 handle 函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了 http.HandleFunc("/", sayhelloName) 嘛。这个作用就是注册了请求 / 的路由规则,当请求 uri 为 "/",路由就会转到函数 sayhelloName,DefaultServeMux 会调用 ServeHTTP 方法,这个方法内部其实就是调用 sayhelloName 本身,最后通过写入 response 的信息反馈到客户端。

4.拓展

 

ServeMux

type ServeMux struct {
    mu sync.RWMutex   // 锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有 host 信息
}

type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个 handler
    pattern  string  // 匹配字符串
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}


Handler 是一个接口,但是前一小节中的 sayhelloName 函数并没有实现 ServeHTTP 这个接口,为什么能添加呢?原来在 http 包里面还定义了一个类型 HandlerFunc, 我们定义的函数 sayhelloName 就是这个 HandlerFunc 调用之后的结果,这个类型默认就实现了 ServeHTTP 这个接口,即我们调用了 HandlerFunc (f), 强制类型转换 f 成为 HandlerFunc 类型,这样 f 就拥有了 ServeHTTP 方法。

5.Go 代码的执行流程

首先调用 Http.HandleFunc

按顺序做了几件事:

1 调用了 DefaultServeMux 的 HandleFunc

2 调用了 DefaultServeMux 的 Handle

3 往 DefaultServeMux 的 map [string] muxEntry 中增加对应的 handler 和路由规则

其次调用 http.ListenAndServe ("8199", nil)

按顺序做了几件事情:

1 实例化 Server

2 调用 Server 的 ListenAndServe ()

3 调用 net.Listen ("tcp", addr) 监听端口

4 启动一个 for 循环,在循环体中 Accept 请求

5 对每个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求进行服务 go c.serve ()

6 读取每个请求的内容 w, err := c.readRequest ()

7 判断 handler 是否为空,如果没有设置 handler(这个例子就没有设置 handler),handler 就设置为 DefaultServeMux

8 调用 handler 的 ServeHttp

9 在这个例子中,下面就进入到 DefaultServeMux.ServeHttp

10 根据 request 选择 handler,并且进入到这个 handler 的 ServeHTTP

mux.handler(r).ServeHTTP(w, r)
11 选择 handler:

A 判断是否有路由能满足这个 request(循环遍历 ServeMux 的 muxEntry)

B 如果有路由满足,调用这个路由 handler 的 ServeHTTP

C 如果没有路由满足,调用 NotFoundHandler 的 ServeHTTP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值