首先,在设计框架之前,需要知道为什么要使用框架,框架能解决什么问题,只有明白了这一点,才能设计出框架中的功能
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
net/http提供了基础的Web功能,即监听端口,映射静态路由,http.ResponseWriter解析HTTP报文,http.Request对其内容进行解析。但还要一些Web开发中简单的需求并不支持,需要手工实现,例如
可以发现,当我们离开框架,使用基础库时,需要频繁手工处理的地方,就是框架的价值所在。
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":9999", nil))
}
// handler echoes r.URL.Path
func indexHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
}
// handler echoes r.URL.Header
func helloHandler(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
}
这里设置了2个路由,/和/hello,分配绑定indexHandler和helloHandler,根据不同的http请求会调用不同的处理函数。
main 函数的最后一行,是用来启动 Web 服务的,第一个参数是地址,:9999表示在 9999 端口监听。而第二个参数则代表处理所有的HTTP请求的实例,nil 代表使用标准库中的实例处理。第二个参数,则是我们基于net/http标准库实现Web框架的入口。
接下来看第二个参数是如何使用的
首先我们定义了一个空的Engine结构体,他的作用是为所有请求做处理的handler,然后我们把前面所有的HandlerFunc通过绑定Engine对象进行管理
实现http.Handler接口
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
通过源码可以看到,Handler是一个接口类型,需要实现ServeHTTP方法,也就是说,只要传入任何实现了 ServerHTTP 接口的实例,所有的HTTP请求,就都交给了该实例处理了。所以我们要自己写一个实现该接口的实例,让所有的HTTP请求由自己来写
// Engine is the uni handler for all requests
type Engine struct{}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/":
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
case "/hello":
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
default:
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}
所以我们定义一个函数来实现ServerHTTP方法,这个方法有两个参数
在该函数中根据请求的URL的path的不同做响应的操作
func main() {
engine := new(Engine)
log.Fatal(http.ListenAndServe(":8090", engine))
}
最后再定义我们的main函数,在 main 函数中,我们给 ListenAndServe 方法的第二个参数传入了刚才创建的engine实例,此时所有的请求都会通过Engine去处理,而不是使用自身的标准库去处理。至此,我们走出了实现Web框架的第一步,即,将所有的HTTP请求转向了我们自己的处理逻辑。还记得吗,在实现Engine之前,我们调用 http.HandleFunc 实现了路由和Handler的映射,也就是只能针对具体的路由写处理逻辑。比如/hello。但是在实现Engine之后,我们拦截了所有的HTTP请求,拥有了统一的控制入口。在这里我们可以自由定义路由映射的规则,也可以统一添加一些处理逻辑,例如日志、异常处理等。
再接下来我们就要把刚才自己实现的代码抽离出来,搭建出整个框架的雏形
gee/
|--gee.go
main.go
在gee.go中,我们首先来定义HandlerFunc函数类型,这是提供给框架用户的,用来定义路由映射的处理方法,定义Engine结构体,Engine结构体里存放router,其中以map字典存放处理函数HandlerFunc,并实现ServerHTTP方法,结构体里存放的路由表,由addRoute方法添加kv,GET和POST这两个方法只是对addRoute的包装而已
然后定义New方法,返回Engine对象,make一个HandlerFunc,定义addRouter方法用来添加router
// HandlerFunc defines the request handler used by gee
type HandlerFunc func(http.ResponseWriter, *http.Request)
// Engine implement the interface of ServeHTTP
type Engine struct {
router map[string]HandlerFunc
}
// New is the constructor of gee.Engine
func New() *Engine {
return &Engine{router: make(map[string]HandlerFunc)}
}
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
key := method + "-" + pattern
engine.router[key] = handler
}
接下来我们定义两个method方法GET和POST,当我们使用GET请求和POST请求时就会调用addRouter方法添加路由。我们在Engine中,添加了一张路由映射表router,key 由请求方法和静态路由地址构成,例如GET-/、GET-/hello、POST-/hello,这样针对相同的路由,如果请求方法不同,可以映射不同的处理方法(Handler)。
// GET defines the method to add GET request
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
engine.addRoute("GET", pattern, handler)
}
// POST defines the method to add POST request
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
engine.addRoute("POST", pattern, handler)
}
然再定义Run方法
// Run defines the method to start a http server
func (engine *Engine) Run(addr string) (err error) {
return http.ListenAndServe(addr, engine)
}
当用户调用(*Engine).GET()方法时,会将路由和处理方法注册到映射表 router 中,当我们观察(*Engine).Run()方法,发现里面实则是调用http.ListenAndServe(addr, engine)。我们会发现所有的http请求的处理首先都会进入ServeHTTP方法,在ServeHTTP方法内,发现通过engine.router这个map传入key,来调用不同的函数来处理不同的请求。
然后让engine对象去实现ListenAndServe接口,即实现ServerHTTP方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
key := req.Method + "-" + req.URL.Path
if handler, ok := engine.router[key]; ok {
handler(w, req)
} else {
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}
在main.go文件的主函数中调用刚才写的Gee框架
func main() {
r := gee.New()
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})
r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
})
r.Run(":9999")
}
至此,整个Gee框架的原型已经出来了。实现了路由映射表,提供了用户注册静态路由的方法,包装了启动服务的函数。当然,到目前为止,我们还没有实现比net/http标准库更强大的能力,不用担心,很快就可以将动态路由、中间件等功能添加上去了。