Go语言设计与实现 — http服务器编程

  • Post author:
  • Post category:其他




Go http服务器编程



初始

http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。

golang 的标准库

net/http

提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和

http.request



http.ResponseWriter

两个对象交互就行。也就是说,我们只要写一个 handler,请求会通过参数传递进来,而它要做的就是根据请求的数据做处理,把结果写到 Response 中。废话不多说,来看看 hello world 程序有多简单吧!

我们有两种写法,现在来看一下这两种写法:

type helloHandler struct {
}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, world!"))
}

func main() {
	http.HandleFunc("/way1", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write([]byte("Hello, world!"))
	})
	http.Handle("/way2", &helloHandler{})
	http.ListenAndServe("localhost:8080", nil)
}

我们先把注意力聚焦到

/way2

上,先暂时不看

/way1

正如上面程序展示的那样,我们只要实现的一个 Handler,它的

接口原型

是(也就是说只要实现了

ServeHTTP

方法的对象都可以作为 Handler):

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

然后,注册到对应的路由路径上就 OK 了。


http.HandleFunc

接受两个参数:第一个参数是字符串表示的 url 路径,第二个参数是该 url 实际的处理对象。


http.ListenAndServe

监听在某个端口,启动服务,准备接受客户端的请求(第二个参数这里设置为

nil

,这里也不要纠结什么意思,后面会有讲解)。每次客户端有请求的时候,把请求封装成

http.Request

,调用对应的 handler 的

ServeHTTP

方法,然后把操作后的

http.ResponseWriter

解析,返回到客户端。



封装


/way2

没有什么问题,但是有一个不便:每次写 Handler 的时候,都要定义一个类型,然后编写对应的

ServeHTTP

方法,这个步骤对于所有 Handler 都是一样的。重复的工作总是可以抽象出来,

net/http

也正这么做了,它提供了

http.HandleFunc

方法,允许直接把特定类型的函数作为 handler。于是

/way2

可以改成

/way1

的方法。


但是实际上/way1的本质还是/way2这种方法

我们看一下源码就可以知道了:


Handler

是一个接口:

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

Handler 接口中声明了名为 ServeHTTP 的函数签名,也就是说任何结构只要实现了这个 ServeHTTP 方法,那么这个结构体就是一个 Handler 对象。其实 go 的 http 服务都是基于 Handler 进行处理,而 Handler 对象的 ServeHTTP 方法也正是用以处理 request 并构建 response 的核心逻辑所在。

我们现在回到上面的

HandleFunc

函数,注意一下这个代码:

mux.Handle(pattern, HandlerFunc(handler))

可能有人认为 HandlerFunc 是一个函数,包装了传入的 handler 函数,返回了一个 Handler 对象。然而这里 HandlerFunc 实际上是将 handler 函数做了一个类型转换,看一下 HandlerFunc 的定义:

type HandlerFunc func(ResponseWriter, *Request)

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

HandlerFunc 是一个类型,只不过表示的是一个具有

func(ResponseWriter, *Request)

签名的函数类型,并且这种类型实现了 ServeHTTP 方法(在 ServeHTTP 方法中又调用了自身),也就是说这个类型的函数其实就是一个 Handler 类型的对象。利用这种类型转换,我们可以将一个 handler 函数转换为一个

Handler

对象,而不需要定义一个结构体,再让这个结构实现

ServeHTTP

方法。读者可以体会一下这种技巧。



路由

虽然上面的代码已经工作,并且能实现很多功能,但是实际开发中,HTTP 接口会有许多的 URL 和对应的 Handler。这里就要讲

net/http

的另外一个重要的概念:

ServeMux



Mux



multiplexor

的缩写,就是多路传输的意思(请求传过来,根据某种判断,分流到后端多个不同的地方)。

ServeMux

可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。我们还是来看例子吧:

func helloHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello, world\n")
}

func echoHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, r.URL.Path)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", helloHandler)
	mux.HandleFunc("/", echoHandler)
	http.ListenAndServe("localhost:8080", mux)
}

这个服务器的功能也很简单:如果在请求的 URL 是

/hello

,就返回

hello, world!

;否则就返回 URL 的路径,路径是从请求对象

http.Requests

中提取的。

这段代码和之前的代码有两点区别:

  1. 通过

    NewServeMux

    生成了

    ServerMux

    结构,URL 和 handler 是通过它注册的

  2. http.ListenAndServe

    方法第二个参数变成了上面的

    mux

    变量

还记得我们之前说过,

http.ListenAndServe

第二个参数应该是 Handler 类型的变量吗?这里为什么能传过来

ServeMux

?嗯,估计你也猜到啦:

ServeMux

也是是

Handler

接口的实现,也就是说它实现了

ServeHTTP

方法,我们来看一下:

type ServeMux struct {
        // contains filtered or unexported fields
}

func NewServeMux() *ServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

哈!果然,这里的方法我们大都很熟悉,除了

Handler()

返回某个请求的 Handler。

Handle



HandleFunc

这两个方法

net/http

也提供了,后面我们会说明它们之间的关系。而

ServeHTTP

就是

ServeMux

的核心处理逻辑:**根据传递过来的 Request,匹配之前注册的 URL 和处理函数,找到最匹配的项,进行处理。**可以说

ServeMux

是个特殊的 Handler,它负责路由和调用其他后端 Handler 的处理方法。

  • URL 分为两种,末尾是

    /

    :表示一个子树,后面可以跟其他子路径; 末尾不是

    /

    ,表示一个叶子,固定的路径


  • /

    结尾的 URL 可以匹配它的任何子路径,比如

    /images

    会匹配

    /images/cute-cat.jpg
  • 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
  • 如果没有找到任何匹配项,会返回 404 错误

  • ServeMux

    也会识别和处理

    .



    ..

    ,正确转换成对应的 URL 地址

你可能会有疑问?我们之间为什么没有使用

ServeMux

就能实现路径功能?那是因为

net/http

在后台默认创建使用了

DefaultServeMux



深入



Server

首先来看

http.ListenAndServe()

:

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

这个函数其实也是一层封装,创建了

Server

结构,并调用它的

ListenAndServe

方法,那我们就跟进去看看:

// 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
    ......
}

// 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

保存了运行 HTTP 服务需要的参数,调用

net.Listen

监听在对应的 tcp 端口,

tcpKeepAliveListener

设置了 TCP 的

KeepAlive

功能,最后调用

srv.Serve()

方法开始真正的循环逻辑。我们再跟进去看看

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
         // 创建 Conn 连接
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
         // 启动新的 goroutine 进行处理
        go c.serve()
    }
}

最上面的注释也说明了这个方法的主要功能:

  • 接受

    Listener l

    传递过来的请求
  • 为每个请求创建 goroutine 进行后台处理
  • goroutine 会读取请求,调用

    srv.Handler
func (c *conn) serve() {
    origConn := c.rwc // copy it before it's set nil on Close or Hijack

      ...

    for {
        w, err := c.readRequest()
        if c.lr.N != c.server.initialLimitedReaderSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }

         ...

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining.  We could let them all process
        // in parallel even if their responses need to be serialized.
        serverHandler{c.server}.ServeHTTP(w, w.req)

        w.finishRequest()
        if w.closeAfterReply {
            if w.requestBodyLimitHit {
                c.closeWriteAndWait()
            }
            break
        }
        c.setState(c.rwc, StateIdle)
    }
}

看到上面这段代码

serverHandler{c.server}.ServeHTTP(w, w.req)

这一句了吗?它会调用最早传递给

Server

的 Handler 函数:

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

哇!这里看到

DefaultServeMux

了吗?如果没有 handler 为空,就会使用它。

handler.ServeHTTP(rw, req)

,Handler 接口都要实现

ServeHTTP

这个方法,因为这里就要被调用啦。

也就是说,无论如何,最终都会用到

ServeMux

,也就是负责 URL 路由的家伙。前面也已经说过,它的

ServeHTTP

方法就是根据请求的路径,把它转交给注册的 handler 进行处理。这次,我们就在源码层面一探究竟。



ServeMux

我们已经知道,

ServeMux

会以某种方式保存 URL 和 Handlers 的对应关系,下面我们就从代码层面来解开这个秘密:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry  // 存放路由信息的字典!\(^o^)/
    hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

没错,数据结构也比较直观,和我们想象的差不多,路由信息保存在字典中,接下来就看看几个重要的操作:路由信息是怎么注册的?

ServeHTTP

方法到底是怎么做的?路由查找过程是怎样的?

// 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 pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

    // 创建 `muxEntry` 并添加到路由字典中
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }

    // 这是一个很有用的小技巧,如果注册了 `/tree/`, `serveMux` 会自动添加一个 `/tree` 的路径并重定向到 `/tree/`。当然这个 `/tree` 路径会被用户显示的路由信息覆盖。
    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
            // In pattern, at least the last character is a '/', so
            // strings.Index can't be -1.
            path = pattern[strings.Index(pattern, "/"):]
        }
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}
    }
}

路由注册没有什么特殊的地方,很简单,也符合我们的预期,注意最后一段代码对类似

/tree

URL 重定向的处理。

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

好吧,

ServeHTTP

也只是通过

mux.Handler(r)

找到请求对应的 handler,调用它的

ServeHTTP

方法,代码比较简单我们就显示了,它最终会调用

mux.match()

方法,我们来看一下它的实现:

// Does path match pattern?
func pathMatch(pattern, path string) bool {
    if len(pattern) == 0 {
        // should not happen
        return false
    }
    n := len(pattern)
    if pattern[n-1] != '/' {
        return pattern == path
    }
    // 匹配的逻辑很简单,path 前面的字符和 pattern 一样就是匹配
    return len(path) >= n && path[0:n] == pattern
}

// Find a handler on a handler map given a path string
// Most-specific (longest) pattern wins
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
         // 最长匹配的逻辑在这里
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}


match

会遍历路由信息字典,找到所有匹配该路径最长的那个。路由部分的代码解释就到这里了,最后回答上面的一个问题:

http.HandleFunc



ServeMux.HandlerFunc

是什么关系?

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

// 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

调用对应的方法,到这里上面的一切都串起来了!



版权声明:本文为qq_61039408原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。