ServeMux、httprouter

一、创建一个简单的goWeb服务器

func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Println("1", r.RemoteAddr)
    fmt.Println("2", r.Method)
    fmt.Println("3", r.Host)
    fmt.Println("4", r.URL)
    w.Write([]byte("这是测试页面"))
}
func main() {
    http.HandleFunc("/", Hello)
    if err := http.ListenAndServe(":8080",nil);err!=nil{
        log.Fatal(err)
    }

上例代码,main()函数通过代码http.ListenAndServe(":8080",nil) 启动一个8080 端口的服务器。如果这个函数传入第一个参数(网络地址)为空,则服务器在启动后默认使用 http://127.0.0.1:8080 地址进行访问;如果这个函数传入的第二个参数为nil,则服务器在启动后将使用默认的多路复用器(DefaultServeMux)

运行上例代码,在浏览器中访问http://127.0.0.1:8080,默认会显示 “这是测试页面”字符串

image

用户可以通过Server结构体对服务器进行更加详细的配置,包括为请求读取操作设置超时时间等。GoWeb服务器的请求和响应流程如下:

image

  1. 客户端发送请求;
  2. 服务器端的多路复用器收到请求;
  3. 多路复用器请求的URL找到注册的处理器,将请求交由处理器处理
  4. 处理器执行程序逻辑,如果必要,则与数据库进行交互,得到处理结果
  5. 处理器调用模板引擎将指定的模板和上一步得到的结果渲染成客户端可识别的数据格式(通常是HTML)
  6. 服务器端将数据通过HTTP相应返回给客户端。
  7. 客户端拿到数据,执行对应的操作(例如渲染出来呈现给用户)

二、接受请求

2.1、ServeMux和DefaultServeMux

####### 1、ServeMux和DefaultServeMux简介
image

ServeMux 是一个结构体,其中包括一个映射,这个映射将会URL映射至相应的处理器。它会在映射中找出与被请求URL最为匹配的URL,然后调用与之相对应的处理器ServeHTTP()方法来处理请求

DefaultServeMux 是 net/http 包中默认提供的一个多路复用器,其本质是ServeMux的一个实例。多路服务器的任务是——根据请求的URL将请求重定向到不同的处理器。如果用户没有为Serve对象指定处理器,则服务器默认使用DefaultServeMux作为ServeMux结构体的实例。

HandleFunc() 函数用于为指定的URL注册一个处理器。HandleFunc() 处理器函数会在内部调用DefaultServeMux对象的对应方法,其内部实现如下:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

上面的方法可以看出,http.HandleFunc() 函数将处理器注册到多路复用器中。用默认多路复用器还可以指定多个处理器,其使用方法如下:

// 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)
}

下面的代码中,自定义了两个Handle,都实现了http.Handler接口,因为他们都实现了ServeHTTP方法

package main

import (
    "fmt"
    "net/http"
)

// 定义Handle1结构体,
type Handle1 struct{}

// Handle1 实现了http.Handler接口
/*
源码:
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
*/
// 实现了Handle1 的ServerHTTP方法,用于处理HTTP请求
func (h1 *Handle1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "这是handle1") // 向相应写入内容
}

// 定义类型 Handle2,同样实现了 http.Handler 接口
type Handle2 struct{}

// 实现 Handle2 的 ServeHTTP 方法,用于处理 HTTP 请求
func (h2 *Handle2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "这是handle2")

}

// 定义一个函数 Hello,用于处理根路径的 HTTP 请求
func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Println("1", r.RemoteAddr)
    fmt.Println("2", r.Method)
    fmt.Println("3", r.Host)
    fmt.Println("4", r.URL)
    w.Write([]byte("这是测试页面"))
}

// 定义一个函数 Test,用于处理 "/test" 路径的 HTTP 请求
func Test(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("testtesttest"))
}
func main() {
    // 创建Handle1和Handle2的实例
    handle1 := Handle1{}
    handle2 := Handle2{}
    // 创建一个HTTP服务器配置
    server := http.Server{
        Addr:    "0.0.0.0:8085",
        Handler: nil,
    }
    // 注册Handle1和Handle2 为不同路径的处理器
    http.Handle("/handle1", &handle1)
    http.Handle("/handle2", &handle2)
    // 注册函数 Hello为根路径的处理器
    http.HandleFunc("/", Hello)
    // 启动HTTP服务器监听
    server.ListenAndServe()

}

上面的代码中,直接用http.Handle() 函数来指定多个处理器。Handle()函数的代码如下:

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

可以看到在http.Handle() 函数中调用了DefaultServeMux.Handle() 方法来处理请求。服务器收到的每个请求都会调用对应多路复用器的ServeHTTP() 方法。该方法的代码如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	handler.ServeHTTP(rw, req)

}

在ServeMux对象的ServeHTTP()方法中,会根据URL查找我们注册的处理器,然后将请求交由它处理。

虽然默认的多路复用器用起来很方便,但是生产环境不建议使用。因为DefaultServeMux是一个全局变量,所有代码(包括第三方代码)都可以修改它。有些第三方代码会在DefaultServeMux中注册一些处理器,可能会与自己注册的处理器冲突。比较推荐的做法是自定义多路复用器。

自定义多路复用器直接调用http.NewServeMux() 函数即可。然后再新创建的多路复用器上注册处理器。

mux := http.NewServeMux()
mux.HandleFunc("/",hi)
// 定义一个函数 Hello,用于处理根路径的 HTTP 请求
func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Println("1", r.RemoteAddr)
    fmt.Println("2", r.Method)
    fmt.Println("3", r.Host)
    fmt.Println("4", r.URL)
    w.Write([]byte("这是测试页面"))
}

// 定义一个函数 Test,用于处理 "/test" 路径的 HTTP 请求
func Test(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("testtesttest"))
}
func main() {
    // 创建新的ServeMux(路由器)
    mux := http.NewServeMux()
    mux.HandleFunc("/", Hello)
    mux.HandleFunc("/test", Test)
    // 创建一个HTTP服务器配置
    server := http.Server{
        Addr:    "0.0.0.0:8085", // 监听的地址和端口
        Handler: mux,  // 使用自定义的ServeMux
    }
    // 启动HTTP服务器监听
    if err := server.ListenAndServe(); err != nil {
        log.Fatal(err)
    }

}

 上面例子中与默认多路复用器的功能相同,都是启动一个HTTP服务器端。这里还创建了服务器对象Server。通过指定服务器的参数,可以创建定制化的服务器,如下:

// 创建一个HTTP服务器配置
server := http.Server{
    Addr:    "0.0.0.0:8085",
    Handler: mux,
    ReadTimeout: 10 * time.Second,
    WriteTimeout: 10 * time.Second,
}

上面代码,创建了一个读超时和写超时都为10s的服务器,下面是源码中定义的一些相关服务器配置的参数:

type Server struct {
	// Addr optionally specifies the TCP address for the server to listen on,
	// in the form "host:port". If empty, ":http" (port 80) is used.
	// The service names are defined in RFC 6335 and assigned by IANA.
	// See net.Dial for details of the address format.
	Addr string

	Handler Handler // handler to invoke, http.DefaultServeMux if nil

	// TLSConfig optionally provides a TLS configuration for use
	// by ServeTLS and ListenAndServeTLS. Note that this value is
	// cloned by ServeTLS and ListenAndServeTLS, so it's not
	// possible to modify the configuration with methods like
	// tls.Config.SetSessionTicketKeys. To use
	// SetSessionTicketKeys, use Server.Serve with a TLS Listener
	// instead.
	TLSConfig *tls.Config

	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body. A zero or negative value means
	// there will be no timeout.
	//
	// Because ReadTimeout does not let Handlers make per-request
	// decisions on each request body's acceptable deadline or
	// upload rate, most users will prefer to use
	// ReadHeaderTimeout. It is valid to use them both.
	ReadTimeout time.Duration

	// ReadHeaderTimeout is the amount of time allowed to read
	// request headers. The connection's read deadline is reset
	// after reading the headers and the Handler can decide what
	// is considered too slow for the body. If ReadHeaderTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, there is no timeout.
	ReadHeaderTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out
	// writes of the response. It is reset whenever a new
	// request's header is read. Like ReadTimeout, it does not
	// let Handlers make decisions on a per-request basis.
	// A zero or negative value means there will be no timeout.
	WriteTimeout time.Duration

	// IdleTimeout is the maximum amount of time to wait for the
	// next request when keep-alives are enabled. If IdleTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, there is no timeout.
	IdleTimeout time.Duration

	// MaxHeaderBytes controls the maximum number of bytes the
	// server will read parsing the request header's keys and
	// values, including the request line. It does not limit the
	// size of the request body.
	// If zero, DefaultMaxHeaderBytes is used.
	MaxHeaderBytes int

	// TLSNextProto optionally specifies a function to take over
	// ownership of the provided TLS connection when an ALPN
	// protocol upgrade has occurred. The map key is the protocol
	// name negotiated. The Handler argument should be used to
	// handle HTTP requests and will initialize the Request's TLS
	// and RemoteAddr if not already set. The connection is
	// automatically closed when the function returns.
	// If TLSNextProto is not nil, HTTP/2 support is not enabled
	// automatically.
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

	// ConnState specifies an optional callback function that is
	// called when a client connection changes state. See the
	// ConnState type and associated constants for details.
	ConnState func(net.Conn, ConnState)

	// ErrorLog specifies an optional logger for errors accepting
	// connections, unexpected behavior from handlers, and
	// underlying FileSystem errors.
	// If nil, logging is done via the log package's standard logger.
	ErrorLog *log.Logger

	// BaseContext optionally specifies a function that returns
	// the base context for incoming requests on this server.
	// The provided Listener is the specific Listener that's
	// about to start accepting requests.
	// If BaseContext is nil, the default is context.Background().
	// If non-nil, it must return a non-nil context.
	BaseContext func(net.Listener) context.Context

	// ConnContext optionally specifies a function that modifies
	// the context used for a new connection c. The provided ctx
	// is derived from the base context and has a ServerContextKey
	// value.
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	inShutdown atomicBool // true when server is in shutdown

	disableKeepAlives int32     // accessed atomically.
	nextProtoOnce     sync.Once // guards setupHTTP2_* init
	nextProtoErr      error     // result of http2.ConfigureServer if used

	mu         sync.Mutex
	listeners  map[*net.Listener]struct{}
	activeConn map[*conn]struct{}
	doneChan   chan struct{}
	onShutdown []func()

	listenerGroup sync.WaitGroup
}
  1. Addr: 指定服务器监听的 TCP 地址和端口。如果留空,则默认使用 ":http"(端口 80)。
  2. Handler: 指定用于处理 HTTP 请求的处理器。如果为 nil,则使用 http.DefaultServeMux
  3. TLSConfig: 提供 TLS 配置,用于在使用 ServeTLSListenAndServeTLS 时使用。注意,该值会被 ServeTLSListenAndServeTLS 克隆,因此无法通过诸如 tls.Config.SetSessionTicketKeys 之类的方法修改配置。
  4. ReadTimeout: 读取整个请求(包括请求体)的最大持续时间。零或负值表示没有超时。
  5. ReadHeaderTimeout: 读取请求头的最大持续时间。在读取完请求头后,连接的读取截止时间会重置,处理程序可以决定请求体的速度。如果为零,则使用 ReadTimeout 的值。如果两者都为零,则没有超时。
  6. WriteTimeout: 写入响应的最大持续时间。在读取新请求的头部时,会重置这个时间。与 ReadTimeout 类似,它不允许处理程序对每个请求进行超时设置。零或负值表示没有超时。
  7. IdleTimeout: 在启用了 Keep-Alive 时等待下一个请求的最大持续时间。如果 IdleTimeout 为零,则使用 ReadTimeout 的值。如果两者都为零,则没有超时。
  8. MaxHeaderBytes: 控制服务器解析请求头键和值的最大字节数,包括请求行。不限制请求体的大小。如果为零,则使用 DefaultMaxHeaderBytes
  9. TLSNextProto: 可选地指定一个函数,用于在 ALPN 协议升级发生时接管提供的 TLS 连接。映射键是协议名称。处理程序参数用于处理 HTTP 请求,并将初始化请求的 TLS 和 RemoteAddr(如果尚未设置)。函数返回时,连接会自动关闭。如果 TLSNextProto 不为 nil,则不会自动启用 HTTP/2 支持。
  10. ConnState: 指定一个可选的回调函数,在客户端连接状态发生变化时调用。
  11. ErrorLog: 指定一个可选的错误记录器,用于记录连接接受错误、处理程序的意外行为以及底层文件系统错误。如果为 nil,则使用 log 包的标准记录器。
  12. BaseContext: 可选地指定一个函数,用于为该服务器上的传入请求返回基本上下文。
  13. ConnContext: 可选地指定一个函数,用于修改新连接的上下文。提供的 ctx 是从基本上下文派生的,具有 ServerContextKey 值。
  14. inShutdown: 标志,当服务器正在关闭时为 true
  15. disableKeepAlives: 禁用 Keep-Alive 的标志,通过原子方式访问。
  16. nextProtoOnce: 用于保护 setupHTTP2_* 初始化的 sync.Once 互斥锁。
  17. nextProtoErr: 如果使用了 http2.ConfigureServer,则为该函数的结果,表示 HTTP/2 的初始化结果。
  18. mu: 用于保护以下字段的 sync.Mutex 互斥锁。
  19. listeners: 跟踪服务器正在监听的网络连接。
  20. activeConn: 跟踪服务器中当前活动的连接。
  21. doneChan: 在服务器关闭时发送信号的通道。
  22. onShutdown: 在服务器关闭时执行的回调函数列表。
  23. listenerGroup: 等待活动监听器完成的 sync.WaitGroup

*简单总结:ServeMux 实现了http.Handler接口的*ServeHTTP(ResponseWriter,*Request)方法。在创建Server时,如果设置Handler为空,则使用默认的DefaultServeMux*作为默认的处理器,而DefaultServeMuxServeMux的一个全局变量。*

####### 2、ServeMux的URL路由匹配
实际应用中,一个web服务器会有很多的URL绑定,不同的URL对应不同的处理器。

如果现在绑定了3个URL,分别是/、/user、/role

  • 如果请求的URL为/,则调用/对应的处理器。
  • 如果请求的URL为/user,则调用/user对应的处理器。
  • 如果请求的URL为/role,则调用/role对应的处理器。

如果注册的URL不是以/结尾的,则它只能精确匹配请求的URL。反之,即使请求的URL只有前缀与被绑定的URL相同,则ServeMux也认为它们是匹配的。例如′如果请求的URL为/user/ 则不能匹配到/user因为/user不以/结尾,必须精确匹配。如果我们绑定的URL为/user/,则当服务器找不到与/user/others完全匹配的处理器时,就会退而求其次,开始寻找能够与/user/匹配的处理器。

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web首页!处理器为:indexHandler")
}
func userHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web用户页!处理器为:userHandler")
}
func roleHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web权限页!处理器为:roleHandler")
}
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/user", userHandler)
    mux.HandleFunc("/role/web", roleHandler)
    // 创建一个HTTP服务器配置
    server := http.Server{
        Addr:         "0.0.0.0:8085",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }
    // 启动HTTP服务器监听
    if err := server.ListenAndServe(); err != nil {
        log.Fatal(err)
    }

}

image

image

输入http://127.0.0.1:8085/role/,将返回首页的处理器:indexHandler,因为绑定的/role需要精确匹配,而请求的/role/不能与之精确匹配所以向上查找到/

处理器和处理器函数都可以进行URL路由匹配。通常情况下,可以使用处理器和处理器函数中的一种或同时使用两者,

// 定义类型 Handle2,同样实现了 http.Handler 接口
type Handle2 struct{}

// 实现 Handle2 的 ServeHTTP 方法,用于处理 HTTP 请求
func (h2 *Handle2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "这是handle2")

}

// 定义一个函数 Hello,用于处理根路径的 HTTP 请求
func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Println("1", r.RemoteAddr)
    fmt.Println("2", r.Method)
    fmt.Println("3", r.Host)
    fmt.Println("4", r.URL)
    w.Write([]byte("这是测试页面"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web首页!处理器为:indexHandler")
}
func userHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web用户页!处理器为:userHandler")
}
func roleHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "欢迎来到Go Web权限页!处理器为:roleHandler")
}
func main() {
    handler2 := Handle2{}
    mux := http.NewServeMux()
    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/user", userHandler)
    mux.HandleFunc("/role/web", roleHandler)
    // 注册处理器
    mux.Handle("/test/", &handler2)
    // 创建一个HTTP服务器配置
    server := http.Server{
        Addr:         "0.0.0.0:8085",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }
    // 启动HTTP服务器监听
    if err := server.ListenAndServe(); err != nil {
        log.Fatal(err)
    }

}

####### 3、HttpRouter简介
ServeMux一个缺陷是:无法使用变量实现URL模式匹配。而HttpRouter则可以。HttpRouter是一个高性能、可扩展的第三方HTTP路由包。HttpRouter包弥补了net/http 包中默认路由不足的问题

(1)、安装HttpRouter

https://github.com/julienschmidt/httprouter

go get -u github.com/julienschmidt/httprouter

(2)、HttpRouter使用方法如下:首先使用httprouter.New()函数生成了一个*Router 路由对象,然后使用**GET()*方法注册一个适配/路径的index函数,最后将Router对象作为参数传给ListenAndServe()函数即可启动HTTP服务

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    w.Write([]byte("首页Index"))
}
func main() {

    router := httprouter.New()
    router.GET("/", Index)
    http.ListenAndServe(":8080", router)
}

HttpRouter包为常用的HTTP方法提供了快捷的使用方式。常用请求方法定义如下:

// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
func (r *Router) GET(path string, handle Handle) {
	r.Handle(http.MethodGet, path, handle)
}

// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
func (r *Router) HEAD(path string, handle Handle) {
	r.Handle(http.MethodHead, path, handle)
}

// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
func (r *Router) OPTIONS(path string, handle Handle) {
	r.Handle(http.MethodOptions, path, handle)
}

// POST is a shortcut for router.Handle(http.MethodPost, path, handle)
func (r *Router) POST(path string, handle Handle) {
	r.Handle(http.MethodPost, path, handle)
}

// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
func (r *Router) PUT(path string, handle Handle) {
	r.Handle(http.MethodPut, path, handle)
}

// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
func (r *Router) PATCH(path string, handle Handle) {
	r.Handle(http.MethodPatch, path, handle)
}

// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
func (r *Router) DELETE(path string, handle Handle) {
	r.Handle(http.MethodDelete, path, handle)
}

HttpRouter包提供了对命名参数的支持,可以很方便的开发RestfulAPI。比如,设计example/user/dujie这样一个URL,可以查看dujie这个用户的信息。如果要查看其他用户(比如zhangsan)的信息,则只需要访问example/user/zhangsan

在HttpRouter包中对URL使用两种匹配模式:

  1. 形如/user/:name 的精确匹配
  2. 形如/user/*name 的匹配所有的模式
func main() {
    router := httprouter.New()
    router.GET("/default", func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
        writer.Write([]byte("default get"))
    })
    router.POST("/default", func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
        writer.Write([]byte("default post"))
    })
    // 精确匹配
    //router.GET("/user/zhangsan", func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
    //    fmt.Println(params)
    //    // fmt.Println(request.URL.Query().Get("name"))
    //    writer.Write([]byte("user name:" + params.ByName("name")))
    //})
    router.GET("/user/*name", func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
        fmt.Println(params)
        rawName := params.ByName("name")
        // 去掉params对应name 前面的/
        name := strings.TrimLeft(rawName, "/")
        fmt.Println(name)
        writer.Write([]byte("user name:" + params.ByName("name")))
    })
    http.ListenAndServe(":8080", router)

}
// params的值: "user/*names" * 后面的names对应 params中的names
[{names /张三}]