仿照Go web框架gin手写自己的web框架 【中】

首先最终目的是模仿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\n", 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\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("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\n", 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\n", 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")
}

总结

至此,一个最最基础的微框架 就完成了,但是还是缺少最核心的一些功能,比如路由分组,中间件处理,错误恢复机制。


文章作者: 王小右
版权声明: 咳咳想白嫖文章?本文章著作权归作者所有,任何形式的转载都请注明出处。 https://www.charmcode.cn !
还能输入 100 字
  目录