golang启动http服务

golang的net/http包提供了启动一个http服务简单的方法,我们直接从调用流程追踪代码查看http包是怎样提供http服务的,下面是一个简单的deamo:

package main

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

func loginHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "login success")
}

func startServer(addr string) {
    http.HandleFunc("/login", loginHandler)
    log.Fatal(http.ListenAndServe(addr, nil))
}

func main() {
    startServer(":10080")
}

因为http是建立在tcp传输协议之上的,所以启动http服务器本质上是启动一个tcp服务器,只是需要对http应用层的协议进行解析;对http request的处理是根据request路径来选择处理方式的,上面代码也就做了这两件事:

  1. 注册http request的处理函数,这里是注册了/login的处理函数loginHandler
  2. 启动一个server接收http request,并根据request path找到注册的处理函数

接下来跟代码,目的是搞清楚http.HandleFunc()注册的处理函数保存到哪里了?server接收到http request后又是怎样找到对应的处理函数的。

一. Handler/HandlerFunc

首先介绍下Handler类型,因为后面分析代码需要它,看看它的声明:

1. Handler

// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

只要实现了ServeHTTP(w http.ResponseWriter, r *http.Request)就是Handler类型,ServeHTTP用来对回复http请求。

3. HandlerFunc

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

HandlerFunc本身是函数类型,实现了ServeHTTP()方法,同时ServeHTTP()内部调用的又是函数本身,HandlerFunc其实就是函数适配器。

二. DefaultServeMux

接下来看看是如何注册处理函数的

http.HandleFunc("/login", loginHandler)

调用的是DefaultServeMux的HandleFunc()函数

// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

对handler进行判空再处理,注意这里把传入的handler参数强制转换成HandlerFunc类型,HandlerFunc又实现ServeHTTP()方法,所以本质是把一个处理函数最终转换成Handler类型。

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

从函数原型可以知道DefaultServeMux是ServeMux类型的实例,我们看下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
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

知道了DefaultServeMux是ServeMux类型的实例,接下来看看DefaultServeMux是如何保存这些pattern和处理函数的。

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

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }
    if pattern[0] != '/' {
        mux.hosts = true
    }
}

原来是把pattern和Handler保存到DefaultServeMux的成员m中了,我们可以猜测匹配过程是通过pattern在m中找到对应Handler去处理http request,那es成员有什么用呢?这就要说道ServeMux的pattern匹配规则了

  1. 如果pattern不以/结尾,如:/index,则只会匹配/index的请求
  2. 如果pattern以/结尾,如:/index/,则该pattern能匹配/index/,/index/info,但是优先匹配长的路径
  3. 所以pattern/能匹配所有不能被其他pattern匹配的路径
  4. 如果pattern不以/结尾,如:/login,如果匹配不到/login,则会去匹配/login/

es保存的就是以/结尾的pattern,并且按字典从大到小排序,所以匹配时遍历es就能优先遍历较长的路径。
到这里注册处理函数就结束了,主要是把处理函数强制转成Handler类型,并把pattern和Handler保存到DefaultServeMux中,我们可以预料在某个时刻http server会通过DefaultServeMux找到对用的Handler

三. ListenAndServe

log.Fatal(http.ListenAndServe(addr, nil))

创建一个Server,监听addr中的地址和端口,注意这里的handler参数传的是nil,看来是默认会使用DefaultServeMux中的Handler,只是要搞清楚它们是怎么关联上的。

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

接下来就是传统的server建立了,开始监听,接收连接,处理请求

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

Serve函数比较长,主要是调用accept()创建连接,并启动一个goroutine去处理新连接的事件

func (srv *Server) Serve(l net.Listener) error {
  ......
  ......
  ......
    for {
        rw, err := l.Accept()
        if err != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := err.(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", err, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return err
        }
  ....
  ....
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        go c.serve(connCtx)
    }
}

这里accpet处理挺有意思,accept失败的时,如果不是致命错误,则依次休眠5ms,10ms,20ms....1s..1s..1s再进行accept()操作,想到的一个场景是文件句柄打开太多了导致accept()创建fd失败,sleep一段时间后系统回收一定的文件句柄,再次调用accpet()就能成功。
接下来看看goroutine是怎么处理http请求的,还是去除不相关代码

// 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())
    ....
    ....
    // HTTP/1.x from here on.

    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        w, err := c.readRequest(ctx)
        ....
        ....
        ....
        c.curReq.Store(w)
        // 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.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ....
        ....
        ....
    }
}

for{}循环中读取http请求,再把解析出来的数据保存到response对象w中,response类型实现了ResponseWriter的方法,最终调用到serverHandler{c.server}.ServeHTTP(w, w.req),继续看看serverHandler是怎么实现ServeHTTP方法的

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

还记得我们启动server时传的参数吗?http.ListenAndServe(addr, nil) handler参数传的就是nil,所以用DefaultServeMux作为handler,因为DefaultServeMux也实现了ServeHTTP方法,所以最终会调用到DefaultServeMux的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)
}

最终会调用到DefaultServeMux的match()方法,并返回真正处理http请求的Handler

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, ""
}

可以看到首先从m中完全匹配pattern,如果匹配不到再到es中去匹配。

实现Handler结构的类型有:


image.png
  1. HandlerFunc类型是个函数适配器,把处理函数转成Handler类型
  2. ServeMux是DefaultServeMux的类型,用来通过path找到对应的handler
  3. serverHandler类型只有一个Server成员,http服务器就是通过Server中handler成员去处理http请求的,如果handler成员为nil,则通过DefaultServeMux去处理

下面是http.HandleFunc(path, handler)的流程图:


image.png

下面是http.ListenAndServe(addr, nil)的流程图:


image.png

除了可以用默认的DefaultServeMux作为http复用器之外,还能自定义实现一个,只要实现ServeHTTP()方法就行,因为本质上是传入一个Handler实例到http.ListenAndServe(string, Handler)

package main

import (
    "fmt"
    "net/http"
)

type MyServeMux struct{}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "loginHandler\n")
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "indexHandler\n")
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "defaultHandler\n")
}

func (m *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/login":
        loginHandler(w, r)
    case "/index":
        indexHandler(w, r)
    default:
        defaultHandler(w, r)
    }
}

func startServer(addr string) {
    http.ListenAndServe(addr, &MyServeMux{})
}

func main() {
    startServer(":12345")
}

用curl测试结果如下:

$$ curl -X GET http://localhost:12345/login
loginHandler
$$:~$ curl -X GET http://localhost:12345/index
indexHandler
$$:~$ curl -X GET http://localhost:12345/
defaultHandler
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容