Files
临书 70dbea1bd6 Update 03.4.md
typo
2017-09-11 16:35:07 +08:00

194 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.4 Go的http包详解
前面小节介绍了Go怎么样实现了Web工作模式的一个流程这一小节我们将详细地解剖一下http包看它到底是怎样实现整个过程的。
Go的http有两个核心功能Conn、ServeMux
## Conn的goroutine
与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立相互不会阻塞可以高效的响应网络事件。这是Go高效的保证。
Go在等待客户端请求里面是这样写的
```Go
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
```
这里我们可以看到客户端的每次请求都会创建一个Conn这个Conn里面保存了该次请求的信息然后再传递到对应的handler该handler中便可以读取到相应的header信息这样保证了每个请求的独立性。
## ServeMux的自定义
我们前面小节讲述conn.server的时候其实内部是调用了http包默认的路由器通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢
它的结构如下:
```Go
type ServeMux struct {
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
m map[string]muxEntry // 路由规则一个string对应一个mux实体这里的string就是注册的路由表达式
hosts bool // 是否在任意的规则中带有host信息
}
```
下面看一下muxEntry
```Go
type muxEntry struct {
explicit bool // 是否精确匹配
h Handler // 这个路由表达式对应哪个handler
pattern string //匹配字符串
}
```
接着看一下Handler的定义
```Go
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}
```
Handler是一个接口但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口为什么能添加呢原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果这个类型默认就实现了ServeHTTP这个接口即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型这样f就拥有了ServeHTTP方法。
```Go
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
```
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`
```Go
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
```
如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler然后执行`h.ServeHTTP(w, r)`
也就是调用对应路由的handler的ServerHTTP接口那么mux.Handler(r)怎么处理的呢?
```Go
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
return RedirectHandler(p, StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
func (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
}
```
原来他是根据用户请求的URL和路由器里面存储的map去匹配的当匹配到之后返回存储的handler调用这个handler的ServeHTTP接口就可以执行到相应的函数了。
通过上面这个介绍我们了解了整个路由过程Go其实支持外部实现的路由器 `ListenAndServe`的第二个参数就是用以配置外部路由器的它是一个Handler接口即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。
如下代码所示,我们自己实现了一个简易的路由器
```Go
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
```
## Go代码的执行流程
通过对http包的分析之后现在让我们来梳理一下整个的代码执行过程。
- 首先调用Http.HandleFunc
按顺序做了几件事:
1 调用了DefaultServeMux的HandleFunc
2 调用了DefaultServeMux的Handle
3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
- 其次调用http.ListenAndServe(":9090", nil)
按顺序做了几件事情:
1 实例化Server
2 调用Server的ListenAndServe()
3 调用net.Listen("tcp", addr)监听端口
4 启动一个for循环在循环体中Accept请求
5 对每个请求实例化一个Conn并且开启一个goroutine为这个请求进行服务go c.serve()
6 读取每个请求的内容w, err := c.readRequest()
7 判断handler是否为空如果没有设置handler这个例子就没有设置handlerhandler就设置为DefaultServeMux
8 调用handler的ServeHttp
9 在这个例子中下面就进入到DefaultServeMux.ServeHttp
10 根据request选择handler并且进入到这个handler的ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 选择handler
A 判断是否有路由能满足这个request循环遍历ServeMux的muxEntry
B 如果有路由满足调用这个路由handler的ServeHTTP
C 如果没有路由满足调用NotFoundHandler的ServeHTTP
## links
* [目录](<preface.md>)
* 上一节: [Go如何使得web工作](<03.3.md>)
* 下一节: [小结](<03.5.md>)