基础要求:
用GIN框架实现了一个简单的类似cloudgo的应用,地址:cloudgo
扩展任务:
本文分析net/http库源码,gorilla/mux源码分析地址:【服务计算】gorilla/mux源码分析
net/http库源码分析
目录
一、概念
HTTP
Handler
ServeMux
http.HandlerFunc
二、创建http服务
注册路由
开启监听
处理请求
总结
参考资料
对于Golang,实现一个最简单的http server用不着几行,却能带来极具杀伤力的性能。
一个Go最简单的http服务器:
package main
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
http.HandleFunc("/", IndexHandler)
http.ListenAndServe("127.0.0.0:8000", nil)
}
一、主要概念
HTTP
除去细节,理解 HTTP 构建的网络应用只要关注两个端—客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。
接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer — DefautServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。
Handler
Golang没有继承,类多态的方式可以通过接口实现。任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeMux
ServeMux是HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler。
ServeMux的源码很简单:
type ServeMux struct {
mu sync.RWMutex//mu是锁,由于请求涉及到并发处理,因此这里需要一个锁机制。
m map[string]muxEntry//ServeMux结构中最重要的字段为m,m是一个map,一个string对应一个mux实体,这里的string就是注册的路由表达式。
hosts bool //hosts表示是否在任意的规则中带有host信息。
}
下面看一下muxEntry
type muxEntry struct {
explicit bool//explicit表示是否精确匹配
h Handler//h表示这个路由表达式对应的handler
pattern string//pattern是匹配字符串
}
ServeMux的ServeHTTP方法不是用来处理request和respone的,而是用来找到路由注册的handler.
http.HandlerFunc
http.HandlerFunc函数类型满足Handler接口
type HandlerFunc func(ResponseWriter, *Request)
//实现Handler接口的ServeHTTP方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r) //调用自身
}
二、创建http服务
创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。
注册路由
在这里,我们是这样注册路由的:
http.HandleFunc("/", IndexHandler)
http.HandleFunc选取了DefaultServeMux作为multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
此外,我们还可以使用自定义的路由来注册处理函数,也可以直接自定义一个Server实例(这种模式可以很方便的管理服务端的行为)。
DefaultServeMux的HandleFunc(pattern, handler)方法实际是定义在ServeMux下的:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
上述代码中,HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
此外,ServeMux的Handle方法,将会对pattern和handler函数做一个map映射,把handler和pattern模式绑定到map[string]muxEntry的map上,此时,pattern和handler的路由注册完成。
开启监听
注册好路由之后,启动web服务还需要开启服务器监听:
http.ListenAndServe("127.0.0.1:8000", nil)
http的ListenAndServe方法中可以看到创建了一个Server对象,并调用了Server对象的同名方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)})
}
server.ListenAndServe()方法内部调用net.Listen(“tcp”, addr),该方法内部又调用net.ListenTCP()创建并返回一个监听器net.Listener,然后把监听器 ln 断言转换为 TCPListener 类型,并根据它构造一个tcpKeepAliveListener 对象并传递给server.Serve()方法:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
...
baseCtx := context.Background()
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
for {
rw, e := l.Accept()
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
Serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来获取连接数据并使用newConn方法创建连接对象。与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
处理请求
监听开启之后,一旦客户端请求到底,go就开启一个go程处理请求,主要逻辑都在serve方法之中。而conn.serve()方法会读取请求,然后根据conn内保存的server来构造一个serverHandler类型,并调用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req)
,该方法的源码如下:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
如上源码可以看到,当 handler == nil 时使用默认的DefaultServeMux路由,否则使用在第1步中为Serve指定了的Handler;然后调用该Handler的ServeHTTP方法(该Handler一般被设置为路由ServeMux类型).
而路由ServeMux的ServeHTTP方法则会根据当前请求提供的信息来查找最匹配的Handler(这里为):
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r) //规范化请求的路径格式,查找最匹配的Handler
h.ServeHTTP(w, r)
}
以上查找到的Handler接口值h就是我们事先注册到路由中与请求匹配的Handler;而h的动态类型是HandlerFunc类型(它也满足Handler接口),所以,以上 h.ServeHTTP(w, r) 实际上调用的是接口值h中持有的动态值。
至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。
总结
通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。
首先调用Http.HandleFunc
按顺序做了几件事:
1 调用了DefaultServeMux的HandleFunc
2 调用了DefaultServeMux的Handle
3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
其次调用http.ListenAndServe(“127.0.0.0:8000”, 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就设置为DefaultServeMux8 调用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
参考资料
Golang构建HTTP服务(一)— net/http库源码笔记
3.4 Go的http包详解
net/http包的使用模式和源码解析