import (
"net/http"
)
func main() {
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ping...ping..."))
})
http.ListenAndServe(":8999", nil)
}
在 Golang只需要几行代码便能启动一个 http 服务,在上述代码中,完成了两件事:
type Server struct {
Addr string
Handler Handler
mu sync.Mutex
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
TLSConfig *tls.Config
ConnState func(net.Conn, ConnState)
activeConn map[*conn]struct{}
doneChan chan struct{}
listeners map[*net.Listener]struct{}
// ...还有其他变量
}
这些变量在 Server 结构定义了服务器的配置和状态跟踪。它们用于控制服务器监听地址、处理请求、超时设置、连接状态跟踪等方面,并提供了并发安全性的保护。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler 是一个接口,定义了方法: ServeHTTP。任何结构只要实现了这个ServeHTTP方法,那么这个结构体就是一个Handler对象。
ServeHTTP方法的作用是,根据 http 请求 Request 中的请求路径 path 映射到对应的 handler 处理函数,对请求进行处理和响应。ServeHTTP方法有两个参数:ResponseWriter和*Request。
通过实现 Handler 接口并定义自己的 ServeHTTP 方法,可以创建HTTP处理程序。当收到一个HTTP请求时,服务器会调用这个处理程序的 ServeHTTP 方法,并将适当的ResponseWriter和*Request给它,以便处理请求并生成响应。
type muxEntry struct {
h Handler
pattern string
}
muxEntry是一个结构体类型,代表具体的路由条目。每个路由条目包含一个路径模式pattern和关联的处理函数h,用于匹配和处理特定的URL路径。
type ServeMux struct {
mu sync.RWMutex //允许多个 goroutine 并发地读取路由表,但在修改路由表时需要独占锁。
m map[string]muxEntry //用于将 所有的URL 模式(或路径模式)映射到 muxEntry 结构体
es []muxEntry //只存储 "/" 结尾的路由,即模糊匹配patter的muxEntry,且切片内按从最长到最短的顺序排列
hosts bool //表示是否有路由模式包含主机名
}
ServeMux是一个用于路由HTTP请求的多路复用器(multiplexer)。它可以将收到的请求根据路径匹配与注册的处理函数进行对应关联,并分发给相应的处理函数进行处理,实际上是一个实现了http.Handler
接口的结构体。内部通过一个 map 维护了从 path 到 handler 的映射关系, 有效地管理和选择正确的处理程序来处理传入的HTTP请求。当收到一个请求时,ServeMux 会根据请求的URL路径进行匹配,并检查注册的路由模式pattern,然后选择适合的处理程序handler进行处理。
//全局变量
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
在http包的初始化程中,会创建一个名为defaultServeMux的全局ServeMux实例,然后通过&defaultServeMux将其地址赋值给DefaultServeMux。这样就得到了一个默认的全局变量,我们可以通过直接使用DefaultServeMux来进行请求路由和处理,而管理自己的多路复用器。当我们使用http.HandleFunc等函数来注册处理函数时,默认情况下它们会将路由信息添加到DefaultServeMux上。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
如上所示,当调用http.HandleFunc()时,会直接调用全局变量DefaultServeMux.HandleFunc()。HandleFunc的第一个参数是路由表达式,也就是请求路径,第二个参数是一个函数类型,也就是真正处理请求的函数。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// ...
mux.Handle(pattern, HandlerFunc(handler))
}
如上所示,在 ServeMux.HandleFunc 定义了接收一个 ResponseWriter 对象和一个 Request 对象的 HandlerFunc 类型。
通过将处理函数转换为 HandlerFunc 类型,并将其作为参数传递给 ServeMux 的 Handle 方法,我们可以通过mux.Handle将处理函数注册到 ServeMux 的路由 map 中,以便在收到请求时能够正确匹配处理函数并处理该请求。这种设计模式使得我们可以将不同的处理函数注册到 ServeMux,并根据请求的路径来选择相应的处理函数进行处理。这样可以实现灵活的路由功能,使我们能够根据具体的业务逻辑来处理不同的请求。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 检查路由表达式是否为空
if pattern == "" {
panic("http: invalid pattern")
}
// 检查处理函数是否为空
if handler == nil {
panic("http: nil handler")
}
// 如果这个路由表达式已经注册过处理器函数,直接panic
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
// 如果 mux.m 为nil 通过 make 函数初始化 map
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 用路由表达式和处理函数handler创建 muxEntry 对象
e := muxEntry{h: handler, pattern: pattern}
// 所有注册的路由都会存放到mux.m里面
mux.m[pattern] = e
// 如果路由以/做结尾,则在mux.es中存放一份规则,同时做好从长到短的排序
if pattern[len(pattern)-1] == '/' {
// 如果路由patterm以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中,路由长的位于切片的前面
mux.es = appendSorted(mux.es, e)
}
// 如果该路由路径不以 "/" 开始,标记该 mux 中有路由的路径带有主机名
if pattern[0] != '/' {
mux.hosts = true
}
}
通过以上代码,Handle 方法实现了将传进来的pattern和handler保存在muxEntry结构中,并且pattern作为key,把muxEntry装入到DefaultServeMux的Map里面。简单来说就是保存当前路由和自己定义的那个处理函数。在收到请求时,ServeMux 可以根据请求的 URL 查找到对应的处理函数,并进行处理。同时,将以“/”结尾的模糊匹配路由添加到 es 切片中,同时在es切片中通过appendSorted对路由按从长到短进行排序,代码如下所示。
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
n := len(es)
i := sort.Search(n, func(i int) bool {
return len(es[i].pattern) < len(e.pattern)
})
if i == n {
return append(es, e)
}
es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
copy(es[i+1:], es[i:]) // Move shorter entries down
es[i] = e
return es
}
在上述http启动过程中,我们通过调用 net/http 包下的公开方法 http.ListenAndServe,实现了对服务端的一键启动。
而在http.ListenAndServe 内部会声明一个新的 Server 对象,嵌套执行 Server.ListenAndServe 方法。我们使用Http包启动web服务时Handler一般设置为nil,所以使用的就是默认的DefaultServeMux。
//The handler is typically nil, in which case the DefaultServeMux is used.
//Handler通常是nil,此时,会默认使用DefaultServeMux。
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)
// ...
return srv.Serve(ln)
}
Server.ListenAndServe 方法中,根据用户传入的地址,申请到一个监听器TCPListener 监听地址addr,继而调用 Server.Serve 方法为这个连接提供服务
var ServerContextKey = &contextKey{"http-server"}
type contextKey struct {
name string
}
func (srv *Server) Serve(l net.Listener) error {
// ...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
//rw可视为tcp连接
rw, err := l.Accept()
// ...
connCtx := ctx
// ...
//c为http连接
c := srv.newConn(rw)
// ...
go c.serve(connCtx)
}
}
Server.Serve 方法体现了典型的服务端的运行架构,使用For + Listener.Accept
的模式来接受并处理客户端请求。这种模式是一种常见的服务器设计模式,通常用于监听网络连接请求的处理。
在这个方法中,通过循环调用listener.Accept()方法来接受客户端连接。每当有新的连接建立时,将 server 封装成一组 valueContext,并开启一个新的goroutine调用 conn.serve 来处理该连接并传入server 封装后的valueContext,这确保了可以同时处理多个同时到达的连接请求。
*conn.serve()代码很长,核心是下面几行代码,简化之后如下:
func (c *conn) serve(ctx context.Context) {
// ...
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
// ...
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
// ...
}
}
*conn.serve 是响应客户端连接的核心方法,用于处理HTTP连接客户端发送的请求,实现了HTTP连接的处理逻辑。它通过不断循环读取和处理客户端请求,根据请求调用相应的处理函数,并最终取消请求的上下文。
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 此handler即为http.ListenAndServe 中的第二个参数
// 获取Server对应的Handler 封装结构体的时候传入的是nil所以使用默认的DefaultServeMux
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
// 如果是 OPTIONS Method 请求且 URI 是 *,就使用 globalOptionsHandler
// Method == "OPTIONS" Preflighted Request(带预检的跨域请求)
// Preflighted Request在发送真正的请求前,会先发送一个方法为OPTIONS的预请求(Preflighted Request)
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// handler传的是nil就执行 DefaultServeMux.ServeHTTP() 方法
handler.ServeHTTP(rw, req)
}
在serverHandler实现的ServeHTTP()方法里的sh.srv.Handler就是我们最初在http.ListenAndServe()中传入的Handler参数。
如果该Handler对象为nil,则会使用默认的DefaultServeMux,否则使用我们自定义的ServeMux对象。最后调用handler的具体实现结构ServeMux的ServeHTTP()方法匹配当前路由对应的handler方法。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 如果RequestURI为 "*" 判断是不是HTTP/1.1 然后关闭长连接 响应 BadRequest
//....代码省略
// 如果是一个正常的 GET POST 请求 执行ServeMux.Handler() 方法 寻找匹配的路由
h, _ := mux.Handler(r)
// 执行匹配到的路由的ServeHTTP方法
h.ServeHTTP(w, r)
}
ServeMux.ServeHTTP()方法主要代码可以分为两步:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// ...代码省略
//在mux.m和mux.es中根据host/url.path寻找对应的handler
return mux.handler(host, r.URL.Path)
}
在 ServeMux.Handler() 方法内部,会调用 ServerMux.handler(host, r.URL.Path) 方法来查找匹配的处理函数。
// handler is the main implementation of Handler.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 若当前 mux 中注册有带主机名的路由,就用"主机名+路由路径"去匹配
// 也就是说带主机名的路由优先于不带的
if mux.hosts {
h, pattern = mux.match(host + path)
}
// 若没有匹配到,就直接把路由路径拿去匹配
if h == nil {
h, pattern = mux.match(path)
}
// 如果还没有匹配到,就默认返回 NotFoundHandler,该 Handler 会往 响应里写上 "404 page not found"
if h == nil {
h, pattern = NotFoundHandler(), ""
}
// 返回获得的 Handler 和路由路径
return
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// 若 mux.m 中已存在该路由映射,直接返回该路由的 Handler,和路径
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// 找到路径能最长匹配的路由。
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
ServeMux.match()方法用于具体实现根据给定的路径 path 找到最佳匹配的路由。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// ...
mux.Handle(pattern, HandlerFunc(handler))
}
获取到handler之后,又调用了handler.ServeHTTP(w ResponseWriter, r *Request)处理请求,这实际上是http.HandleFunc中注册的路由对应的处理函数,通过HandlerFunc可以将具有func(ResponseWriter, *Request)签名的普通函数转换为一个Handler对象,从而调用 handler.ServeHTTP()进行处理。如示例中在 http.HandleFunc 中注册了 ping 路由对应的处理函数为 ping 函数,那么在调用 handler.ServeHTTP(w, r) 时实际上是调用了 ping(w, r) 函数。