自从拿到offer之后主要都是玩和准备毕业论文的事,是时候该继续学习啦!!!
gee
简单来说,gee就是在net库的基础上实现一个更加强大的web框架。
net/http库的基本使用
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
以上是使用net/http库实现一个简单的服务。如果你有底层网络库的经验的话,这里应该会很熟悉。
http.ListenAndServer主要功能如下:
- 监听并接受新的连接
- 起一个新协程去处理连接,然后循环执行,继续监听
大致过程如下:
for {
rw, err := l.Accept()
...
go srv.Handle(rw)
}
接收连接的过程大致如下:
- 使用HTTP解析器解析数据流
- 读取解析相应的请求参数
- 根据请求方法和URL,路由到相应的函数
第一天:
http.ListenAndServer的第二个参数则是用来指定路由函数的,如果我们传入nil,则启动一个默认的路由。查看源码可以发现:它的第二个参数一个接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
只需要实现这个接口的类型,就可以作为自定义的路由传入。
package main
import (
"fmt"
"net/http"
)
type Engine struct{}
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprintf(w, "Hello World")
case "/favicon.ico":
fmt.Fprintf(w, "favicon")
default:
fmt.Fprintf(w, "404 not found")
}
}
func main() {
http.ListenAndServe(":8080", &Engine{})
}
可以看到,只要实现相应的接口;我们自定义的实例就可以接受并处理所有的请求。接下来,我们仿照gin实现一个类似的功能:
package gee
import (
"fmt"
"net/http"
)
type HandlerFunc func(w http.ResponseWriter, r *http.Request)
type Engine struct {
router map[string]HandlerFunc
}
func NewEngine() *Engine {
return &Engine{router: make(map[string]HandlerFunc)}
}
func (e *Engine) AddRoute(method, pattern string, handler HandlerFunc) {
key := method + "-" + pattern
e.router[key] = handler
}
func (e *Engine) GET(pattern string, handler HandlerFunc) {
e.AddRoute("GET", pattern, handler)
}
func (e *Engine) POST(pattern string, handler HandlerFunc) {
e.AddRoute("POST", pattern, handler)
}
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
key := r.Method + "-" + r.URL.Path
if handler, ok := e.router[key]; ok {
handler(w, r)
} else {
fmt.Fprintf(w, "404 Not Found: %s\n", r.URL)
}
}
func (e *Engine) Run(addr string) error {
return http.ListenAndServe(addr, e)
}
实现思路:
- 关键是完成请求-函数的映射
- 请求包括请求类型和URL,把两个字段组合在一起
- 使用一个
map记录映射关系
缺点:
map效率低- 只能完成最基础的路由映射功能
第二条:设计上下文,封装更多功能
为了实现类似于gin框架的使用体验,我们需要对上述实现进一步的封装。底层的网络库已经帮我们完成了监听、建立连接、HTTP协议解析。在此基础上,实现该接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
就可以用自定义实例去处理所有的请求。处理请求无非就是:
- 从
http.request中取出数据 - 逻辑处理
- 将响应写回
http.ResponseWriter中
为了实现下列的调用:
func main() {
r := gee.NewEngine()
r.GET("/", func(c *gee.Context) {
c.HTML("<h1>Hello Gee</h1>")
})
r.Run(":9090")
}
我们需要对第一天编写的内容进一步封装。由于context和请求具有高度相关性,它伴随一条请求的整个生命周期,可以把一些繁琐的操作封装到context中。
- 上述例子中用户传入的不再是
Handler类型接口,但由上面的分析可知:这是处理请求必须的 - 那么很自然地就想到把该变量封装到
context中
// 只展示了修改部分
type HandlerFunc func(c *Context)
func (c *Context) HTML(templateStr string) {
tmpl, _ := template.New("test").Parse(templateStr)
tmpl.Execute(c.w, nil)
}
// 底层网络库传入的必须两个参数w,r被封装到context中
// 同时封装了一些常用方法
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
key := r.Method + "-" + r.URL.Path
if handler, ok := e.router[key]; ok {
ctx := &Context{w, r}
handler(ctx)
} else {
fmt.Fprintf(w, "404 Not Found: %s\n", r.URL)
}
}