Go Web编程:http包分析

yellowswan 发布于1年前 阅读12370次
0 条评论

一个简单的web服务

package main

import (
    "io"
    "log"
    "net/http"
)

func HelloGoServer(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "Hello, this is a GoServer")
}

func main() {
    http.HandleFunc("/", HelloGoServer)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServer ", err)
    }
}

运行上面的code,然后浏览器中输入:localhost:9090/。浏览器会输出:Hello, this is a GoServer。这里我们的浏览器是一个客户端,上面那段程序执行时是一个服务端,这两个角色不要搞反了。
下面我一一来分享我的分析过程,主要是从源码入手,同时参考网络牛人写的心得,他们这些文字我后面的参考有列出,强烈建议看看。

http包分析

HandleFunc分析

从main入手,先看第一句:

http.HandleFunc("/", HelloGoServer)

查看函数HandleFunc的源码,在server.go中,如果你用的是vim,参考重温vim的配置:支持go,配置好vim后,vim打开上面的代码,光标停留在HandleFunc,敲入gd,会很轻松的跳转过去看到相应的源码 。
在server.go中有两个HandleFunc接口函数,一个是有接收者,一个是没有接收者,很明显,main函数调用的是没有接收者的函数。

// HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } 

这里的DefaultServeMux是一个全局变量,查看声明的地方,

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux 

var defaultServeMux ServeMux 

你会发现有意思的是defaultServeMux还没有声明,就先把它的引用赋值给了DefaultServeMux。童鞋们,这与我们平时用的C、java不一样,go就是这么任性,why?具体参考Go语言基础:变量赋值顺序 。看完它就明白了。
再回到上面,接着调用的是有接收者的HandleFunc,如下:

// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler)) 
} 

Handle方法跟HandleFunc,也有两个,一个有接收者,一个没有接收者,这里HandleFunc调用的是有接收者的Handle方法,源码如下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
}

从注释的英文翻译过来,该函数的功能就是为pattern注册一个handler。用这里的代码翻译就是HandleFunc的功能就是给”/”注册一个handler,这个handler就是HelloGoServer。

ListenAndServe

函数ListenAndServe字面上的解释是监听和服务。这里是监听9090端口。
server.go中同样有两个ListenAndServe,main函数调用的是不带接收者的函数ListenAndServe,然后再由它调用带接收者的函数ListenAndServe,函数源码如下:

// ListenAndServe listens on the TCP network address addr  // and then calls Serve with handler to handle requests  // on incoming connections.  // Accepted connections are configured to enable TCP keep-alives.  // Handler is typically nil, in which case the DefaultServeMux is // used. //  // A trivial example server is:  //  // package main  //  // import ( // "io"  // "net/http"  // "log"  // )  //  // // hello world, the web server // func HelloServer(w http.ResponseWriter, req *http.Request) { // io.WriteString(w, "hello, world!\n")  // }  //  // func main() {  // http.HandleFunc("/hello", HelloServer) // log.Fatal(http.ListenAndServe(":12345", nil)) // }  //  // ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()             
} 

先来看看上面有段注释,我特意截下来如下:

// ListenAndServe listens on the TCP network address addr  
// and then calls Serve with handler to handle requests 
// on incoming connections. 
// Accepted connections are configured to enable TCP keep-alives. 
// Handler is typically nil, in which case the DefaultServeMux is
// used.

翻译过来意思是ListenAndServe监听TCP网络的地址addr,然后调用服务处理程序handler来处理传入的连接请求。如果Handler是空的,则用默认的DefaultServeMux。
我们再来深入函数ListenAndServe,第一句定义Server类型server。Server类型定义http服务相关的参数,比如addr、handler等

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.  
type Server struct {  
    Addr         string        // TCP address to listen on, ":http" if empty  
    Handler      Handler       // handler to invoke, http.DefaultServeMux if nil
    ...
    }

第二句return server.ListenAndServe()调用的是带接收者server的接口函数ListenAndServe,

// ListenAndServe listens on the TCP network address srv.Addr and then  // calls Serve to handle requests on incoming connections.  // Accepted connections are configured to enable TCP keep-alives. // If srv.Addr is blank, ":http" is used.  // ListenAndServe always returns a non-nil error.
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)})
} 

函数Listen监听传入的addr地址,函数Serve等待浏览器输入对应的adder地址连接。

// Serve accepts incoming connections on the Listener l, creating a  // new service goroutine for each. The service goroutines read requests and  // then call srv.Handler to reply to them. //  // For HTTP/2 support, srv.TLSConfig should be initialized to the // provided listener's TLS Config before calling Serve. If  // srv.TLSConfig is non-nil and doesn't include the string "h2" in  // Config.NextProtos, HTTP/2 support is not enabled. // // Serve always returns a non-nil error.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {  
        fn(srv, l)
    } 
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil { 
        return err
    } 

    // TODO: allow changing base context? can't imagine concrete 
    // use cases yet. 
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv) 
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() { 
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond 
                } else {
                    tempDelay *= 2 
                } 
                if max := 1 * time.Second; tempDelay > max { 
                    tempDelay = max 
                } 
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue 
            }  
            return e 
        } 
        tempDelay = 0 
        c := srv.newConn(rw) 
        c.setState(c.rwc, StateNew) // before Serve can return 
        go c.serve(ctx)
    }
}  

for循环中的accept()等待浏览器输入地址连接,一旦连接成功后,会新建一个Conn,并新开一个goroutine,启动conn服务。对应的源码如下:

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    ...
    serverHandler{c.server}.ServeHTTP(w, w.req)
    ...
}

服务中会调用ServeHTTP,即我们传入的handler函数。呃,这里明明是两个不通的函数,怎么会有关联呢?

ServeHTTP

先来看看函数ServeHTTP的源码如下:

type Handler interface { ServeHTTP(ResponseWriter, *Request) } 

// serverHandler delegates to either the server's Handler or 
// DefaultServeMux and also handles "OPTIONS *" requests. 
type serverHandler struct { srv *Server } 

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)
}  

结合前面的web服务程序,我们传入的handler是空,在这里,会将默认的handler:DefaultServeMux传给了handler。
ServeHTTP是一个接口,对象HandlerFunc调用了接口ServeHTTP,对象实现接口ServeHTTP的源码如下:

// The HandlerFunc type is an adapter to allow the use of 
// ordinary functions as HTTP handlers. If f is a function 
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request) 

// ServeHTTP calls f(w, r). 
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { 
    f(w, r) 
} 

不难发现即使调用的就是对象f函数。然后回到头重新分析下main的两个函数,不难理解这里调用的其实是函数HelloGoServer。
好,到这里,两个重要接口都分析完了。接下来我们分析其中所接触到的结构体以及对应的接口方法。

几个相关的结构体

ServerMux结构

ServeMux大致作用是,他有一张map表,map里的key记录的是r.URL.String(),而value记录的是一个方法,这个方法和ServeHTTP是一样的,这个方法有一个别名,叫HandlerFunc

type ServeMux struct { 
    mu    sync.RWMutex 
    m     map[string]muxEntry // 存放具体路由的信息,一个string对应一个mux实体,本例中"/"对应HelloGoServer
    hosts bool // whether any patterns contain hostnames
} 

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string                                 
}  

ServeMux定义的一些方法有:

func (mux *ServeMux) match(path string) (h Handler, pattern string) //根据path查找handler和pattern
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) // 内部调用(mux *ServeMux)handler根据host、path查找handler和pattern
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) //内部调用(mux *ServeMux) match,根据host,path查找handler和pattern
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //并不是前面所分析的ServeHTTP接口,因为前面分析的接收者是handler。这里也是查找handler,当ServeMux中没有要找的路由时,会调用NotFoundHandler,输出"404 page not found"。具体看源码。
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) //前面已有分析,给pattern注册一个handler

http包也提供了外部使用的几个方法。但实际上是调用ServeMux的内部方法

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }

Server结构

type Server struct { Addr string // TCP address to listen on, ":http" if empty 监听的地址和端口 Handler Handler // handler to invoke, http.DefaultServeMux if nil 所有请求调用的handler ReadTimeout time.Duration // maximum duration before timing out read of the request WriteTimeout time.Duration // maximum duration before timing out write of the response TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS MaxHeaderBytes int TLSNextProto map[string]func(*Server, *tls.Conn, Handler) ConnState func(net.Conn, ConnState) ErrorLog *log.Logger disableKeepAlives int32 // accessed atomically. nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used } 

Server实现的接口如下:

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

http包也提供了外部使用的几个方法。但实际上是调用Server的内部方法

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

参考:

Golang Http Server源码阅读
http.ServeMux解析
golang(GO语言)http详解简单基础(1)
go语言的http包

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。