• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

总结

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


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Go语言中使用MySql数据库发布时间:2022-07-10
下一篇:
[Go]使用net包作为tcp客户端读取http发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap