Golang HTTP Server启动流程简析及常用Handler介绍

rrpi8494 4年前

来自: http://blog.kifile.com/golang/2015/11/21/go_server_intro.html

服务器启动流程简析

不管是什么语言,对于一个HTTP SERVER,它所做的就是监听指定端口的HTTP请求,然后根据HTTP请求中的报头以及请求信息,进行处理,最后返回数据.

Golang自然也不例外,系统为我们提供了两个方法 http.ListenAndServe(string, Handler) , http.ListenAndServeTLS(string, string, string, Handler) ,分别用于开启HTTP服务器和HTTPS服务器.

启动HTTP服务器

我们先看一下 http.ListenAndServe(string, Handler) 是如何实现服务器的开启的.

Golang 首先根据addr和handler构造出一个Server类型的结构体,然后调用 net.Listen("tcp", string) 方法去对指定端口进行注册tcp监听,

真正处理HTTP请求的方法在

 1 func (srv *Server) Serve(l net.Listener) error {   2  defer l.Close()   3  var tempDelay time.Duration // how long to sleep on accept failure   4  for {   5   rw, e := l.Accept()   6   if e != nil {   7    if ne, ok := e.(net.Error); ok && ne.Temporary() {   8     ... // Re-try if necessary.   9     continue  10    }  11    return e  12   }  13   tempDelay = 0  14   c, err := srv.newConn(rw)  15   if err != nil {  16    continue  17   }  18   c.setState(c.rwc, StateNew) // before Serve can return  19   go c.serve()  20  }  21 }

从上面的代码我们可以看出,当系统针对指定的端口进行监听后,就立刻进入一个无限循环的队列中,不停的从端口中获取消息,一旦获取到消息之后,使用routine新开线程进行消息处理.

让我们再看一下在异步线程中处理请求的流程,

 1 func (c *conn) serve() {   2  origConn := c.rwc // copy it before it's set nil on Close or Hijack   3  defer func() {   4   ... // 处理错误信息   5  }()   6    7  if tlsConn, ok := c.rwc.(*tls.Conn); ok {   8      ... // 处理TLS类型的请求.   9  }  10   11  for {  12    ... // 读取请求信息.  13   14         // 调用Handler处理消息  15   serverHandler{c.server}.ServeHTTP(w, w.req)  16     17   ... // 结束  18  }  19 }

可以看到,在serve方法中,首先定义了一个defer延迟回调,用于catch消息处理过程中可能产生的异常,避免服务器崩溃.然后系统之前构造的Server结构体中的Handler对象处理请求.

启动HTTPS服务器

启动HTTPS服务器的基本流程和启动HTTP服务器的流程基本一致,不过由于HTTPS服务需要在握手时对请求签名进行验证,故在conn.serve方法中,会针对TLS协议进行验证,之后再进入Handler处理流程.

常用Handler介绍

看完上面的代码之后,我们会发现其实在启动服务器之后,主线程不断从TCP端口获取新的请求,然后使用routine进行异步执行.服务器所起的作用其实就是一个不断拉去消息的消息队列.

而实际上真正处理业务逻辑的代码是在注册监听时,随端口参数一起传入的handler对象(PS:当传入的Handler为nil时,默认是用系统的 http.DefaultServeMux 作为处理对象.

下面我们将对一些常用的Handler对象进行介绍

Handler

Handler接口中有一个 ServeHTTP(ResponseWriter, *Request) 方法,当SERVER从TCP端口中获取到新的请求时,会调用这个方法去执行具体,换而言之, ServeHTTP方法就是所有SERVER消息处理接口的入口函数.其他所有想要处理HTTP请求的方法都必须直接或间接通过这个接口实现.

ServeMux

ServeMux从名字上我们就可以看出它是一个路由器,对于一个SERVER,他的主入口Handler其实只有一个. 但是访问不同的URL地址,我们又应该给予用于不同的回应.因此我们需要根据不同的URL地址,将请求分发给不同的Handler对象进行处理,ServeMux帮我们实现了这个功能.

为了让ServeMux知道对于指定的URL地址应该使用哪个Handler对象处理,我们需要对Handler在ServeMux中进行提前注册. ServeMux中有两个方法 Handle 和 HandleFunc 分别用于注册Handler对象和 func 对象.

系统默认的 http.DefaultServeMux 也是一个ServeMux类型的对象,通常我们也可以直接调用 http.Handle , 或者 http.HandleFunc 方法进行注册.

通过查阅 ServeMux 的源码,发现其进行URL路径匹配的代码如下:

 1 func (mux *ServeMux) match(path string) (h Handler, pattern string) {   2  var n = 0   3  for k, v := range mux.m {   4   if !pathMatch(k, path) {   5    continue   6   }   7   if h == nil || len(k) > n {   8    n = len(k)   9    h = v.h  10    pattern = v.pattern  11   }  12  }  13  return  14 }

mux.m 对象中包含了通过 Handle 和 HandleFuc 注册的Handler对象(PS:通过HandleFunc 注册的func对象也会被包装成一个Handler),因此这里其实就是对 已注册的 Handler对象做一次遍历, 寻找最优长度匹配的URL地址,然后返回正确的Handler对象,将请求信息分发给他进行处理.

HandlerFunc

Golang和Java有个很大的不同,在于Golang允许将方法作为变量使用,类似C中的方法指针,但是Java中是不能这么操作的.

有时候,某些HTTP请求的业务逻辑可以在单个方法内执行完成,此时专门为了一个业务逻辑去构建一个实现了Handler接口的结构体是一件很不值得的事情,

因此Golang中将定义了一个 HandlerFunc 的类型作为 func(http.ResponseWriter, *http.Request) 的别名,并为他实现了 Handler接口.方便我们直接使用方法处理HTTP请求.

StripPrefix

StripPrefix,顾名思义,他的作用就是去除URL地址前缀.

例如,对于一个原本请求的URL地址为’/api1/sample’ 的Handler对象,如果他被一个 prefix为 /api1 的 StripPrefix 包含,那么实际上分发给他的URL地址,就是 “/sample”.

在我看来StripPrefix其实应该和ServeMux共同使用.

可以试想一下,对于同一个功能模块的Handler,他们处理的URL肯定都会拥有一个相同的前缀用于表明他们所属的功能模块,但是如果有一天功能模块的名字变了,那么改动起来也是一件相当麻烦的事情.

但假如我们将这个功能模块的所有Handler都注册到同一个ServeMux对象中,然后通过StripPrefix统一去掉他们的前缀,这时候我们改动模块名的时候,只需要更改ServeMux和StripPrefix的路径就好了.

RedirectHandler

重定向接口,返回一个重定向之后的地址,供浏览器再次请求.

TimeoutHandler

超时访问接口,

在执行ServeHTTP方法时,他并没有直接在本线程内执行,而是新建了一个channel,然后在异步线程中执行请求,而后本线程等待channel消息用于判断请求是否完成或超时.

FileServer

FileServer负责处理静态资源,对于一个服务器而言,并不意味着一切资源都是以动态形式存在,例如图片,视频等还是以静态文件的形式存在的.

通过http.FileServer方法,可以构建一个fileServe结构体,专门用于将请求地址分发到正确的静态资源地址上.

总结

Golang 已经在官方代码中为我们提供了一整套完备的服务器框架,我们可以根据自己的需求去实现自己的 HTTP 服务器.