go 语言 http 服务器的实现

dongkui0712 · 2017-12-12 09:22:52 · 1758 次点击 · 预计阅读时间 7 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2017-12-12 09:22:52 的文章,其中的信息可能已经有所发展或是发生改变。

本文总结分析 go 1.9 http 服务器的实现

hello world

可以用如下代码实现简单的 go 语言 HTTP 服务的 hello world:

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

最简单的 golang HTTP 服务器由 http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)) 做路由分发, http.ListenAndServe(addr, hanlder) 开启监听并启动服务。

http.HandleFunc

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

HandleFunc 函数在 DefaultServeMux 中为指定的 pattern 注册 HTTP 服务处理函数 func(ResponseWriter, *Request) ,即由该 handler 处理对 pattern 的请求。

http.ListenAndServe

代码如下:

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

ListenAndServe 监听 TCP 地址,调用 Server 处理该连接中的请求。如果 handler 为空,则使用 DefaultServeMux。

该函数,其实是默认 Server 的 ListenAndServe 方法的封装。

ServeMux

ServeMux 是一个 HTTP 请求多路转接器。它将收到的 HTTP 请求的 URL 和已经注册的 pattern 做匹配,并调用最匹配的 pattern 对应的 hanlder 来处理该请求。

pattern 是固定的、以跟路径符号 / 开始的路径,如 "/favicon.ico"。也可以是跟路径的子树,如 "/images/"。匹配时,长 pattern 优先级更高。

/ 结尾的 pattern 定义了一个子树。/ 本身可以匹配任何 pattern。

如果已经注册了一个如 /images/ 的子树 pattern,接收到一个如 /images 的不带最后 / 符号的请求时,ServeMux 会将该请求重定向到子树 pattern /images/ 对应的处理器 handler(除非 /images 被单独注册)。

pattern 可以可选地指定域名。指定域名的 pattern 只会匹配该域名的 URL。指定域名的 pattern 优先级高于未指定的。

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
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux 的 ServeHTTP 方法分发 HTTP 请求到与其 URL 匹配度最高的 pattern 注册的处理器 h:mux.Handler(r) 返回匹配度最高的处理器 h,然后调用 h.ServeHTTP(w, r) 处理请求。

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

Server

Server 结构体定义启动 HTTP 服务器的参数

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)

    ErrorLog *log.Logger
}

此处的 Handler 是个接口,意味着只要实现 ServeHTTP(ResponseWriter, *Request) 方法,就能作为 Server 的 Handler:

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

ListenAndServe 方法

ListenAndServe 方法监听 Addr 字段指定的 TCP 连接,处理请求。接收的连接激活了 TCP keep-alives。如果 Addr 为空,则使用 ":http"。总是返回非空 error。

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

ListenAndServe 方法的实现主要有两个步骤:

  • net.Listen("tcp", addr) 得到 net.Listener
  • Serve 方法并发处理监听到的请求

下面看看 Serve 方法实现的主要步骤(只保留主要逻辑):

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

    ...

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, _ := l.Accept()
        c := srv.newConn(rw)
        go c.serve(ctx)
    }
}

最终,serve 方法调用 Server 中的 Handler 的 ServeHTTP 方法处理该请求。

注册函数

Handle 和 HandleFunc 将 HTTP 请求处理函数注册到 ServeMux 中

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

使用方式略有差别。

HandleFunc(pattern, handler)Handle(pattern, HandlerFunc(handler)) 效果一样。例如:

http.Handle("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}))

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

也可以从 ServeMux.HandleFunc 的实现看出来:

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

比较

http 包有几个概念易混,特别分析对比一下。

HandlerFunc

HandlerFunc 是一种类型,是以 func(ResponseWriter, *Request) 申明的函数。是个将普通函数转化为 HTTP 处理器的适配器。

type HandlerFunc func(ResponseWriter, *Request)

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

用法:

定义普通函数 handlerFunc 如下,HandlerFunc(f) 即是一个调用 f 的 HTTP 处理器。

func f(w http.ResponseWriter, r *http.Request) { ... }

Handler

Handler 是个接口。功能如其名:处理器。

http 包中实现 ServeHTTP(ResponseWriter, *Request) 方法即拥有处理 HTTP 请求的能力。

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

HandlerFunc 类型和 ServeMux 结构体都实现了 ServeHTTP(w ResponseWriter, r *Request) 方法, 也就是说,HandlerFunc 类型和 ServeMux 结构体都是一种 Handler。

HandlerFunc 的 ServeHTTP 方法直接调用自身,处理 HTTP 请求。

ServeMux 调用与请求路径匹配的 func(ResponseWriter, *Request) 类型的处理函数。

同样的,我们可以自己定义一个实现 ServeHTTP(ResponseWriter, *Request) 方法的结构体,定制我们自己的 HTTP 处理器。第三方的路由处理器(如 mux, httprouter 等)都是 Handler 的一种实现。

Handle 和 HandleFunc 的作用就是将 HTTP 请求处理函数注册到 ServeMux 中。


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1758 次点击  
加入收藏 微博
上一篇:golang
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传