Go搭建简单服务器及http包源码分析

Go搭建web服务器

使用go语言搭建一个简单的web服务器是非常方便的,一个简单的例子如下:

// main.go
package main

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

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!") // 这个写入到w的是输出到客户端的
}

func main() {
    http.HandleFunc("/", sayhelloName) // 设置访问的路由
    port := ":9090"  // 设置监听的端口
    err := http.ListenAndServe(port, nil) // 开始监听
    if err != nil {
        log.Fatal("ListenAndServe: ", err)  // 输出错误日志
    }
    fmt.Printf("Server listen at: %s", port)
}

运行服务器:

$ go run main.go

运行后,服务器已经在9090端口开始监听http请求了

使用curl工具访问,并查看详细情况:

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET / HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 15 Nov 2017 08:32:16 GMT
< Content-Length: 6
< Content-Type: text/plain; charset=utf-8
< 
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
Hello!

可以看到,想要实现一个简单的web服务器,仅仅需要调用net/http包中的两个函数HandleFunc()ListenAndServe()

http包源码分析

下面通过阅读http包源码,并结合上面的例子,详细分析一下,只通过调用HandleFunc()ListenAndServe()两个函数,是怎样使一个web服务器运行起来的。

HandleFunc()
HandleFunc()用于注册路由。在上面的的例子中,即指定:当路径为”/”时,使用sayhelloName()来处理。

// server.go line 2313
// 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)
}

源码中调用了DefaulServeMux的HandleFunc,那么再看看DefaultServeMux是什么:

// server.go line 2116
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

定义了ServeMux如下:

// server.go line 2101
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是一个http request的多路复用器(即相当于路由),它根据request的URL,与已经注册的所有patterns相匹配,并根据匹配结果,调用对应的Handler。ServeMux中利用map[string]muxEntry来进行匹配。对于每一个已经注册的pattern(string类型),都能找到一个对应的muxEntry,也就能找到相应的Handler。

再来看一下Handler:

// server.go line 82
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Handler是一个接口类型,所有实现了ServeHTTP的类型都可以作为一个Handler。

上文提到HandleFunc()在注册路由时是调用了DefaulServeMux的HandleFunc(),阅读了ServeMux的源码后,现在来看一下HandleFunc()是什么:

// server.go line 2300
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

回顾上文,例子中的sayHelloName()似乎没有实现ServeHTTP,却也能实现接口?这是因为,看以上这部分代码,在将函数传递到下一层时,使用了一次强制类型转换。由于sayHelloName()与HandlerFunc()有相同的参数,因此完成了转换,也就实现了接口。
HandlerFunc()相关源码如下:

// server.go line 1910
// 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)
}

HandleFunc把上一层的两个参数继续传递到mux.Handle()。

//server.go line 2257
// 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 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
    }

    ...

    }
}

由于代码较长,我们在这里只看一下最基本的功能部分,省略了一些异常处理,重定向等相关的代码。可以看到,这里注册路由的功能就真正实现了,在map中加入了对应的(patterm, MuxEntry)项,且MuxEntry中存储了对应的pattern和Handler。

以上就是路由注册的过程,也就是HandleFunc()的实现。

假设map中注册好了路由规则后,下面看一下接受到请求后,Handler是如何匹配的:
前面我们提到了这个需要被实现的函数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)
}

简单的处理之后,将request传递到mux.Handler():

// server.go line 2203
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)
    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    return mux.handler(host, r.URL.Path)
}

处理参数后,继续传递到mux.Hanler(),但这时参数内容已经改变:

// server.go line 2226
unc (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
}

这里,就真正完成了patter和handler的匹配。可以看到,这就是在我们上文分析的ServeMux中的map来进行匹配的。

ListenAndServe()
完成了路由相关规则的处理后,开始监听端口。

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

ListenAndServe()创建了一个server,并调用它的ListenAndServe()。Server结构体由于代码太长,我们只分析server.IstenAndServe():

// server.go line 2627
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()中,在传输层发起了tcp的监听。
再看看server.Serve():

// server.go line 2678
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()

    ...

    for {
        rw, e := l.Accept()
        if e != nil {

            ...

        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

同样,这里只保留了核心代码,把有关的异常处理,时延等相关代码都省略。可以看到,在一个for循环中,客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息。使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。

至此,例子中的web服务器,基本的代码运行流程就已经分析完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值