Gee教程2.上下文Context

news2025/2/22 7:16:57

先来看看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.coloradmin.cn/o/1255405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PRD学习

产品经理零基础入门(五)产品需求文档PRD(全16集)_哔哩哔哩_bilibili 1. PRD的2种表现形式 ① RP格式 (1)全局说明 ② 文档格式 2. 交互说明撰写 ① 维度 ② 步骤 ③ 规则 (1)单位…

通达信视频教程的下载地址

百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com/s/12yNV62ROERRzmyqm9u22aQ?pwdgmdx

Leetcode 1727. 具有重排的最大子矩阵

题目要求: 给定一个大小为 m x n 的二进制矩阵,并且允许您以任意顺序重新排列矩阵的列。 对列进行最佳重新排序后,返回矩阵中每个元素都为 1 的最大子矩阵的面积。 输入:矩阵 [[0,0,1],[1,1,1],[1,0,1]] 输出:4 说明…

2017年五一杯数学建模B题自媒体时代的消息传播问题解题全过程文档及程序

2017年五一杯数学建模 B题 自媒体时代的消息传播问题 原题再现 电视剧《人民的名义》中人物侯亮平说:“现在是自媒体时代,任何突发性事件几分钟就传播到全世界。”相对于传统媒体,以互联网技术为基础的自媒体以其信息传播的即时性、交往方式…

x大网校登录接口js逆向分析

网站: import base64 # 解码 result base64.b64decode(aHR0cHM6Ly91c2VyLndhbmd4aWFvLmNuL2xvZ2lu.encode(utf-8)) websiteresult.decode(utf-8) # print(result) print(website)思路: 模拟登录,得到token值,才能对内部数据进行…

【YOLOv5入门】目标检测

【大家好,我是爱干饭的猿,本文重点介绍YOLOv5入门-目标检测的任务、性能指标、yolo算法基本思想、yolov5网络架构图。 后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关注一下吧】 上一篇…

利用 LD_PRELOAD 环境变量

文章目录 原理LD_PRELOAD介绍如何上传.so文件 例题 [虎符CTF 2022]ezphp 原理 LD_PRELOAD介绍 LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接…

系列十九、Spring实例化bean的方式

一、概述 所谓实例化bean,大白话讲就是Spring如何把这一个个的普通的Java对象创建为Spring bean的。 二、方式 Spring中实例化bean常用的有以下四种,即: ① 构造器方式; ② 静态工厂方式; ③ 实例工厂方式;…

c语言实现10进制转16进制

代码如下&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h>int dectohex(int b, char array[]) {char a[17] { "0123456789ABCDEF" };int c[30] { 0 }, i 0, base 16, j 0;while (b){c[i] b % base;b b / base;}j i;for (i--; i >…

steam/csgo搬砖项目真能月入过万吗?到底真的假的

steam/csgo搬砖第三课之如何出售 steam搬砖核心原理是什么&#xff1f;为什么会有差价产生&#xff1f;buff不是更低价吗&#xff1f;很多小白会有这些疑问&#xff01; steam搬砖指的是通过买卖csgo游戏装备赚钱的。 玩过游戏的应该就很清楚&#xff0c;像绝地求生&#xff…

数据结构——带头循环双向链表(List)

1、带头双向循环链表介绍 在上一篇博客中我们提到了链表有三个特性&#xff0c;可以组合成为8种不同类型的链表。单链表是其中比较重要的一种&#xff0c;那么这次我们选择和带头双向循环链表会会面&#xff0c;这样我们就见识过了所有三种特性的呈现。 带头双向循环链表&#…

qt pdf 模块简介

文章目录 1. 技术平台2. Qt pdf 模块3. cmake 使用模块4. 许可证5. 简单示例5.1 CMakeLists.txt5.2 main.cpp 6. 总结 1. 技术平台 项目说明OSwin10 x64Qt6.6compilermsvc2022构建工具cmake 2. Qt pdf 模块 Qt PDF模块包含用于呈现PDF文档的类和函数。 QPdfDocument 类加载P…

Python基础:字符串详解(需补充完善)

1. 字符串定义 在Python中&#xff0c;字符串是一种数据类型&#xff0c;用于表示文本数据。字符串是由字符组成的序列&#xff0c;可以包含字母、数字、符号和空格等字符。在Python中&#xff0c;你可以使用单引号&#xff08;&#xff09;或双引号&#xff08;"&#x…

SpringBoot 拦截器高级篇

Springboot 拦截器 定义使用场景拦截器与过滤器的区别实现步骤全局拦截器的局限性全局拦截器VS局部拦截器局部拦截器自定义局部拦截器使用多个局部拦截器 定义 拦截器是Spring MVC框架中的一个重要组件&#xff0c;它是一种AOP&#xff08;面向切面编程&#xff09;的实现方式&…

汽车租聘管理与推荐系统Python+Django网页界面+协同过滤推荐算法

一、介绍 汽车租聘管理与推荐系统。本系统使用Python作为主要编程语言&#xff0c;前端采用HTML、CSS、BootStrap等技术搭建前端界面&#xff0c;后端采用Django框架处理用户的请求。创新点&#xff1a;使用协同过滤推荐算法实现对当前用户个性化推荐。 其主要功能如下&#x…

lua的gc原理

lua垃圾回收(Garbage Collect)是lua中一个比较重要的部分。由于lua源码版本变迁&#xff0c;目前大多数有关这个方面的文章都还是基于lua5.1版本&#xff0c;有一定的滞后性。因此本文通过参考当前的5.3.4版本的Lua源码&#xff0c;希望对Lua的GC算法有一个较为详尽的探讨。 L…

4.前端--HTML标签-表格列表表单【2023.11.25】

1.表格 1.1表格的作用 表格的作用&#xff1a;表格主要用于显示、展示数据 1.2表格的基本格式 <table><tr><td>单元格内的文字</td><td>单元格内的文字</td>...</tr>... </table><table> </table> 是用于定义表…

【C++】类和对象——拷贝构造和赋值运算符重载

上一篇我们讲了构造函数&#xff0c;就是对象实例化时会自动调用&#xff0c;那么&#xff0c;我们这里的拷贝构造在形式上是构造函数的一个重载&#xff0c;拷贝构造其实也是一种构造函数&#xff0c;那么我们就可以引出这里的规则 1.拷贝构造函数的函数名必须与类名相同。 2.…

vue3+tsx的使用

<template><div><xiaoman on-click"getItem" name"似懂非懂"></xiaoman></div> </template><script setup langts>import xiaoman from "./App"const getItem(item:any)>{console.log(item,it…

Java多线程二-线程安全

1、线程安全问题 多个线程&#xff0c;同时操作同一个共享资源的时候&#xff0c;可能会出现业务安全问题。 2、实例&#xff1a;取钱的线程安全问题 2.1、场景 小明和小红是夫妻&#xff0c;他们有个共同账户&#xff0c;余额是十万元&#xff0c;如果两人同时取钱并且各自取…