为什么要写这个项目 使用 Go 语言实现一个简单的 Web 框架,通过这个项目 可以让我更好的去理解Go语言是如何实现一个简单的Web框架的。
当我们离开框架,使用基础库时,需要频繁手工处理的地方,就是框架的价值所在。但并不是每一个频繁处理的地方都适合在框架中完成。此时理解一个框架的特性是非常重要的,这也是我想要通过Go去实现一个框架的真正原因。
项目地址
HTTP基础 一个简单的Web服务 Go语言内置了 net/http
库,封装了HTTP网络编程的基础的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "net/http" ) func index (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World" ) } func main () { http.HandleFunc("/" , index) http.ListenAndServe(":9090" , nil ) }
http.HandleFunc http.HandleFunc("/", index)
注册路径处理函数,这里将路径/
的处理函数设置为index
。
处理函数的类型:
1 func (http.ResponseWriter, *http.Request)
*http.Request
表示 HTTP 请求对象,该对象包含请求的所有信息,如 URL、首部、表单内容、请求的其他内容等。
http.ResponseWriter
是一个接口类型:(去看一看源码)
1 2 3 4 5 6 type ResponseWriter interface { Header() Header Write([]byte ) (int , error ) WriteHeader(statusCode int ) }
用于向客户端发送响应,实现了ResponseWriter
接口的类型显然也实现了io.Writer
接口。所以在处理函数index
中,可以调用fmt.Fprintln()
向ResponseWriter
写入响应信息。
http.ListenAndServe http.ListenAndServe
用以监听本地 9090 端口以提供服务。标准的 HTTP 端口是 80 端口,如 baidu.com:80
,另一个 Web 常用是 HTTPS 的 443 端口,如 baidu.com:443
。
1 2 3 4 5 6 7 8 9 10 11 func ListenAndServe (addr string , handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } type Handler interface { ServeHTTP(ResponseWriter, *Request) }
查看源码得知,http.ListenAndServe第二个参数是Handler类型,Handler需要实现ServeHTTP方法。所以我们需要传入一个ServerHTTP 接口的实例,就可以将HTTP请求传给这个实例处理。
实现一个ServeHTTP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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.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) } } func main () { engine := new (Engine) log.Fatal(http.ListenAndServe(":9090" , engine)) }
ServeHTTP方法的两个参数
Request包含了该HTTP请求的所有信息(请求地址、Header、Body等)
ResponseWriter可以构造针对该请求的响应
LisrenAndServe方法的第二个参数传入了engine实例。engine这个实例拦截了所有的HTTP请求,有了统一的控制入口。我们就可以自定义路由映射规则,也可以统一添加一些处理逻辑,如日志、异常处理等。
Gee框架雏形 目录结构 1 2 3 4 5 gee/ |--gee.go |--go.mod main.go go.mod
go.mod 在 go.mod
中使用 replace
将 gee 指向 ./gee
1 2 3 4 5 6 7 module demo01 go 1.18 require gee v0.0 .0 replace gee => ./gee
main.go main.go的内容和当初使用gin框架的时候感觉是一摸一样,因为gee设计参考了gin。
使用New()创建gee实例(创建gin实例)
使用GET()方法添加路由。
使用Run()启动Web服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "net/http" "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(":9090" ) }
gee.go
gee.go通过Engine实现了ServeHTTP方法,解析请求路径,查找路由映射表
若查找到就执行注册的处理方法
若没查找到返回404
定义自定义类型HandlerFunc
router map[string]HandlerFunc
作为路由映射表
key是请求方法如:GET-/
,GET-/hello
,POST-/hello
,value是映射处理的方法。
(*Engine).GET()
方法时,会将路由和处理方法注册到映射表 router 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package geeimport ( "fmt" "net/http" ) type HandlerFunc func (http.ResponseWriter, *http.Request) type Engine struct { router map [string ]HandlerFunc } 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 } func (engine *Engine) GET(pattern string , handler HandlerFunc) { engine.addRoute("GET" , pattern, handler) } 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 if handler, ok := engine.router[key]; ok { handler(w, req) } else { fmt.Fprintf(w, "404 NOT FOUND: %s\n" , req.URL) } }
这就是Gee框架的原型。实现了路由映射表,提供了用户注册静态路由的方法,包装了服务启动函数。