Go搭建简单服务器及http包源码分析

来源:互联网 发布:js onload 编辑:程序博客网 时间:2024/06/05 14:43

Go搭建web服务器

使用go语言搭建一个简单的web服务器是非常方便的,一个简单的例子如下:

// main.gopackage mainimport (    "fmt"    "log"    "net/http")func sayhelloName(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, "Hello!") // 这个写入到w的是输出到客户端的}func main() {    http.HandleFunc("/", sayhelloName) // 设置访问的路由    port := ":9090"  // 设置监听的端口    err := http.ListenAndServe(port, nil) // 开始监听    if err != nil {        log.Fatal("ListenAndServe: ", err)  // 输出错误日志    }    fmt.Printf("Server listen at: %s", port)}

运行服务器:

$ go run main.go

运行后,服务器已经在9090端口开始监听http请求了

使用curl工具访问,并查看详细情况:

*   Trying ::1...* TCP_NODELAY set* Connected to localhost (::1) port 9090 (#0)> GET / HTTP/1.1> Host: localhost:9090> User-Agent: curl/7.52.1> Accept: */*> < HTTP/1.1 200 OK< Date: Wed, 15 Nov 2017 08:32:16 GMT< Content-Length: 6< Content-Type: text/plain; charset=utf-8< * Curl_http_done: called premature == 0* Connection #0 to host localhost left intactHello!

可以看到,想要实现一个简单的web服务器,仅仅需要调用net/http包中的两个函数HandleFunc()ListenAndServe()

http包源码分析

下面通过阅读http包源码,并结合上面的例子,详细分析一下,只通过调用HandleFunc()ListenAndServe()两个函数,是怎样使一个web服务器运行起来的。

HandleFunc()
HandleFunc()用于注册路由。在上面的的例子中,即指定:当路径为”/”时,使用sayhelloName()来处理。

// server.go line 2313// 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)}

源码中调用了DefaulServeMux的HandleFunc,那么再看看DefaultServeMux是什么:

// server.go line 2116// DefaultServeMux is the default ServeMux used by Serve.var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMux

定义了ServeMux如下:

// server.go line 2101type ServeMux struct {    mu    sync.RWMutex    m     map[string]muxEntry    hosts bool // whether any patterns contain hostnames}type muxEntry struct {    explicit bool    h        Handler    pattern  string}

ServeMux是一个http request的多路复用器(即相当于路由),它根据request的URL,与已经注册的所有patterns相匹配,并根据匹配结果,调用对应的Handler。ServeMux中利用map[string]muxEntry来进行匹配。对于每一个已经注册的pattern(string类型),都能找到一个对应的muxEntry,也就能找到相应的Handler。

再来看一下Handler:

// server.go line 82type Handler interface {    ServeHTTP(ResponseWriter, *Request)}

Handler是一个接口类型,所有实现了ServeHTTP的类型都可以作为一个Handler。

上文提到HandleFunc()在注册路由时是调用了DefaulServeMux的HandleFunc(),阅读了ServeMux的源码后,现在来看一下HandleFunc()是什么:

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

回顾上文,例子中的sayHelloName()似乎没有实现ServeHTTP,却也能实现接口?这是因为,看以上这部分代码,在将函数传递到下一层时,使用了一次强制类型转换。由于sayHelloName()与HandlerFunc()有相同的参数,因此完成了转换,也就实现了接口。
HandlerFunc()相关源码如下:

// server.go line 1910// 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)}

HandleFunc把上一层的两个参数继续传递到mux.Handle()。

//server.go line 2257// 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) {    mux.mu.Lock()    defer mux.mu.Unlock()    ...    if mux.m == nil {        mux.m = make(map[string]muxEntry)    }    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}    if pattern[0] != '/' {        mux.hosts = true    }    ...    }}

由于代码较长,我们在这里只看一下最基本的功能部分,省略了一些异常处理,重定向等相关的代码。可以看到,这里注册路由的功能就真正实现了,在map中加入了对应的(patterm, MuxEntry)项,且MuxEntry中存储了对应的pattern和Handler。

以上就是路由注册的过程,也就是HandleFunc()的实现。

假设map中注册好了路由规则后,下面看一下接受到请求后,Handler是如何匹配的:
前面我们提到了这个需要被实现的函数ServeHTTP():

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)    h.ServeHTTP(w, r)}

简单的处理之后,将request传递到mux.Handler():

// server.go line 2203func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {    // CONNECT requests are not canonicalized.    if r.Method == "CONNECT" {        return mux.handler(r.Host, r.URL.Path)    }    // All other requests have any port stripped and path cleaned    // before passing to mux.handler.    host := stripHostPort(r.Host)    path := cleanPath(r.URL.Path)    if path != r.URL.Path {        _, pattern = mux.handler(host, path)        url := *r.URL        url.Path = path        return RedirectHandler(url.String(), StatusMovedPermanently), pattern    }    return mux.handler(host, r.URL.Path)}

处理参数后,继续传递到mux.Hanler(),但这时参数内容已经改变:

// server.go line 2226unc (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {    mux.mu.RLock()    defer mux.mu.RUnlock()    // Host-specific pattern takes precedence over generic ones    if mux.hosts {        h, pattern = mux.match(host + path)    }    if h == nil {        h, pattern = mux.match(path)    }    if h == nil {        h, pattern = NotFoundHandler(), ""    }    return}

这里,就真正完成了patter和handler的匹配。可以看到,这就是在我们上文分析的ServeMux中的map来进行匹配的。

ListenAndServe()
完成了路由相关规则的处理后,开始监听端口。

// server.go line 2880func ListenAndServe(addr string, handler Handler) error {    server := &Server{Addr: addr, Handler: handler}    return server.ListenAndServe()}

ListenAndServe()创建了一个server,并调用它的ListenAndServe()。Server结构体由于代码太长,我们只分析server.IstenAndServe():

// server.go line 2627func (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)})}

可以看到,在ListenAndServe()中,在传输层发起了tcp的监听。
再看看server.Serve():

// server.go line 2678func (srv *Server) Serve(l net.Listener) error {    defer l.Close()    ...    for {        rw, e := l.Accept()        if e != nil {            ...        }        tempDelay = 0        c := srv.newConn(rw)        c.setState(c.rwc, StateNew) // before Serve can return        go c.serve(ctx)    }}

同样,这里只保留了核心代码,把有关的异常处理,时延等相关代码都省略。可以看到,在一个for循环中,客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息。使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。

至此,例子中的web服务器,基本的代码运行流程就已经分析完了。

原创粉丝点击