Go语言HTTP包浅谈(二)

Go语言HTTP包浅谈一中讲解了Server和Handler这两个结构,接下来深入讲解一个http请求是如何被解析和处理的。

越看Go的net/http就会越觉得Handler这个接口设计的巧妙,至少你会发现很多关于Go的web中间件都是在Handler接口上做文章的。

http包内部使用Handler接口最有效的例子就是他的路由mux模块。因此开发实际上是可以自己实现一个单独的路由的。甚至实现一个链状的嵌套中间件模型,negroni就是利用了这种特性实现的一种中间件模型。

Server结构体有一个Serve方法,它的主要工作是接口一个TCP连接,经过简单的判断确定无错误之后,基于这个TCP连接创建一个新的连接对象。然后使用go关键字调用该对象的一个私有方法serve,启动一个单独的groutine处理随后的逻辑。主协程继续等待下一个连接的建立。打字模型如下:

 for {
      rw, err := l.Accept()
      if err != nil {
          // do something
      }
      c, err := newConn(rw)
      if err != nil {
          continue
      }
      go c.serve()
 }

关键点在newConnserve这两个方法中。

http内部声明了一个私有的结构体conn。用于包装一个TCP连接。

type conn struct {
    remoteAddr string
    server *Server
    rwc *net.Conn
    buf *bufio.ReadWriter
    ......
}

conn对象中包含了Server对象,conn对象本身是在Server对象中产生的,本身又包含了Server对象,这种用法在我刚开始看的时候,觉得很新奇,但是渐渐地发现,其实这在Golang里面是一件很常见的事情。rwc表示原生的TCP连接句柄。buf是对TCP句柄的一个包装,使用了bufio.Readerbufio.Writer来封装。同时这两个封装是对具有读写方法的对象最常见的封装。其实newConn主要就是做了这么一件事情。

serve方法进来之后,使用了defer func,并且在函数内部使用了recover,防止用户处理请求的时候出现运行时错误导致整个web服务挂掉。然后在一个for循环里面调用了readRequest方法。在这个方法里面做了所有HTTP协议解析工作。把TCP字节流解析成HTTP协议请求中报文中对应的header和body。应用开发者可以通过调用相应的函数来获得对应的数据体。这一步完成之后,就开始调用HandlerServeHTTP方法了,首先传递给路由模块处理,然后根据用户注册的url和处理函数对应关系,调用相应的处理函数。在调用这些处理函数的时候,同时传递了一个ResponseWriter接口,用于写入返回给客户端的数据。这里,由于http包本身使用了bufio模块,并且设置了固定大小的缓存区(4KB)。因此,对于小数据量的写入会有一定的性能影响,尤其,http包为了兼容chunked传输方式,会额外建立2KB的bufio缓存区,并且会先写入该缓存区。在源码TODO里面表示了要改进。但是对于一般的应用,这一点应该不会有太大的影响。

Xiang Chao 05 May 2014
blog comments powered by Disqus
Fork me on GitHub