不管是什么语言,对于一个HTTP SERVER,它所做的就是监听指定端口的HTTP请求,然后根据HTTP请求中的报头以及请求信息,进行处理,最后返回数据.
Golang自然也不例外,系统为我们提供了两个方法 http.ListenAndServe(string, Handler)
, http.ListenAndServeTLS(string, string, string, Handler)
,分别用于开启HTTP服务器和HTTPS服务器.
我们先看一下 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服务器的基本流程和启动HTTP服务器的流程基本一致,不过由于HTTPS服务需要在握手时对请求签名进行验证,故在conn.serve方法中,会针对TLS协议进行验证,之后再进入Handler处理流程.
看完上面的代码之后,我们会发现其实在启动服务器之后,主线程不断从TCP端口获取新的请求,然后使用routine进行异步执行.服务器所起的作用其实就是一个不断拉去消息的消息队列.
而实际上真正处理业务逻辑的代码是在注册监听时,随端口参数一起传入的handler对象(PS:当传入的Handler为nil时,默认是用系统的 http.DefaultServeMux
作为处理对象.
下面我们将对一些常用的Handler对象进行介绍
Handler接口中有一个 ServeHTTP(ResponseWriter, *Request)
方法,当SERVER从TCP端口中获取到新的请求时,会调用这个方法去执行具体,换而言之, ServeHTTP方法就是所有SERVER消息处理接口的入口函数.其他所有想要处理HTTP请求的方法都必须直接或间接通过这个接口实现.
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对象,将请求信息分发给他进行处理.
Golang和Java有个很大的不同,在于Golang允许将方法作为变量使用,类似C中的方法指针,但是Java中是不能这么操作的.
有时候,某些HTTP请求的业务逻辑可以在单个方法内执行完成,此时专门为了一个业务逻辑去构建一个实现了Handler接口的结构体是一件很不值得的事情,
因此Golang中将定义了一个 HandlerFunc 的类型作为 func(http.ResponseWriter, *http.Request) 的别名,并为他实现了 Handler接口.方便我们直接使用方法处理HTTP请求.
StripPrefix,顾名思义,他的作用就是去除URL地址前缀.
例如,对于一个原本请求的URL地址为’/api1/sample’ 的Handler对象,如果他被一个 prefix为 /api1 的 StripPrefix 包含,那么实际上分发给他的URL地址,就是 “/sample”.
在我看来StripPrefix其实应该和ServeMux共同使用.
可以试想一下,对于同一个功能模块的Handler,他们处理的URL肯定都会拥有一个相同的前缀用于表明他们所属的功能模块,但是如果有一天功能模块的名字变了,那么改动起来也是一件相当麻烦的事情.
但假如我们将这个功能模块的所有Handler都注册到同一个ServeMux对象中,然后通过StripPrefix统一去掉他们的前缀,这时候我们改动模块名的时候,只需要更改ServeMux和StripPrefix的路径就好了.
重定向接口,返回一个重定向之后的地址,供浏览器再次请求.
超时访问接口,
在执行ServeHTTP方法时,他并没有直接在本线程内执行,而是新建了一个channel,然后在异步线程中执行请求,而后本线程等待channel消息用于判断请求是否完成或超时.
FileServer负责处理静态资源,对于一个服务器而言,并不意味着一切资源都是以动态形式存在,例如图片,视频等还是以静态文件的形式存在的.
通过http.FileServer方法,可以构建一个fileServe结构体,专门用于将请求地址分发到正确的静态资源地址上.
Golang 已经在官方代码中为我们提供了一整套完备的服务器框架,我们可以根据自己的需求去实现自己的 HTTP 服务器.