golang的webserver是如何工作的

我们知道golang实现一个webserver非常简单,但是其内部是如何工作的呢,我们深入探究一下其原理。

实现一个webserver服务

下面我们就用golang内置的服务实现一个简单的webserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

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

func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不会解析的
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}

func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由
http.HandleFunc("/hello", sayhelloName) //设置访问的路由
err := http.ListenAndServe(":8090", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

我们可以通过go run main.go来开启Server服务, 当我们访问http://localhost:8090/http://localhost:8090/hello都会得到Hello astaxie!, 也就是都执行了sayhelloName函数。
下面让我们来分析一下服务的代码:
首先我们从main函数入口进入程序执行,首先执行了http.HandleFunc("/", sayhelloName)http.HandleFunc("/hello", sayhelloName)两个方法,这两个方法其实就是设置路由及其对应的处理函数。
然后执行http.ListenAndServe(":8090", nil)这个函数开始监听8090端口并把用户的请求根据之前设置的路由规则交给特定的函数进行处理。
下面我将针对这两个函数进行深入的分析。

http.HandleFunc

这个函数是net/http包中定义的, 第一个参数patternstring类型,表示匹配的URL, 第二个参数handler这是个函数类型,表示一个处理函数。其定义在net/http/server.go中,第一如下:

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

这个函数调用了下面这个函数:

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

HandelrFunc定义如下, 声明为一个函数类型, HandlerFunc(handler)就是把handler强制类型转化为HandlerFunc类型

1
type HandlerFunc func(ResponseWriter, *Request)

mux.Handle的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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)
}

if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

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

// 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, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}

可以看出这个函数会把patternhandler的对应关系读存储到mux.m这个map里了,mux类型是ServeMux,其定义如下:

1
2
3
4
5
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}

经过上面的处理后通过http.HandleFunc设置的patternhandler的对应关系都被存储到了DefaultServeMux这个对象的m中。

http.ListenAndServe

这个函数也是在net/http/server.go中定义的,其定义如下:

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

上面函数最终对调用到下面这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure

if err := srv.setupHTTP2_Serve(); err != nil {
return err
}

srv.trackListener(l, true)
defer srv.trackListener(l, false)

baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
for {
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
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
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}

这里通过一个for循环不停的接收请求l.Accept()来得到接收的请求,然后再通过go c.serve(ctx)进行请求的处理。这里用到了协程,也就是每个请求其实是由单独的协程进行处理的,这也是golang作为webserver高效的原因所在。c.serve函数中有一个for循环,会不断读取同一个请求的数据,直到出现问题或者正确读取完毕。读取完请求后会调用serverHandler{c.server}.ServeHTTP(w, w.req)这个函数来处理请求。这个函数定义如下:

1
2
3
4
5
6
7
8
9
10
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 为 nil:

可以看到当我们不在ListenAndServe中传递handler时,也就是sh.srv.Handler = nilhanlder=DefaultServeMux,这个 DefaultServeMux正式我们前面通过http.HandleFunc来设置的。 下面调用了hanlder.ServeHTTP,这里也就是调用了DefaultServeMux.ServeHTTP, 这个函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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)
}

这个函数中的mux.Handler从请求r中找到请求的URL然后在去mux.m的map结构中找到对应的映射关系从而得出h这个处理函数名。
由于上面说过h是转换为类型HandlerFunc, 这个类型定义的ServeHTTP函数如下:

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

所以调用h.ServeHTTP(w,r)就等于调用h(w,r),也就是我们调用我们自己的写的处理函数。
这些都完成后会执行收尾工作,并把得到的结构返回给请求用户。

当 handler 不为 nil:

这时调用h.ServerHTTP(w,r)其实就是调用自己传入的handlerServerHTTP函数,例如web框架revel的源码github.com/revel/cmd/harness/harness.go中执行revel run app是就会执行下面的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Run the harness, which listens for requests and proxies them to the app
// server, which it runs and rebuilds as necessary.
func (h *Harness) Run() {
var paths []string
if revel.Config.BoolDefault("watch.gopath", false) {
gopaths := filepath.SplitList(build.Default.GOPATH)
paths = append(paths, gopaths...)
}
paths = append(paths, revel.CodePaths...)
watcher = revel.NewWatcher()
watcher.Listen(h, paths...)

go func() {
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
revel.INFO.Printf("Listening on %s", addr)

var err error
if revel.HTTPSsl {
err = http.ListenAndServeTLS(
addr,
revel.HTTPSslCert,
revel.HTTPSslKey,
h)
} else {
err = http.ListenAndServe(addr, h)
}
if err != nil {
revel.ERROR.Fatalln("Failed to start reverse proxy:", err)
}
}()

// Kill the app on signal.
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt, os.Kill)
<-ch
if h.app != nil {
h.app.Kill()
}
os.Exit(1)
}
```
这里也调用了`http.ListenAndServe`但是第二个参数`hanlder`传入了`h`,所以最终会调用`h.ServerHTTP`函数, 这个函数`revel`中是这么实现的:

```golang

// ServeHTTP handles all requests.
// It checks for changes to app, rebuilds if necessary, and forwards the request.
func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
// Reverse proxy the request.
// (Need special code for websockets, courtesy of bradfitz)
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
proxyWebsocket(w, r, h.serverHost)
} else {
h.proxy.ServeHTTP(w, r)
}
}