首先最终目的是模仿gin框架核心的几个功能就够了。
声明: 三部曲文章主要参考: https://geektutu.com/post/gee.html
所以最终的框架核心文件如下:
- gee/
- context.go // 上下文
- gee.go // gee核心函数
- recovery.go // 处理异常恢复中间件 并且trace错误
- router.go // 路由处理
- trie.go // 前缀树路由 (这一点实现比较复杂,可以参考 https://geektutu.com/post/gee-day3.html)
基础封装
实现一个最基础的一版 只包含一个gee.go
文件的框架,通过上一章提到的实现 Handler
接口的方式简单封装路由。
对于代码 github
- gee/
- gee.go // gee核心函数
main.go // 用户引用文件
首先gee.go
文件如下
package gee
import (
"fmt"
"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)}
}
// 把请求路径 和 处理函数放入 router 这个map中
func (engine *Engine) addRoute(method string, 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)
}
// 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
// 通过方法和请求路径 匹配到 handler请求处理函数
if handler, ok := engine.router[key]; ok {
handler(w, req)
} else {
w.WriteHeader(http.StatusNotFound)
// 没有匹配到则表示 路径和请求方法不存在
_, _ = fmt.Fprintf(w, "404 NOT FOUND: %s
", req.URL)
}
}
用户使用gee
框架 main.py
文件
package main
import (
"fmt"
"net/http"
"gee"
)
func main() {
r := gee.New()
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
_, _ = fmt.Fprintf(w, "URL.Path = %q
", 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
", k, v)
}
})
_ = r.Run("127.0.0.1:7052")
}
这种方式了,原理就是把路由和请求处理函数,存储到 Engine
属性router
这个map中,然后每次请求进来,通过请求方法 - 请求路径的形式,来从map中匹配到请求响应函数,然后处理请求在返回。
封装上下文,路由 和 返回值
这一个是基于上一步,把响应的返回值还有响应状态,给封装了,方便返回不同的数据类型,比如JSON
,字符串,或者HTML渲染。
此次章节代码如下
对应github代码
- gee/
- context.go // 上下文
- gee.go // gee核心函数
- router.go // 路由处理
main.go // 用户使用框架文件夹
router.go
单独抽取出路由文件
package gee
import "net/http"
type Route struct {
// 存储 请求方式-路由 : 请求处理函数
handlers map[string]HandlerFunc
}
// 初始化路由
func NewRoute() *Route {
return &Route{
handlers: make(map[string]HandlerFunc),
}
}
// 路由内部添加添加方法
func (r *Route) addRoute(method string, pattern string, handler HandlerFunc) {
// 拼接请求方式 和 路由
key := method + "-" + pattern
// 存储到路由处理的映射中 和 请求处理函数 一一对应
r.handlers[key] = handler
}
// 找到并执行处理请求函数
func (r *Route) handle(c *Context) {
key := c.Method + "-" + c.Path
if handler, ok := r.handlers[key]; ok {
handler(c)
} else {
// 没有找到直接 404
c.String(http.StatusNotFound, "404 NOT FOUND: %s
", c.Path)
}
}
gee.go
文件,把路由模块单独抽出去了,只用在封装一层添加路由和匹配处理函数即可
package gee
import "net/http"
type Route struct {
// 存储 请求方式-路由 : 请求处理函数
handlers map[string]HandlerFunc
}
// 初始化路由
func NewRoute() *Route {
return &Route{
handlers: make(map[string]HandlerFunc),
}
}
// 路由内部添加添加方法
func (r *Route) addRoute(method string, pattern string, handler HandlerFunc) {
// 拼接请求方式 和 路由
key := method + "-" + pattern
// 存储到路由处理的映射中 和 请求处理函数 一一对应
r.handlers[key] = handler
}
// 找到并执行处理请求函数
func (r *Route) handle(c *Context) {
key := c.Method + "-" + c.Path
if handler, ok := r.handlers[key]; ok {
handler(c)
} else {
// 没有找到直接 404
c.String(http.StatusNotFound, "404 NOT FOUND: %s
", c.Path)
}
}
context.go
请求上下封装,把常见的请求信息(获取请求参数信息),和响应信息(响应特定类型)封装到里面
package gee
import (
"encoding/json"
"fmt"
"net/http"
)
// 用于返回JSON数据
type H map[string]interface{}
// 存储请求上下文信息
type Context struct {
Req *http.Request
Writer http.ResponseWriter
// 把一些基础信息单独抽出来
Path string
Method string
StatusCode int
}
// 构建上下文实例
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
Req: r,
Writer: w,
Path: r.URL.Path,
Method: r.Method,
}
}
// 获取url的查询参数
func (c *Context) Query(name string) string {
return c.Req.URL.Query().Get(name)
}
// 获取表单参数
func (c *Context) PostForm(key string) string {
return c.Req.FormValue(key)
}
// 设置状态码
func (c *Context) Status(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
// 设置header
func (c *Context) SetHeader(key string, value string) {
c.Writer.Header().Set(key, value)
}
// 快速构建响应
// 返回字符串
func (c *Context) String(code int, format string, values ...interface{}) {
c.SetHeader("Content-Type", "text/plain;charset=utf-8")
c.Status(code)
_, _ = c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
// 返回json数据
func (c *Context) JSON(code int, obj interface{}) {
c.SetHeader("Content-Type", "application/json;charset=utf-8")
c.Status(code)
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(obj); err != nil {
http.Error(c.Writer, err.Error(), 500)
}
}
// 返回字节流数据
func (c *Context) Data(code int, data []byte) {
c.Status(code)
_, _ = c.Writer.Write(data)
}
// 返回html数据
func (c *Context) HTML(code int, html string) {
c.SetHeader("Content-Type", "text/html;charset=utf-8")
c.Status(code)
_, _ = c.Writer.Write([]byte(html))
}
用户main.go
文件调用
package main
import (
"practice/03_case_demo/09_custom_web_framework/04_gee_context/gee"
)
func main() {
r := gee.New()
r.GET("/", func(c *gee.Context) {
c.String(200, "测试首页 你输入的name为%s 路径为 %s", c.Query("name"), c.Path)
})
r.GET("/json", func(c *gee.Context) {
c.JSON(200, gee.H{
"json": "Value",
})
})
_ = r.Run("127.0.0.1:7053")
}
总结
至此,一个最最基础的微框架 就完成了,但是还是缺少最核心的一些功能,比如路由分组,中间件处理,错误恢复机制。