Go搭建web服务器
使用go语言搭建一个简单的web服务器是非常方便的,一个简单的例子如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 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)
}
运行服务器:1
$ go run main.go
运行后,服务器已经在9090端口开始监听http请求了
使用curl工具访问,并查看详细情况:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16* 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()
来处理。1
2
3
4
5
6
7// 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是什么:1
2
3
4
5// server.go line 2116
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
定义了ServeMux如下:1
2
3
4
5
6
7
8
9
10
11
12// 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:1
2
3
4// server.go line 82
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler是一个接口类型,所有实现了ServeHTTP的类型都可以作为一个Handler。
上文提到HandleFunc()在注册路由时是调用了DefaulServeMux的HandleFunc(),阅读了ServeMux的源码后,现在来看一下HandleFunc()是什么:1
2
3
4
5// 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()相关源码如下:1
2
3
4
5
6
7
8
9
10
11// 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()。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//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():1
2
3
4
5
6
7
8
9
10
11func (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():1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 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(),但这时参数内容已经改变:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 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():
完成了路由相关规则的处理后,开始监听端口。1
2
3
4
5// 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():1
2
3
4
5
6
7
8
9
10
11
12// 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():1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 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服务器,基本的代码运行流程就已经分析完了。
本文为博主原创文章,转载请注明出处。