Web 基础
Go 搭建一个 Web 服务器
Go 搭建一个 Web 服务器
使用 http
包可以轻松的搭建一个 Web 服务器:
http.HandleFunc
函数:用于绑定路由。http.ListenAndServe
函数:设置监听的端口。
1 | package main |
Go 中 Web 的工作方式
以下是服务器端的几个基本概念:
Request
:用户的请求信息。Response
:服务器需要反馈给客户端的信息。Conn
:用户每次请求的链接。Handler
:处理请求和生成返回信息的处理逻辑。
http 包运行机制
Go 实现 Web 服务的工作模式如下:
- 创建 Listen Socket,监听指定的端口,等待客户端请求到来。
- Listen Socket 接收客户端的请求,得到 Client Socket,通过Client Socket 与客户端进行通信。
- 处理客户端的请求,交给 Handler进行处理,Handler 处理好数据之后通过 Client Socket 返回给客户端。
这整个的过程里面我们只要了解清楚下面三个问题,也就知道 Go 是如何让 Web 运行起来了:
- 如何监听端口?
通过 http.ListenAndServe
函数,可以监听端口并分配handler,底层实现如下:初始化一个 server 对象,然后调用了 net.Listen("tcp", addr)
,也就是底层用 TCP 协议搭建了一个服务,然后监控我们设置的端口。
- 如何接收客户端请求?
以下来自 http 包的源码,可以看到整个 http 的处理过程。
1 | func (srv *Server) Serve(l net.Listener) error { |
在监听完端口过后,调用了 srv.Serve(net.Listener)
方法,这个方法就是处理客户端的请求信息。在 Accept 之后,创建一个 Conn,然后开启一个协程来单独处理来自客户端的 http 请求:go c.serve()
。
- 如何分配 handler?
Conn 首先会解析 request:c.readRequest()
,然后获取相应的 handler:handler := c.server.Hander
,c.server.Handler
即为 http.ListenAndServe
的第二个参数;若为空,则默认获取 handler = DefaultServeMux
。
DefaultServeMux 实际上是一个路由器,它用来匹配 url 跳转到其相应的 handle 函数,通过 http.HandleFunc
函数设置。DefaultServeMux 会调用 ServeHTTP方法,这个方法内部调用了 http.HandleFunc
函数定义的 handler。
和http包
http 包
Go 的 http 有两个核心功能:Conn、ServeMux
Conn 的 goroutine
在 http 包中,当与客户端建立 TCP 连接之后,会建立一个协程来单独处理这一次用户的请求:
1 | c, err := srv.newConn(rw) |
ServeMux
当 c.server.Handler
为空时,则默认获取 handler = DefaultServeMux
。这个 DefaullServeMux 是一个路由器,结构如下:
1 | type ServeMux struct { |
muxEntry 的结构如下:
1 | type muxEntry struct { |
Handler 是一个接口:
1 | type Handler interface { |
但是用户自定义的 Handler 并没有实现 ServeHTTP 方法,所以在 http 包里面还定义了一个类型 HandlerFunc
,这个类型实现了 ServeHTTP 方法,也就是 Handler 接口。调用了 http.HandlerFunc
函数之后,我们自定义的 Handler 会被强制类型转换为 HandleFunc 类型,这样就有了 ServeHTTP方法:
1 | type HandlerFunc func(ResponseWriter, *Request) |
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了 ServeHTTP
:
1 | func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { |
在路由器收到请求之后,如果是 *
,则关闭连接。否则调用 mux.Handler(r)
返回应设置路由的处理 Handler, 然后执行 h.ServeHTTP(w, r)
。
mux.Handler(r)
函数实现如下,它根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行到相应的函数了。:
1 | func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { |
自定义路由器
ListenAndServe
函数的第二个参数可以用来配置外部路由器,它是一个 Handler 接口,即即外部路由器只要实现了 Handler 接口就可以。
1 | package main |
总结
Go语言中 Web 的工作流程如下:
首先调用 http.HandleFunc 函数:
- 调用 DefaultServeMux 的HandleFunc
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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)
}
// 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 的 Handle
- 往 DefaultServeMux 的 map[string]muxEntry 中增加对应条目
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// 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
}
}其次调用 http.ListenAndServe 函数:
- 实例化 Server
- 调用 Server 的 ListenAndServe
1
2
3
4
5
6
7
8
9
10
11// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}- 调用 net.Listen(“tcp”, addr) 监听端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
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)
}- 启动一个 for 循环,在循环体中 Accept 请求。对于每一个请求实例化一个 Conn,请开启一个 goroutine 运行服务 go c.serve()
- 读取每一个请求的内容 w, err := c.readRequest()
- 判断 handler 是否为空,如果为空则将 handler 设置为 DefaultServeMux
- 调用 handler 的 ServeHTTP,即 DefaultServeMux.ServeHTTP
- 根据请求选择 handler,并进入这个handler 的 ServeHTTP:
mux.handler(r).ServeHTTP(w, r)