Day1:HTTP 基础
今天的任务是:
- 简单介绍
net/http
库以及http.Handler
接口; - 搭建
Gee
框架的雏形,代码约 50 行;
标准库启动 Web 服务
Golang 内置了 net/http
库,封装了 HTTP 网络编程基础的接口,我们将要实现的 Gee 框架就是基于 net/http
的。接下来通过一个例子介绍 net/http
这个库如何使用:
package mainimport ("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 httptype Handler interface {ServeHTTP(w ResponseWriter, r *Request)
}func ListenAndServe(address string, h Handler) error
通过查看net/http
的源码,发现Handler
是一个接口,需要实现ServeHTTP
方法,即:只要传入任何实现了 ServeHTTP
接口的实例,所有的 HTTP 请求就都将会交给这个传入的实例来处理了。现在让我们尝试一下:
package mainimport ("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 mainimport ("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 geeimport ("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 + "-" + patternengine.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 mapif 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 框架的原型已经完成了,逻辑还是非常的清晰和易于理解的。