美文网首页golang学习
golang实现简易http服务器以及关键函数分析

golang实现简易http服务器以及关键函数分析

作者: StormZhu | 来源:发表于2018-10-07 19:32 被阅读44次

    简易HTTP服务器的实现

    先看一个使用net/http包实现的简单服务器程序示例。

    package main
    
    import (
        "io"
        "log"
        "net/http"
    )
    
    type myHandler struct{}
    
    func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "I'm home page")
    }
    
    func sayHello(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world!")
    }
    
    func main() {
        // 设置路由
        http.Handle("/", &myHandler{})
        http.HandleFunc("/hello", sayHello)
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
            log.Fatal(err)
        }
    }
    

    运行程序,打开浏览器,访问http://localhost:8080,可以看到浏览器上显示I'm home page,访问http://localhost:8080/hello,可以看到hello world!。就是如此简单,但是程序背后做了哪些事呢?

    分析

    http.Handle主要是设置路由规则,先看它的具体实现吧:

    // Handle registers the handler for the given pattern
    // in the DefaultServeMux.
    // The documentation for ServeMux explains how patterns are matched.
    func Handle(pattern string, handler Handler) { 
        DefaultServeMux.Handle(pattern, handler) 
    }
    

    可以看到两个不认识的东西,HandlerDefaultServeMux。继续查看定义:

    // Handler是一个接口
    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    
    // ServeMux是一个自定义类型
    // ServeMux also takes care of sanitizing the URL request path,
    // redirecting any request containing . or .. elements or repeated slashes
    // to an equivalent, cleaner URL.
    type ServeMux struct {
        mu    sync.RWMutex
        m     map[string]muxEntry //模式字符串与处理器的映射关系
        hosts bool // whether any patterns contain hostnames
    }
    
    // muxEntry 是一个自定义类型,包括一个模式字符串和对应的Handler接口
    type muxEntry struct {
        h       Handler
        pattern string
    }
    
    var defaultServeMux ServeMux
    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    

    DefaultServeMux*ServeMux类型,指向defaultServeMux这个内部变量。ServeMux结构体的含义是路由器,它的成员m比较重要,存储的是模式字符串与处理器的映射关系。*ServeMux实现了Handle函数,主要用途就是填充成员m的,具体代码如下:

    // 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()
        // 错误判断,模式字符串为空、处理起handler为空、对应的模式字符串的处理器已经存在
        if pattern == "" {
            panic("http: invalid pattern")
        }
        if handler == nil {
            panic("http: nil handler")
        }
        if _, exist := mux.m[pattern]; exist {
            panic("http: multiple registrations for " + pattern)
        }
    
        // 如果没有实例化,就make一下
        if mux.m == nil {
            mux.m = make(map[string]muxEntry)
        }
        mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
    
        if pattern[0] != '/' {
            mux.hosts = true
        }
    }
    

    可以看到前面有三个错误判断,这个不影响理解,重要的是mux.m[pattern] = muxEntry{h: handler, pattern: pattern},将传递进来的处理器handler和模式字符串pattern组合成muxEntry结构体,使用map结构绑定patternmuxEntry之间的关系。

    综上所述,http.Handle干了什么呢?主要就是操作DefaultServeMux这个结构体变量,填充它的成员m(map[string]muxEntry类型),建立路由规则。通俗一点,就是为每个url模式串注册了一个回调函数(go语言中称之为处理器Handler)!

    接下来的http.ListenAndServe函数,具体实现如下:

    // ListenAndServe always returns a non-nil error.
    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    

    这一段代码不追究细节还是很好理解的,第一个参数是端口号,第二个参数是具体的处理器(该示例中传入nil),这个函数就是先创建一个具体的Server对象,然后调用ListenAndServe(),不看具体实现,也能猜到,就是先listen(监听),再建立一个事件循环,监听到了连接事件或者读写事件发生,就启动一个协程进行处理。

    问题是,传递进去的handlerServeHTTP方法再什么时候调用的呢?继续翻源码,server.ListenAndServe()主要分为两个步骤,1.处理新连接,即调用Accpet函数。2.处理其他事件,代码中主要体现在go c.serve(ctx)。而在c.serve函数中找到了这句话:serverHandler{c.server}.ServeHTTP(w, w.req)serverHandler类型其实就是重新包装了*Server类型,如下:

    // 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接口变量
        handler := sh.srv.Handler
        // 如果是空,就是用默认的Handler
        if handler == nil {
            handler = DefaultServeMux
        }
        if req.RequestURI == "*" && req.Method == "OPTIONS" {
            handler = globalOptionsHandler{}
        }
        // 调用 ServeHTTP 方法!
        handler.ServeHTTP(rw, req)
    }
    

    serverHandlerServeHTTP方法中调用了它的成员Handler(最开始由你传递进来的)的ServeHTTP方法(这就是你自定义的处理器实现的ServeHTTP的作用所在了)。本示例中的传入的Handlernil,所以最后使用的是DefaultServeMuxServeHTTP方法,而DefaultServeMux*ServeMux类型,它的ServeHTTP方法实现如下:

    // ServeHTTP dispatches the request to the handler whose
    // pattern most closely matches the request URL.
    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)
    }
    
    // handler is the main implementation of Handler.
    // The path is known to be in canonical form, except for CONNECT methods.
    func (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
    }
    

    mux.Handler函数中调用了mux.match函数,mux.match函数主要是在mux.m这个之前注册好的map(字典)中查找url的对应处理器。根据之前定义的路由规则找到了对应的处理器之后,就调用处理器的ServeHTTP方法。

    所以,在访问http://localhost:8080时,执行的是myHandler自定义类型的ServeHTTP方法,返回浏览器I'm home page字符串,并在浏览器上显示出来。

    http.HandleFunc的用法和http.Handle一样,但是第二个参数是func(ResponseWriter, *Request)这个函数类型而不是Handler接口,由上面的分析可知,ServeMux的成员m存储要求是个Handler接口,那么传入个函数怎么处理呢?见源码:

    // 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)
    }
    
    // HandleFunc registers the handler function for the given pattern.
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        mux.Handle(pattern, HandlerFunc(handler))
    }
    
    // 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)
    }
    
    

    DefaultServeMux使用了HandleFunc方法,该方法将函数类型的handler强行转换成了自定义类型HandlerFunc类型,该类型实现了Handler接口,也就使得传入的函数也实现了Handler接口,在ServeHTTP方法中,直接调用自身代表的函数。很是巧妙!

    这些就是net/http包背后默默帮你做的事情。

    自定义路由器

    下一个问题,如果不使用DefaultServeMux呢,能否自己定义一个ServeMux(路由器)?答案是可以的,将简单的HTTP服务器修改一下:

    package main
    
    import (
        "io"
        "log"
        "net/http"
    )
    
    type myHandler struct{}
    
    func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "I'm home page")
    }
    
    func sayHello(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world")
    }
    
    func main() {
        mux := http.NewServeMux() // new出一个新的ServeMux对象
    
        mux.Handle("/", &myHandler{})
        mux.HandleFunc("/hello", sayHello)
        err := http.ListenAndServe(":8080", mux) //传入自定义的路由器(mux)
        if err != nil {
            log.Fatal(err)
        }
    }
    

    与文章开头不同的地方在于,使用http.NewServeMux()创建了一个ServeMux对象,然后调用mux.Handle注册路由,最后在http.ListenAndServe中传入自定义的路由器。

    更进一步,能否不使用ServeMux结构体呢?我们可以直接使用http.ListenAndServe中使用的Server结构,并且自己处理路由分发(自己定义字典存储路由规则)。

    package main
    
    import (
        "io"
        "log"
        "net/http"
        "time"
    )
    
    // 用字典实现路由器
    var mux map[string]func(http.ResponseWriter, *http.Request)
    
    // 自己实现的Handler
    type myHandler struct{}
    
    func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        // 根据url选择对应的处理函数
        if h, ok := mux[r.URL.String()]; ok {
            h(w, r)
            return
        }
        io.WriteString(w, "sorry, error happened!")
    }
    
    func sayHello(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world")
    }
    
    func sayHome(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "I'm home page")
    }
    
    func main() {
        server := http.Server{
            Addr:        ":8080",
            Handler:     &myHandler{},
            ReadTimeout: 5 * time.Second, //设置超时时间
        }
    
        mux = make(map[string]func(http.ResponseWriter, *http.Request))
        mux["/"] = sayHome
        mux["/hello"] = sayHello
        err := server.ListenAndServe()
        if err != nil {
            log.Fatal(err)
        }
    }
    

    参考

    相关文章

      网友评论

        本文标题:golang实现简易http服务器以及关键函数分析

        本文链接:https://www.haomeiwen.com/subject/syqraftx.html