【Gee】Day1:HTTP 基础
Day1:HTTP 基础
今天的任务是:
- 简单介绍
net/http
库以及http.Handler
接口; - 搭建
Gee
框架的雏形,代码约 50 行;
标准库启动 Web 服务
Golang 内置了 net/http
库,封装了 HTTP 网络编程基础的接口,我们将要实现的 Gee 框架就是基于 net/http
的。接下来通过一个例子介绍 net/http
这个库如何使用:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":9999", nil))
}
func indexHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
}
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 请求调用不同的处理函数。访问 /
,响应是 URL.Path = /
。而 /hello
的响应则是 header 中的键值对信息。
main 函数的最后一行是用来启动 Web 服务的,第一个参数是地址,:9999
代表在localhost:9999
监听。第二个参数代表处理所有的 HTTP 请求的实例,nil
代表使用标准库中的实例处理。
第二个参数正是我们基于 net/http
标准库实现 Web 框架的入口。
实现 http.Handler 接口
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
通过查看net/http
的源码,发现Handler
是一个接口,需要实现ServeHTTP
方法,即:只要传入任何实现了 ServeHTTP
接口的实例,所有的 HTTP 请求就都将会交给这个传入的实例来处理了。现在让我们尝试一下:
package main
import (
"fmt"
"log"
"net/http"
)
type Engine struct{}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/":
fmt.Fprint(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)
}
}
func main() {
engine := new(Engine)
log.Fatal(http.ListenAndServe(":9999", engine))
}
- 在上例当中,我们定义了一个空结构体
Engine
,它实现了方法ServeHTTP
。这个方法有两个参数,第二个参数是 Request,该对象包含了该 HTTP 请求的所有的信息,比如请求地址、Header 和 Body 等信息;第一个参数是 ResponseWriter,利用 ResponseWriter 可以构造针对该请求的响应。 - 在 main 当中,我们给 ListenAndServe 方法的第二个参数传入了刚才创建的 engine 实例。至此,我们已经走出了实现 Web 框架的第一步,即:将所有的 HTTP 请求转向了我们自己的处理逻辑。在实现
Engine
之前,我们只能通过调用http.HandleFunc
才能实现路由和 Handler 映射,也就是只能针对具体的路由写处理逻辑。但是在实现Engine
之后,我们拦截了所有的 HTTP 请求,拥有了统一的控制入口。在Engine
的 ServeHTTP 方法当中,我们可以自由定义路由映射的规则,也可以统一添加一些处理逻辑,例如日志、异常处理等。
Gee 框架的雏形
文件的组织形式如下:
首先“搭建” main 函数:
package main
import (
"fmt"
"net/http"
)
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
在设计时其框架和 API 的设计均参考了 gin
,因此上述逻辑与 gin
非常相似。使用 New()
创建 gee 的服务实例,使用 GET()
方法添加路由,最后使用 Run()
启动 Web 服务。目前路由仅支持静态路由。
gee.go
我们再来实现 gee.go
。
package gee
import (
"fmt"
"net/http"
)
// HandlerFunc defines the request handler used by gee
type HandlerFunc func(w http.ResponseWriter, r *http.Request)
// Engine implements 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)}
}
// addRoute combines method and pattern together and then add method-pattern and handler to map
func (engine *Engine) addRoute(method, pattern string, handler HandlerFunc) {
key := 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)
}
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 // method + "-" + pattern, as key in Engine's map
if handler, ok := engine.router[key]; ok {
handler(w, req)
} else {
fmt.Fprintf(w, "404 NOT FOUNT: %s\n", req.URL)
}
}
- 首先定义了类型
HandlerFunc
,它是提供给框架用户的,用来定义路由映射的处理方法。在Engine
中,添加了一张路由映射表router
,key
由请求方法和静态路由地址拼接而成,例如:GET-/
、GET-/hello
、POST-/hello
等。 - 当用户调用
(*Engine).GET()
方法时,会将路由和处理方法注册到映射表 router 中。(*Engine).Run()
是 ListenAndServe 的包装。 Engine
实现的 ServeHTTP 方法的作用就是,解析请求的路径,查找路由映射表,如果查到了,那么就执行注册的处理方法,否则返回 404 NOT FOUND。
至此,Gee 框架的原型已经完成了,逻辑还是非常的清晰和易于理解的。