简易HTTP服务器的实现
先看一个使用net/http
包实现的简单服务器程序示例。
package main
import (
"io"
"log"
"net/http"
)
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world!")
}
func main() {
// 设置路由
http.Handle("/", &myHandler{})
http.HandleFunc("/hello", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
运行程序,打开浏览器,访问http://localhost:8080
,可以看到浏览器上显示I'm home page
,访问http://localhost:8080/hello
,可以看到hello world!
。就是如此简单,但是程序背后做了哪些事呢?
分析
http.Handle
主要是设置路由规则,先看它的具体实现吧:
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
可以看到两个不认识的东西,Handler
和DefaultServeMux
。继续查看定义:
// Handler是一个接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ServeMux是一个自定义类型
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry //模式字符串与处理器的映射关系
hosts bool // whether any patterns contain hostnames
}
// muxEntry 是一个自定义类型,包括一个模式字符串和对应的Handler接口
type muxEntry struct {
h Handler
pattern string
}
var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
DefaultServeMux
是*ServeMux
类型,指向defaultServeMux
这个内部变量。ServeMux
结构体的含义是路由器,它的成员m
比较重要,存储的是模式字符串与处理器的映射关系。*ServeMux
实现了Handle
函数,主要用途就是填充成员m
的,具体代码如下:
// 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()
// 错误判断,模式字符串为空、处理起handler为空、对应的模式字符串的处理器已经存在
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)
}
// 如果没有实例化,就make一下
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
}
可以看到前面有三个错误判断,这个不影响理解,重要的是mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
,将传递进来的处理器handler
和模式字符串pattern
组合成muxEntry
结构体,使用map
结构绑定pattern
和muxEntry
之间的关系。
综上所述,http.Handle
干了什么呢?主要就是操作DefaultServeMux
这个结构体变量,填充它的成员m
(map[string]muxEntry
类型),建立路由规则。通俗一点,就是为每个url
模式串注册了一个回调函数(go语言中称之为处理器Handler)!
接下来的http.ListenAndServe
函数,具体实现如下:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
这一段代码不追究细节还是很好理解的,第一个参数是端口号,第二个参数是具体的处理器(该示例中传入nil
),这个函数就是先创建一个具体的Server
对象,然后调用ListenAndServe()
,不看具体实现,也能猜到,就是先listen
(监听),再建立一个事件循环,监听到了连接事件或者读写事件发生,就启动一个协程进行处理。
问题是,传递进去的handler
的ServeHTTP
方法再什么时候调用的呢?继续翻源码,server.ListenAndServe()
主要分为两个步骤,1.处理新连接,即调用Accpet
函数。2.处理其他事件,代码中主要体现在go c.serve(ctx)
。而在c.serve
函数中找到了这句话:serverHandler{c.server}.ServeHTTP(w, w.req)
。serverHandler
类型其实就是重新包装了*Server
类型,如下:
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 重点, 取出内部的Handler接口变量
handler := sh.srv.Handler
// 如果是空,就是用默认的Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// 调用 ServeHTTP 方法!
handler.ServeHTTP(rw, req)
}
serverHandler
的ServeHTTP
方法中调用了它的成员Handler
(最开始由你传递进来的)的ServeHTTP
方法(这就是你自定义的处理器实现的ServeHTTP
的作用所在了)。本示例中的传入的Handler
是nil
,所以最后使用的是DefaultServeMux
的ServeHTTP
方法,而DefaultServeMux
是*ServeMux
类型,它的ServeHTTP
方法实现如下:
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request 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)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
}
mux.Handler
函数中调用了mux.match
函数,mux.match
函数主要是在mux.m
这个之前注册好的map
(字典)中查找url
的对应处理器。根据之前定义的路由规则找到了对应的处理器之后,就调用处理器的ServeHTTP
方法。
所以,在访问http://localhost:8080
时,执行的是myHandler
自定义类型的ServeHTTP
方法,返回浏览器I'm home page
字符串,并在浏览器上显示出来。
http.HandleFunc
的用法和http.Handle
一样,但是第二个参数是func(ResponseWriter, *Request)
这个函数类型而不是Handler
接口,由上面的分析可知,ServeMux
的成员m
存储要求是个Handler
接口,那么传入个函数怎么处理呢?见源码:
// 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)) {
mux.Handle(pattern, HandlerFunc(handler))
}
// 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)
}
DefaultServeMux
使用了HandleFunc
方法,该方法将函数类型的handler
强行转换成了自定义类型HandlerFunc
类型,该类型实现了Handler
接口,也就使得传入的函数也实现了Handler
接口,在ServeHTTP
方法中,直接调用自身代表的函数。很是巧妙!
这些就是net/http
包背后默默帮你做的事情。
自定义路由器
下一个问题,如果不使用DefaultServeMux
呢,能否自己定义一个ServeMux
(路由器)?答案是可以的,将简单的HTTP服务器修改一下:
package main
import (
"io"
"log"
"net/http"
)
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
func main() {
mux := http.NewServeMux() // new出一个新的ServeMux对象
mux.Handle("/", &myHandler{})
mux.HandleFunc("/hello", sayHello)
err := http.ListenAndServe(":8080", mux) //传入自定义的路由器(mux)
if err != nil {
log.Fatal(err)
}
}
与文章开头不同的地方在于,使用http.NewServeMux()
创建了一个ServeMux
对象,然后调用mux.Handle
注册路由,最后在http.ListenAndServe
中传入自定义的路由器。
更进一步,能否不使用ServeMux
结构体呢?我们可以直接使用http.ListenAndServe
中使用的Server
结构,并且自己处理路由分发(自己定义字典存储路由规则)。
package main
import (
"io"
"log"
"net/http"
"time"
)
// 用字典实现路由器
var mux map[string]func(http.ResponseWriter, *http.Request)
// 自己实现的Handler
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 根据url选择对应的处理函数
if h, ok := mux[r.URL.String()]; ok {
h(w, r)
return
}
io.WriteString(w, "sorry, error happened!")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
func sayHome(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func main() {
server := http.Server{
Addr: ":8080",
Handler: &myHandler{},
ReadTimeout: 5 * time.Second, //设置超时时间
}
mux = make(map[string]func(http.ResponseWriter, *http.Request))
mux["/"] = sayHome
mux["/hello"] = sayHello
err := server.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}