jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

深入Golang之http

作者:jasper | 分类:Golang | 标签:   | 阅读 119 次 | 发布:2017-12-10 8:43 p.m.

Golang作为一个适用于高并发场景的语言,非常适合用以构建web服务。而且基于Golang的HTTP模块可以很方便地来实现,因为他自带了路由注册,连接处理等功能,这也是现在开源界Golang的web框架如此多的原因。下面我们就来看看Golang中HTTP的实现,从中我们也可以学习到Golang编程的一下小技巧。

从一个小例子开始

前言里面说了用Golang实现一个HTTP server很简单,那我们来看看一个简单的例子:

func main() {
    handler := func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello world\n"))
    }
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

我们使用了http包里面的两个函数HandleFuncListenAndServe就实现了一个带有路由功能的HTTP服务,下面我们通过源码来了解一下其内部实现。

ListenAndServe

首先来看ListenAndServe的实现,在http.ListenAndServe时候,会先生成一个Server对象,这也是我们常说的http的服务端:

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

最后调用的是Server的ListenAndServe方法,那我们有必要来看一下Server包含的内容:

type Server struct {
    Addr      string   
    Handler   Handler    
    TLSConfig *tls.Config 
    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    ConnState func(net.Conn, ConnState)

    disableKeepAlives int32     
    inShutdown        int32  
    nextProtoOnce     sync.Once 
    nextProtoErr      error    

    mu         sync.Mutex
    listeners  map[net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()

具体这些字段表示的内容就不赘述了,直接看名字应该就能清楚了,下面我们来看其ListenAndServe方法:

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

其中会创建一个TCP的Listener,用于接收客户端的数据,然后把这个Listener作为参数传给Serve:

func (srv *Server) Serve(l net.Listener) error {
    //......

    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        // .....
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew)
        go c.serve(ctx)
    }
}

里面的一个循环就通过传进来的listener接收来自客户端的请求并建立连接srv.newConn,然后为每一个连接创建routine执行c.serve(),对接收到的数据的真正的处理也就是在这个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)
        // 接下来是几个return的条件
    }   
}

这里省略了很多,我们只来看最关键的代码部分,就是serverHandler{c.server}.ServeHTTP(w, w.req),即根据注册的不同的handler来处理不同的请求:

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

接下来我们着重来看一下这个handler的原理。

Handler

基于Golang中对于接口的实现,Handler的定义如下:

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

任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。那我们来看一下代码里面哪些实现了这个ServeHTTP方法呢:

  • redirectHandler //跳转到指定url
  • ServeMux // 不是用来处理request和respone,而是用来找到路由注册的handler
  • serverHandler // 调用注册handler的ServeHTTP,如果没有默认为DefaultServeMux
  • timeoutHandler // 设置了timeout
  • globalOptionsHandler // 处理OPTIONS请求
  • initNPNRequest // 处理NPN请求
  • HandlerFunc // 让普通函数作为handler

下面我们会着重来介绍ServeMux和HandlerFunc.

ServerMux的数据结构:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool 
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux结构中字段m,这是一个map,key是一些url的pattern,value是一个muxEntry结构,后者里定义存储了具体的url的pattern和handler。

而HandleFunc选取了DefaultServeMux作为多路复用:

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

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

mux.Handle里面的逻辑其实就是把handler加入到m中去,注册过程也就结束了。

现在我们再回到上面的请求接收之后的处理逻辑,即调用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)
}

这里的mux.Handler(r)就是从上面的m中匹配到具体的handler,然后再运行对应的handler的ServeHTTP:

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

在f函数中把response写到http.RequestWirter对象返回给客户端。f函数运行结束即serverHandler{c.server}.ServeHTTP(w, w.req)运行结束。接下来就是对请求处理完毕之后和连接断开的相关逻辑,这里就不多说了。

至此,完整的http服务运行完毕了,包括注册路由,开启监听,处理连接,路由处理函数。


转载请注明出处:http://www.opscoder.info/golang_http.html

【上一篇】 使用InfluxDB监控kafka
【下一篇】 深入Golang之内存管理
其他分类: