跟Gin一块搭建自己的web框架(二)
本文介绍Gin的路由控制。
上一篇文章中直接用Golang自带的net/http实现了一个Web服务,并且具备了路由功能。但是,net/http在功能和性能上都有所欠缺。 比如从功能上来说, 现在的很多RESTful接口的定义方式中,习惯使用不同的请求方法来实现不同的语义:GET、POST、HEAD、DELETE、OPTION等等。 如何用net/http单独的对请求方法注册特定的处理函数也变成了一个麻烦的问题。再比如RESTful风格的API重度依赖请求路径。会将很多参数放在请求URI中,支持这些URI的参数也是一个问题。
route实现原理
首先看一下net/http是如何实现路由功能的。上一篇的例子中
http.HandleFunc("/", handler)
这条语句通过http.HandleFunc函数注册了对路径 / 处理的函数handler。看一下它内部的实现
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
可以看到是调用了 DefaultServeMux 的 HandleFunc 函数。再看 DefaultServeMux 的定义:
var defaultServeMux ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
mu 是读写的信号量,m 是一个map结构,用来存储路径与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))
}
// 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)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
}
从源码上可以看到,实际上添加路由信息的过程先判断对应的handler是否为空,不为空的话,将对应的路径和handler添加到ServeMux.m中。
在处理请求的时候,实际是调用了ServerHTTP的函数:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
//这个判断成立,因为我们传递的是nil
if handler == nil {
handler = DefaultServeMux
}
//省略了一些代码
handler.ServeHTTP(rw, req)
}
也就是说,在我们启动Web服务的时候
http.ListenAndServe(":8080", nil)
如果第二个handler参数传的是nil,就用启用默认的DefaultServeMux , 而我们之前已经把对应的路由加到了DefaultServeMux的map里边,来了请求之后直接去map里边找对应的关系就行。
换句话说,如果我们要实现自己的路由功能,只需要按照默认的格式定义路由,在启动的时候,将我们的自定义路由传入到ListenAndServe的第二个参数即可。
httprouter
httprouter就是一个现成的高效率的路由实现,现在不少go web的框架使用了httprouter对路由进行支持,对应的源码地址: https://github.com/julienschmidt/httprouter 。 我们先用httprouter来改写上一篇的web服务。
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func handler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprintf(w, "Hi there")
}
func main() {
router := httprouter.New()
router.GET("/", handler)
log.Fatal(http.ListenAndServe(":8080", router))
}
我们首先生成了一个新的路由服务 httprouter.New(), 然后添加了对于 / 的GET请求的处理。启动的时候把这个router作为第二个参数传入到ListenAndServe然后就生效了。
同样,利用httprouter,我们能够简单有效对其他比如POST、HEAD等操作提供支持,也能快速的实现路由参数的功能,具体的可以参考httprouter的使用。
下一篇仿照Gin实现前缀路由的功能。