我们定义了一个空的结构体Engine,实现了方法ServeHTTP。这个方法有2个参数,第二个参数是 Request ,该对象包含了该HTTP请求的所有的信息,比如请求地址、Header和Body等信息;第一个参数是 ResponseWriter ,利用 ResponseWriter 可以构造针对该请求的响应。
在 main 函数中,我们给 ListenAndServe 方法的第二个参数传入了刚才创建的engine实例。至此,我们走出了实现Web框架的第一步,即,将所有的HTTP请求转向了我们自己的处理逻辑。还记得吗,在实现Engine之前,我们调用 http.HandleFunc 实现了路由和Handler的映射,也就是只能针对具体的路由写处理逻辑。比如/hello。但是在实现Engine之后,我们拦截了所有的HTTP请求,拥有了统一的控制入口。在这里我们可以自由定义路由映射的规则,也可以统一添加一些处理逻辑,例如日志、异常处理等。
代码的运行结果与之前的是一致的。
Gee框架的雏形 代码结构 tree gee_demo1 gee_demo1 ├── gee │ ├── gee.go │ └── go.mod ├── go.mod └── main.go 1 directory, 4 files go.mod module gee_demo1 go 1.13 require gee v0.0.0 replace gee => ./gee在 go.mod 中使用 replace 将 gee 指向 ./gee
从 go 1.11 版本开始,引用相对路径的 package 需要使用上述方式。
main.go package main import ( "fmt" "gee" "net/http" ) func main() { r := gee.New() r.GET("http://www.likecs.com/", 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(":8000") }看到这里,如果你使用过gin框架的话,肯定会觉得无比的亲切。gee框架的设计以及API均参考了gin。使用New()创建 gee 的实例,使用 GET()方法添加路由,最后使用Run()启动Web服务。这里的路由,只是静态路由,不支持/hello/:name这样的动态路由,动态路由我们将在下一次实现。
gee.go package gee import ( "fmt" "log" "net/http" ) // 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 log.Printf("Route %4s - %s", method, pattern) engine.router[key] = 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 defines the method to start a http server func (engine *Engine) Run(addr string) (err error) { return http.ListenAndServe(addr, engine) } 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) } }那么gee.go就是重头戏了。我们重点介绍一下这部分的实现。
首先定义了类型HandlerFunc,这是提供给框架用户的,用来定义路由映射的处理方法。我们在Engine中,添加了一张路由映射表router,key 由请求方法和静态路由地址构成,例如GET-/、GET-/hello、POST-/hello,这样针对相同的路由,如果请求方法不同,可以映射不同的处理方法(Handler),value 是用户映射的处理方法。
当用户调用(*Engine).GET()方法时,会将路由和处理方法注册到映射表 router 中,(*Engine).Run()方法,是 ListenAndServe 的包装。
Engine实现的 ServeHTTP 方法的作用就是,解析请求的路径,查找路由映射表,如果查到,就执行注册的处理方法。如果查不到,就返回 404 NOT FOUND 。
执行go run main.go,再用 curl 工具访问,结果与最开始的一致
测试 $ curl localhost:8000 URL.Path = "http://www.likecs.com/" $ curl localhost:8000/hello Header["Accept"] = ["*/*"] Header["User-Agent"] = ["curl/7.64.1"]至此,整个Gee框架的原型已经出来了。实现了路由映射表,提供了用户注册静态路由的方法,包装了启动服务的函数。当然,到目前为止,我们还没有实现比net/http标准库更强大的能力,不用担心,很快就可以将动态路由、中间件等功能添加上去了。
上下文Context将路由(router)独立出来,方便之后增强。