• 仿照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
    ", 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")
    }
    

    总结

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

  • 相关阅读:
    Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020
    浙江农林大学第十九届程序设计竞赛暨天梯赛选拔赛
    Educational Codeforces Round 97 (Rated for Div. 2)
    2018icpc南京区域赛的补题
    天梯赛的一些题目
    djangorestful framework (三)学习
    rest-framework之版本控制
    rest-framework之响应器(渲染器)
    rest-framework之分页器
    rest-framework之频率控制
  • 原文地址:https://www.cnblogs.com/CharmCode/p/14372375.html
Copyright © 2020-2023  润新知