前面文章我们已经实现了一个简单的Web服务。现在我们详细解剖http包,分析内部实现的细节。
1.1 http包中重要的类型和接口:
- server:HTTP服务器,定义监听的地址、端口,处理器等信息。
- conn:用户每次请求的链接。
- response:返回给用户的信息。
- request:用户请求的信息。
- Handler: 处理器,用户请求到来时,服务器的处理逻辑,它是一个包含ServeHTTP方法的接口。
1.2 http包的运行流程:
2.1 http包如何实现高并发
http包中,server每接收一个用户请求,都会生成一个conn链接,并生成一个goroutines来处理对应的conn。所以每个请求都是独立的,相互不阻塞的。
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
go c.serve(ctx)
2.2 处理器(Handler)和多路复用器(ServeMux)
前一小节我们使用ListenAndServe启动服务,第二个参数是一个处理器。但是我们传入的是一个nil,http是如何工作的呢?
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)
}
所以当我们的处理器为空时,http包会默认帮我们生成一个DefaultServeMux处理器。
不是说第二个参数是处理器吗,但是DefaultServeMux是一个ServeMux结构的实例, 是一个多路复用器。这是怎么回事?
其实处理器是一个拥有ServeHTTP方法的接口,只要实现了这个方法,它就是一个处理器。所以DefaultServeMux不仅是一个多路复用器,而且还是一个处理器。只是这个处理器比较特殊,它只是根据请求的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)
}
我们可以自定义自己的处理器
package main
import (
"fmt"
"net/http"
)
// 定一个自定义的Handler的结构
type MyHanlder struct{}
// 为MyHanlder结构实现Hanlder接口的ServeHTTP的方法,此时MyHandler将是一个处理器(Handler)
func (h MyHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This my handler")
}
func main() {
// 实例化MyHandler
hanlder := MyHanlder{}
//启动服务器并监听8000/tcp端口
err := http.ListenAndServe(":8000", hanlder)
if err != nil {
fmt.Println(err)
}
}
2.3 ServeMux和DefaultServeMux
前面我们说DefaultServeMux是一个ServeMux的实例,它不仅是一个多路复用器,而且是一个处理器。多路复用器具有路由功能,可以根据请求的URL将请求传递给对应的处理器处理。看下http包中默认的多路复用器是怎么实现的:
type ServeMux struct {
mu sync.RWMutex //锁,应为请求是并发处理的,所以这里需要锁机制
m map[string]muxEntry //路由规则的map,一个string对应一个muxEntry
es []muxEntry
hosts bool
}
type muxEntry struct {
h Handler //string对应的处理器
pattern string //匹配的字符串
}
默认的多路复用器是根据请求的URL与存储的map做匹配,匹配到了就返回对应的handler,然后调用该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)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method == "CONNECT" {
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.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)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
我们也可以根据接口定义实现自己简单的路由器
package main
import (
"fmt"
"net/http"
)
// 自定义一个多路复用器的结构
type MyMux struct{}
// 实现ServeHTTP方法
func (m MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 判断URL并转到对应的handler处理
if r.URL.Path == "/index" {
IndexHandler(w, r)
return
}
http.NotFound(w, r)
return
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is index page")
}
func main() {
// 实例化一个自定义的路由器
mux := MyMux{}
err := http.ListenAndServe(":8000", mux)
if err != nil {
fmt.Println(err)
}
}