http效劳源码剖析

2019年7月2日11:04:33http效劳源码剖析已关闭评论 238

多读go的源码,能够加深对go言语的明白和认知,本日分享一下http相干的源码局部
在不运用第三方库的情况下,我们能够很轻易的的用go完成一个http效劳,

package main

import (
    "fmt"
    "net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world ! ")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    if err := http.ListenAndServe(":9100", nil); err != nil {
        panic(err)
    }
}

直接在浏览器里接见9100端口就能够返回 hello world !
go已把一切的细节封装好了,我们只需要本身去写Handler完成就够了。源码简朴来讲做了以下几件事:

  • 把我们自界说的Handler要领添加到默许路由DefaultServeMux的Map里好比:http.HandleFunc("/", IndexHandler) (btw: go言语的map黑白线程平安的,能够在http源码里看到官方的处置惩罚方式);
  • 启动一个tcp效劳监听9100端口,守候http挪用;
  • 当监听到有http挪用时,启动一个协程来处置惩罚这个要求,这个是go的http效劳快的一个重要原因,把要求内容转换成http.Request, 把以后衔接封装http.RespnseWriter;
  • 默许路由DefaultServeMux依据request的path找到相应的Handler,把 request和 responseWriter传给Handler 举行营业逻辑处置惩罚,response相应信息write给客户端;

ServeMux & Handler

http 包的默许路由 DefaultServeMuxServeMux 构造休的实例
http.HandleFunc("/", IndexHandler) 的挪用,会把path信息和自界说的要领信息生存到 DefaultServeMuxm map[string]muxEntry变量里
我们看一下ServeMux 的界说:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

ServeMux 中生存了pathHandler 的对应干系,也是路由干系。

Handler

muxEntry 中的 h Handler 对就的就是我们自界说的Handler要领好比,我们本身例子中的要领 func IndexHandler(w http.ResponseWriter, r *http.Request) 仔细的同砚可能会问 Handler是一个接口,然则我们只是界说了一个要领,这是怎样转换的呢?
接口Halder设置了署名划定规矩,也就是我们自界说的处置惩罚要领

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

go言语中一切的自界说范例都能够完成本身的要领,http包是用一个自界说的func往来来往完成了Handler接口,

type HandlerFunc func(ResponseWriter, *Request)

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

然后在ServerMux的要领HandleFunc处置惩罚的时刻会把 handler func(ResponseWriter, *Request) 转换成 HandlerFunc, 以下所示:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

ServerMux 构造中另有一个读写锁 mu sync.RWMutex mu就是用来处置惩罚多线程下map的平安接见的。

查找&挪用 Handler

获得自界说的handler要领,就是去map中依据path婚配获得Handler

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
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.  mux.es contains all patterns
    // that end in / sorted from longest to shortest.
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

ServeMux 完成了 Handler 接口,也是默许的路由挪用的详细划定规矩完成的处所,他的 ServeHTTP 要领处置惩罚方式就是获得自界说的handler要领,并挪用我们自界说的要领:

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

接口Halder设置了署名划定规矩,也就是我们自界说的处置惩罚要领
好比下面的代码,函数IndexHandler就是我们自界说的要领,返回给客户端要求一个 hello world ! 字符串。中心要求是怎样挪用到我们自界说的要领的详细逻辑都是http包供应的,然则一点也不神奇,

http.HandleFunc("/", IndexHandler)

// IndexHandler 我们本身界说的Handler要领
func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world ! ")
}
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
// 
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

http ListenAndServe

说完 ServeMux 是怎样连系 Handler 接口,来完成路由和挪用后,就要说一下,http效劳是怎样获得客户端传入的信息,封装requet和rresponse的。
在启动顺序的时刻http.ListenAndServe, 有两个参数,第一个是指写端口号,第二个是处置惩罚逻辑,若是我们没有给定处置惩罚逻辑,会运用默许的处置惩罚DefaultServeMux

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

ListenAndServe 要领翻开tcp端口举行监听,然后把Listener 传给srv.Serve要领

func (srv *Server) ListenAndServe() error {
    // 省略局部代码 ...
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

详细要说一下 Service 要领,这个要领中,对监听tcp要求,然后把要求的客户端衔接举行封装,

func (srv *Server) Serve(l net.Listener) error {
    // 省略局部代码 ...
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        // 省略局部代码 ...
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

把客户端的要求封装成一个Conn,然后启动一个协程go c.serve(ctx)来处置惩罚这个衔接要求,这就是http包快的一个重要原因,每个衔接就是一个协程。客户端能够先和效劳器举行衔接,然后应用这个conn来屡次发送http要求,如许,就能够削减每次的举行衔接而提高一些速率。像一些rpc里就是应用这点去完成的双向的stream流,好比我之前的帖子go微效劳框架go-micro深度进修(五) stream 挪用历程详解,他就是竖立一个tcp衔接,然后基于这个conn,发送多个request,返回屡次respose数据。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    // 省略局部代码 ...
    // 轮回读取要求 ...
    for {
        // 读取要求数据,封装response
        w, err := c.readRequest(ctx)
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        // 省略局部代码 ...
        // 路由挪用自界说的要领,把封装好的responseWrite和 request传到要领内
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        // 省略局部代码 ...
    }
}
avatar