Gee教程2.上下文Context

news/2024/7/16 9:27:41 标签: xcode, macos, ide

先来看看Gin框架的简单例子

func main() {
   engine := gin.Default()
   engine.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
    //监听并启动服务,默认 http://localhost:8080/
   engine.Run()
}

//我们自己写的
func main() {
	engine := gee.New()
	engine.GET("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
	})
	engine.Run("localhost:10000")
}

对比我们自己写的,Hander方法的参数变成了*gin.Context。

那么这版的主要修改策略是:

  • 路由(Router)独立出来,方便之后增强。
  • 设计上下文(Context),封装 Request 和 Response ,并提供对 JSON、HTML 等返回类型的支持。

 设计Context

必要性

1.web服务,基本就是根据客户的请求*http.Request,构造回应客户的响应http.ResponseWriter。但是这两个对象提供的接口粒度比较细。比如要构造一个完整的响应,需要考虑消息头和消息体,而消息头包含了状态码,消息类型等。那每次构造回复,都要写这些设置的代码。所以,如果不进行有效的封装,那么使用该框架的用户就需要写大量繁琐重复的代码。

通俗点说,就是要让使用框架的用户尽量少写重复的代码,少写代码可以实现同样的功能。

用返回JSON数据进行比较,来感受下封装前后的变化。

封装之前

engine.GET("/", func(w http.ResponseWriter, req *http.Request) {
		obj := map[string]any{
			"name": "abc",
			"age":  20,
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		encoder := json.NewEncoder(w)
		if err := encoder.Encode(obj); err != nil {
			http.Error(w, err.Error(), 500)
		}
	})

封装后

	engine.GET("/", func(c *gee.Context) {
		c.JSON(http.StatusOK, gee.H{
			"username": c.PostForm("username"),
			"password": c.PostForm("password"),
		})
	})

2.针对使用场景,封装*http.Requesthttp.ResponseWriter的方法,简化了相关接口的调用,这只是设计Context的原因之一。

对应框架来说,还需要支撑额外的功能。例如,之后要解析动态路由/hello/:name,参数:name的值放在哪里?Context随着每一个请求的出现而产生,请求的结束而销毁,和当前请求的强相关的信息都应由Context承载。每一个HTTP请求就会带有一个Context。

因此,设置Context结构,扩展性和复杂性都留在了内部,从而简化了接口。而路由的处理函数,以及之后要实现的中间件,参数都统一使用Context实例。Context就像一次会话的百宝箱,可以找到任何东西。

Context的实现

type H map[string]any

type Context struct {
	Wrtier http.ResponseWriter
	Req    *http.Request

	Path   string
	Method string
	//响应的状态码
	StatusCode int
}

func newContext(w http.ResponseWriter, req *http.Request) *Context {
	return &Context{
		Wrtier: w,
		Req:    req,
		Path:   req.URL.Path,
		Method: req.Method,
	}
}

func (c *Context) PostForm(key string) string {
	return c.Req.FormValue(key)
}

func (c *Context) Query(key string) string {
	return c.Req.URL.Query().Get(key)
}

func (c *Context) Status(code int) {
	c.StatusCode = code
	c.Wrtier.WriteHeader(code)
}

func (c *Context) SetHeader(key string, value string) {
	c.Wrtier.Header().Set(key, value)
}


//以下是快速构造Sring/Data/JSON/HTML响应的方法
func (c *Context) String(code int, format string, values ...any) {
	c.SetHeader("Content-Type", "text/plain")
	c.Status(code)
	c.Wrtier.Write([]byte(fmt.Sprintf(format, values...)))
}

func (c *Context) JSON(code int, obj any) {
	c.SetHeader("Content-Type", "application/json")
	c.Status(code)
	encoder := json.NewEncoder(c.Wrtier)
	if err := encoder.Encode(obj); err != nil {
		http.Error(c.Wrtier, err.Error(), 500)
	}
}

func (c *Context) Data(code int, data []byte) {
	c.Status(code)
	c.Wrtier.Write(data)
}

func (c *Context) HTML(code int, html string) {
	c.SetHeader("Content-Type", "text/html")
	c.Status(code)
	c.Wrtier.Write([]byte(html))
}

代码开头,给map[string]any起了别名H,构建JSON数据时候,使用gee.H显得更加简洁。

  • Context目前包含了http.ResponseWriter*http.Request,这样为路由函数的参数可以修改成(c *gee.Context)做准备。另外也提供了对Method和Path这两个常用属性的直接访问。
  • 提供了访问Query和PostForm参数的方法
  • 提供了快速构造Sring/Data/JSON/HTML响应的方法,使用简便。

 路由(Router)

将路由抽取出来,放到一个新的文件router.go,方便我们下次对router功能的增强,录入添加动态路由的支持。而router的hander方法做了一个调整,就是handler的参数变成了前面所说的*Context

将路由单独抽离出来,那和路由相关的路由方法的操作也会在该文件内。

package gee

import "net/http"
//HandlerFunc的定义在gee.go文件中

type router struct {
	handers map[string]HandlerFunc
}

func newRouter() *router {
	return &router{handers: make(map[string]HandlerFunc)}
}

// 添加路由
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
	key := method + "-" + pattern
	r.handers[key] = handler
}

func (r *router) handle(c *Context) {
	key := c.Method + "-" + c.Path
	if hander, ok := r.handers[key]; ok {
		hander(c)
	} else {
		c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
	}
}

框架入口

gee.go

type Engine struct {
	router *router
}

// 创建enginx实例
func New() *Engine {
	return &Engine{router: newRouter()}
}

func (engine *Engine) addRoute(method string, pattern string, hander HandlerFunc) {
	engine.router.addRoute(method, pattern, hander)
}

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) error {
	return http.ListenAndServe(addr, engine)
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := newContext(w, req)
	engine.router.handle(c)
}

 在将路由router相关的代码独立之后,gee.go相对简单了不少。其中最重要的还是通过实现了ServeHTTP接口,接管了所有的HTTP请求。

这版的改变主要的改变,添加了Context,把路由router独立开来。在此基础上,gee.go中最大变化的是在ServHTTP方法中。

在Context中说了,一个HTTP请求,就带有一个Context。所以一个HTTP请求到来后,就为这个请求创建一个Context实例

独立router部分后,执行路由方法的代码也在router中了。而路由方法的参数也由(http.ResponseWriter, *http.Request)修改成(*Context)。所以,ServHTTP需要调用router.handle去执行对应的路由方法,并把*context传递进去,给该请求发送响应,由用户自己定义的,但都是调用Context中的api来实现的,例如返回string字符串,Context.String函数。

所以,Context承载了整个会话的生命周期

测试

到这里,使用的样子已经和Gin相似了。来看看使用例子。

func main() {
	engine := gee.New()
	engine.GET("/", func(c *gee.Context) {
		c.String(http.StatusOK, "ok..")
	})

	engine.POST("/hello", func(c *gee.Context) {
		c.JSON(http.StatusOK, gee.H{
			"name": "abc",
			"age":  32,
		})
	})

	engine.Run("localhost:10000")
}

 执行 go run main.go查看效果

 完整代码:https://github.com/liwook/Go-projects/tree/main/gee-web/2-context


http://www.niftyadmin.cn/n/5216638.html

相关文章

数据结构与算法(Java)-树形DP题单

树形DP(灵神笔记) 543 二叉树的直径 543. 二叉树的直径 - 力扣(LeetCode) 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根…

Linux系统的文件权限

Linux系统权限的相关概念与理解 (xshell下进行演示) 文章目录: 1:linux系统下两种用户 超级用户(root)与普通用户(非root)的理解root与非root用户之间切换的指令非root用户之间进行切换的指令操作 2:linux文件权限管理 文件访问者的介绍文件的类型与文件的访问权…

7天用Go实现Web框架Gee教程

本文是学习模仿自https://geektutu.com/post/gee.html 在此基础上,加入自己的学习过程与理解 框架 设计框架之前,需要知道为什么需要使用框架,框架能解决什么问题。只有明白了这一点,才能设计出框架中的功能。 通俗来讲&#…

python 点云las生成深度图

一、代码实现 import laspy import cv2 import numpy as np import matplotlib.pyplot as plt# 相机内参 CAM_WID, CAM_HGT = 475, 475 # 深度图尺寸 CAM_FX, CAM_FY = 5.2640790081811531e+02, 5.2616047137164196e+02 # fx/fy

LC-828. 统计子串中的唯一字符(贡献法 思考变化量)

828. 统计子串中的唯一字符 困难 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,并返回唯一字符的个数。 例如:s "LEETCODE" ,则其中 "L", "T","C","O","…

shell脚本整数变量表达式

1、整数变量表达式 序号参数说明1-eq等于2-ne不等于3-gt大于4-ge大于等于5-lt小于6-le小于等于 2、特殊变量 序号参数说明1$0当前脚本的文件名2$n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是13$#传递给脚本或函数的参数…

基于51单片机的智能垃圾桶硬件设计

**单片机设计介绍, 基于51单片机的智能垃圾桶硬件设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的智能垃圾桶设计旨在通过传感器和控制电路实现智能化的垃圾桶功能。下面是一个简要的硬件设计介绍&…

告别软件代码,硬件攻城狮也能DIY的 PD DRP+OTG 芯片来了

随着 USB-C 接口的普及,越来越多的设备开始采用这种接口。由于 USB-C接口的高效性和便携性,使各种设备之间的连接和数据传输变得非常方便快捷,它们不仅提供了强大的功能,还为我们的日常生活和工作带来了极大的便利,USB…