【转】Go语言Http Server源码阅读

目录(?)[-]

  • 前言
  • 几个重要概念
  • 具体分析
    • 几个接口
    • Handler
    • ResponseWriter Flusher Hijacker
    • response
    • HandlerFunc
    • ServerMux结构
    • Server
  • 具体例子分析
    • 首先调用HttpHandleFunc
    • 其次调用httpListenAndServe12345 nil
  • 后记

转自:http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

这篇文章出现的理由是业务上需要创建一个Web Server。创建web是所有语言出现必须实现的功能之一了。在nginx+fastcgi+php广为使用的今天,这里我们不妨使用Go来进行web服务器的搭建。

前言

使用Go搭建Web服务器的包有很多,大致有下面几种方法,直接使用net包,使用net.http包,使用第三方包(比如gorilla)。使用net包就需要从tcp层开始封装,耗费人力物力极大,果断舍弃。直接使用封装好的net.http和第三方包才是上策。这里我们就选择了使用官方提供的net.http包来搭建web服务。另外附带一句,gorilla的第三方包现在使用还是非常广的,文档也是比较全的,有兴趣的同学可以考虑使用一下。

建议看这篇文章前先看一下net/http文档 http://golang.org/pkg/net/http/

net.http包里面有很多文件,都是和http协议相关的,比如设置cookie,header等。其中最重要的一个文件就是server.go了,这里我们阅读的就是这个文件。

几个重要概念

ResponseWriter: 生成Response的接口

Handler: 处理请求和生成返回的接口

ServeMux: 路由,后面会说到ServeMux也是一种Handler

Conn : 网络连接

具体分析

(具体的说明直接以注释形式放在代码中)

几个接口:Handler

  1. type Handler interface {
  2. ServeHTTP(ResponseWriter, *Request) // 具体的逻辑函数
  3. }

复制代码

实现了handler接口的对象就意味着往server端添加了处理请求的逻辑。

下面是三个接口(ResponseWriter, Flusher, Hijacker):

ResponseWriter, Flusher, Hijacker

  1. // ResponseWriter的作用是被Handler调用来组装返回的Response的
  2. type ResponseWriter interface {
  3. // 这个方法返回Response返回的Header供读写
  4. Header() Header
  5. // 这个方法写Response的Body
  6. Write([]byte) (int, error)
  7. // 这个方法根据HTTP State Code来写Response的Header
  8. WriteHeader(int)
  9. }
  10. // Flusher的作用是被Handler调用来将写缓存中的数据推给客户端
  11. type Flusher interface {
  12. // 这个方法将写缓存中数据推送给客户端
  13. Flush()
  14. }
  15. // Hijacker的作用是被Handler调用来关闭连接的
  16. type Hijacker interface {
  17. // 这个方法让调用者主动管理连接
  18. Hijack() (net.Conn, *bufio.ReadWriter, error)
  19. }

复制代码

response

实现这三个接口的结构是response(这个结构是http包私有的,在文档中并没有显示,需要去看源码)

  1. // response包含了所有server端的http返回信息
  2. type response struct {
  3. conn *conn // 保存此次HTTP连接的信息
  4. req *Request // 对应请求信息
  5. chunking bool // 是否使用chunk
  6. wroteHeader bool // header是否已经执行过写操作
  7. wroteContinue bool // 100 Continue response was written
  8. header Header // 返回的http的Header
  9. written int64 // Body的字节数
  10. contentLength int64 // Content长度
  11. status int // HTTP状态
  12. needSniff bool // 是否需要使用sniff。(当没有设置Content-Type的时候,开启sniff能根据HTTP body来确定Content-Type)
  13. closeAfterReply bool //是否保持长链接。如果客户端发送的请求中connection有keep-alive,这个字段就设置为false。
  14. requestBodyLimitHit bool //是否requestBody太大了(当requestBody太大的时候,response是会返回411状态的,并把连接关闭)
  15. }

复制代码

在response中是可以看到

  1. func (w *response) Header() Header
  2. func (w *response) WriteHeader(code int)
  3. func (w *response) Write(data []byte) (n int, err error)
  4. func (w *response) Flush()
  5. func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)

复制代码

这么几个方法。所以说response实现了ResponseWriter,Flusher,Hijacker这三个接口 HandlerFunc

handlerFunc是经常使用到的一个type

  1. // 这里将HandlerFunc定义为一个函数类型,因此以后当调用a = HandlerFunc(f)之后, 调用a的ServeHttp实际上就是调用f的对应方法
  2. type HandlerFunc func(ResponseWriter, *Request)
  3. // ServeHTTP calls f(w, r).
  4. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  5. f(w, r)
  6. }

复制代码

这里需要多回味一下了,这个HandlerFunc定义和ServeHTTP合起来是说明了什么?说明HandlerFunc的所有实例是实现了ServeHttp方法的。另,实现了ServeHttp方法就是什么?实现了接口Handler!

所以你以后会看到很多这样的句子:

  1. func AdminHandler(w ResponseWriter, r *Request) {
  2. ...
  3. }
  4. handler := HandlerFunc(AdminHandler)
  5. handler.ServeHttp(w,r)

复制代码

请不要讶异,你明明没有写ServeHttp,怎么能调用呢? 实际上调用ServeHttp就是调用AdminHandler。

好吧,理解这个也花了我较长时间,附带上一个play.google写的一个小例子

http://play.golang.org/p/nSt_wcjc2u

有兴趣继续研究的同学可以继续试验下去

如果你理解了HandlerFunc,你对下面两个句子一定不会讶异了

  1. func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
  2. func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

复制代码

下面接着看Server.go

ServerMux结构

它就是http包中的路由规则器。你可以在ServerMux中注册你的路由规则,当有请求到来的时候,根据这些路由规则来判断将请求分发到哪个处理器(Handler)。

它的结构如下:

  1. type ServeMux struct {
  2. mu sync.RWMutex //锁,由于请求设计到并发处理,因此这里需要一个锁机制
  3. m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是我注册的路由表达式
  4. }
  5. // 下面看一下muxEntry
  6. type muxEntry struct {
  7. explicit bool // 是否精确匹配
  8. h Handler // 这个路由表达式对应哪个handler
  9. }

复制代码

看到这两个结构就应该对请求是如何路由的有思路了:

当一个请求request进来的时候,server会依次根据ServeMux.m中的string(路由表达式)来一个一个匹配,如果找到了可以匹配的muxEntry,就取出muxEntry.h,这是个handler,调用handler中的ServeHTTP(ResponseWriter, *Request)来组装Response,并返回。

ServeMux定义的方法有:

  1. func (mux *ServeMux) match(path string) Handler //根据path获取Handler
  2. func (mux *ServeMux) handler(r *Request) Handler //根据Request获取Handler,内部实现调用match
  3. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //!!这个说明,ServeHttp也实现了Handler接口,它实际上也是一个Handler!内部实现调用handler
  4. func (mux *ServeMux) Handle(pattern string, handler Handler) //注册handler方法
  5. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) //注册handler方法(直接使用func注册)

复制代码

在godoc文档中经常见到的DefaultServeMux是http默认使用的ServeMux

var DefaultServeMux = NewServeMux()

如果我们没有自定义ServeMux,系统默认使用这个ServeMux。

  1. <span style="color: rgb(51, 51, 51); font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: rgb(255, 255, 255);">// 换句话说,http包外层(非ServeMux)中提供的几个方法:</span>

复制代码

实际上就是调用ServeMux结构内部对应的方法。

Server

下面还剩下一个Server结构

  1. type Server struct {
  2. Addr string // 监听的地址和端口
  3. Handler Handler // 所有请求需要调用的Handler(实际上这里说是ServeMux更确切)如果为空则设置为DefaultServeMux
  4. ReadTimeout time.Duration // 读的最大Timeout时间
  5. WriteTimeout time.Duration // 写的最大Timeout时间
  6. MaxHeaderBytes int // 请求头的最大长度
  7. TLSConfig *tls.Config // 配置TLS
  8. }

复制代码

Server提供的方法有:

  1. func (srv *Server) Serve(l net.Listener) error //对某个端口进行监听,里面就是调用for进行accept的处理了
  2. func (srv *Server) ListenAndServe() error //开启http server服务,内部调用Serve
  3. func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error //开启https server服务,内部调用Serve

复制代码

当然Http包也直接提供了方法供外部使用,实际上内部就是实例化一个Server,然后调用ListenAndServe方法

  1. func ListenAndServe(addr string, handler Handler) error //开启Http服务
  2. func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error //开启HTTPs服务

复制代码

具体例子分析

下面根据上面的分析,我们对一个例子我们进行阅读。这个例子搭建了一个最简易的Server服务。当调用http://XXXX:12345/hello的时候页面会返回“hello world”

  1. func HelloServer(w http.ResponseWriter, req *http.Request) {
  2. io.WriteString(w, "hello, world!\n")
  3. }
  4. func main() {
  5. http.HandleFunc("/hello", HelloServer)
  6. err := http.ListenAndServe(":12345", nil)
  7. if err != nil {
  8. log.Fatal("ListenAndServe: ", err)
  9. }
  10. }

复制代码

首先调用Http.HandleFunc

按顺序做了几件事:

1 调用了DefaultServerMux的HandleFunc

2 调用了DefaultServerMux的Handle

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

其次调用http.ListenAndServe(":12345", 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 判断header是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

8 调用handler的ServeHttp

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

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

mux.handler(r).ServeHTTP(w, r)

11 选择handler:

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

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

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

后记

对于net.http包中server的理解是非常重要的。理清serverMux, responseWriter, Handler, HandlerFunc等常用结构和函数是使用go web的重要一步。个人感觉由于go中文档较少,像这样有点复杂的包,看godoc的效果就远不如直接看代码来的快和清晰了。实际上在理解了http包后,才会对godoc中出现的句子有所理解。后续还会写一些文章关于使用net.http构建web server的。请期待之。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2015-12-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

"LollipopGo/library/lollipop/common" 测试3

Golang语言社区 开源轻量级web应用框架,可以快速创建博客及商城等

50890
来自专栏攻城狮的动态

iOS面试题梳理(三)

39070
来自专栏Janti

基础巩固——长连接 、短连接、心跳机制与断线重连

本文将从长连接和短连接的概念切入,再到长连接与短连接的区别,以及应用场景,引出心跳机制和断线重连,给出代码实现。

47110
来自专栏大内老A

[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)

条件获取(Conditional Update)可以避免相同数据的重复传输,进而提高性能。条件更新(Conditional Update)用于解决资源并发操作问...

23690
来自专栏Golang语言社区

package http

要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:

23340
来自专栏前端大白专栏

关于ant-design表单问题

33340
来自专栏向前进

vue-cli脚手架npm相关文件解读(4)utils.js

系列文章传送门: 1、build/webpack.base.conf.js 2、build/webpack.prod.conf.js 3、build/webp...

33260
来自专栏nummy

Grunt快速入门

Grunt是基于JavaScript的命令行构建工具,它可以帮助开发者们自动化重复性的工作。你可以把它看成是JavaScript下的Make或者Ant。它可以完...

9520
来自专栏WindCoder

《Linux内核分析》之操作系统是如何工作的 实验总结

实验阶段,由于学校网速等条件限制,未能在真机上搭建出实验环境。在实验楼中,将代码粘贴进去出现严重的缩进错位,最终未能完成编译新的。本文以分析关键代码为主。

20230
来自专栏刘望舒

Android系统启动流程(四)Launcher启动过程与系统启动流程

前言 此前的文章我们学习了init进程、Zygote进程和SyetemServer进程的启动过程,这一篇文章我们就来学习Android系统启动流程的最后一步:L...

25780

扫码关注云+社区

领取腾讯云代金券