golang自建HTTP服务器

Posted by cwen 2015-11-20

golang作为二十一世纪的编程语言,让我们一起看看golang是如何实现自己的http服务器

go封装http服务器简单实例

让我们直接来看代码

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)
    err := http.ListenAndServe(":12345", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

没错就是这么简单运,一个http服务器就搭建成功,只要调用http包的两个函数就可以了。
这里首先调用的是http.HandleFunc函数,函数签名如下

//HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。
//ServeMux的文档解释了模式的匹配机制。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

接着就是设置监听端口,使用的是http.ListenAndServer。函数签名如下

//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
//handler参数一般会设为nil,此时会使用DefaultServeMux。
func ListenAndServe(addr string, handler Handler) error

看到这里我相信大家脑子里会有很多疑惑...那么我们就接着往下探讨
我们先从ListenAndServe这个函数看起,看看它到底为我们做了什么

ListenAndServer深入探究

我们先从源码看起

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

从函数的参数来看,这个函数传入俩个参数,addrhandler,很明显addr是我们想要监听的端口地址,第二的参数是一个Handler,通过查看文档得知,它是一个只包含了ServeHTTP(ResponseWriter, *Request)的接口,也就说只要某个struct有``ServeHTTP(ResponseWriter, *Request)这个方法,那这个struct就自动实现了Handler接口。 显示什么网页取决于第二个参数HanderHander又只有1个ServeHTTP 所以可以证明,显示什么网页取决于ServeHTTP 那就ServeHTTP方法,他需要2个参数,一个是http.ResponseWriter,另一个是http.Requesthttp.ResponseWriter`写入什么内容,浏览器的网页源码就是什么内容
http.Request里面是封装了,浏览器发过来的请求(包含路径、浏览器类型等等)

接下来让我来实现一个自己Handler

package main

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

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "myHandler Hello Word")
}

func main() {

    err := http.ListenAndServe(":12345", &myHandler{})
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

http.ListenAndServe(":12345", &myHandler{})后,开始等待有访问请求
一旦有访问请求过来,http包帮我们处理了一系列动作后,最后他会去调用myHandlerServeHTTP这个方法,并把自己已经处理好的http.ResponseWriter, *http.Request传进去
myHandlerServeHTTP这个方法,拿到*http.ResponseWriter后,并往里面写东西,客户端的网页就显示出来了

我接着还是回到ListenAndServe这个函数上,看看这个函数到底为我们干了些什么呢?
这个底层其实这样处理的:初始化一个server对象,然后调用了server.ListenAndServe()
我们跳到server.ListenAndServe()

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":http" is used.
 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),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。

我们在看看Serve函数,

// 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.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    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, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve()
    }
}

监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了srv.Serve(net.Listener)函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个for{},首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:go c.serve()。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。

那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:c.readRequest(),然后获取相应的handler:handler := c.server.Handler,也就是我们刚才在调用函数ListenAndServe时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取handler = DefaultServeMux,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了http.HandleFunc("/", HelloServer)嘛。这个作用就是注册了请求/的路由规则,当请求uri"/",路由就会转到函数HelloServerDefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用HelloServer本身,最后通过写入response的信息反馈到客户端。

http连接处理流程(图片摘自go web编程)

路由处理

实际上我们在前边也多多少少谈到了路由,前面有说到实现Handler接口的struct,没错我们可以在这个structServeHTTP函数中进行路由判断
来看下面这个例子

package main

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

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := r.URL.String()
    switch path {
    case "/":
        io.WriteString(w, "<h1>root</h1><a href=\"abc\">abc</a>")
    case "/abc":
        io.WriteString(w, "<h1>abc</h1><a href=\"/\">root</a>")
    }
}

func main() {

    err := http.ListenAndServe(":12345", &myHandler{})
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

没错每一个case就是一个页面,那么问题来了如果一个网站有上百个页面,肿么办?上百个case?答案是否定的(其实上百个case是可以实现)。那我们该肿么做呢?

那接下来看看ServeMux吧....
其实ServeMax存在一张map表,map里的key记录的是r.URL.String(),而value记录的是一个方法,这个方法和ServeHTTP是一样的,这个方法有一个别名,叫HandlerFunc
ServeMux还有一个方法名字是Handle,他是用来注册HandlerFunc
ServeMux还有另一个方法名字是ServeHTTP,这样ServeMux是实现Handler接口的,否者无法当http.ListenAndServe的第二个参数传输
我们直接上源码

type ServeMux struct {
    mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  //路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有host信息
}

再看 muxEntry

type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个handler
    pattern  string  //匹配字符串
}

你是不是有一个疑问?那就是我们什么时候用ServeMax?接下来我就为你解答这和问题
其实当我们在调用http.ListenAndServe(":12345", nil)的时候,第2个参数是nil时
http内部会自己建立一个叫DefaultServeMuxServeMux,因为这个ServeMuxhttp自己维护的,如果要向这个ServeMux注册的话,就要用http.HandleFunc这个方法

现在我们在看最一开始的http.HandleFunc("/hello", HelloServer) 就会问HelloServer 不是没有实现Handler接口吗?(其实这个地方我也纠结了好久,后来找了源码以及看了好几篇大牛的博客才明白过来)
其实在http中存在这样一个类型

type HandlerFunc func(ResponseWriter, *Request)

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

没错它经过一次类型转换,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

我们现在不使用DefaultServeMux,来自己实现一个ServeMax,直接上代码

package main

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

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "My server: "+r.URL.String())
}

func sayBye(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Bye bye, this is version 2.")
}

func main() {
    mux := http.NewServeMux()

    mux.Handle("/", &myHandler{})

    // 使用函数作为 handler
    mux.HandleFunc("/bye", sayBye)

    err = http.ListenAndServe(":12345", mux)
    if err != nil {
         log.Fatal("ListenAndServe: ", err)
    }
}

到这里我们的golang版的http服务器就探究结束了,如果读者有兴趣还可以更深入的探究,尝试实现自己的Server

博文参考了 asta谢的《go web编程》 以及 waynehu 的 go语言的http包 的这篇博客,在此感谢这两位作者
如有发现什不正确或是有疑问的地方,欢迎留言或是发邮件联系博主
如有转载请注明出处