day2 上下文Context

文章目录

  • 使用效果
  • 设计Context
  • 路由(Router)
  • 框架入口

本文代码地址:day2 上下文Context

本文是 7天用Go从零实现Web框架Gee教程系列的第二篇。

主要内容如下:

  • 将路由(router)独立出来,方便之后增强。
  • 设计上下文(Context),封装 RequestResponse ,提供对 JSONHTML 等返回类型的支持。

动手写 Gee 框架的第二天,框架代码140行,新增代码约90行

使用效果

为了展示第二天的成果,以及加强对要实现的功能的整体感知,我们首先看一看在使用时的效果。

day2-context/main.go

func main() {r := gee.New()r.GET("/", func(c *gee.Context) {c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")})r.GET("/hello", func(c *gee.Context) {// expect /hello?name=geektutuc.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)})r.POST("/login", func(c *gee.Context) {c.JSON(http.StatusOK, gee.H{"username": c.PostForm("username"),"password": c.PostForm("password"),})})r.Run(":9999")
}
  • Handler的参数变成成了gee.Context,并且提供了Query获取路由参数,PostForm获取表单参数的功能。

  • gee.Context封装了HTML/String/JSON函数,能够快速构造HTTP响应。

设计Context

必要性
Web服务来说,无非是根据请求*http.Request,构造响应http.ResponseWriter。但是这两个对象提供的接口粒度太细,比如我们要构造一个完整的响应,需要考虑消息头(Header)和消息体(Body),而 Header 包含了状态码(StatusCode),消息类型(ContentType)等几乎每次请求都需要设置的信息。因此,如果不进行有效的封装,那么框架的用户将需要写大量重复,繁杂的代码,而且容易出错。针对常用场景,能够高效地构造出 HTTP 响应是一个好的框架必须考虑的点。

下面以返回 JSON 数据为例,对封装前后的使用进行对比,感受下封装前后的差距。

封装前

obj = map[string]interface{}{"name": "geektutu","password": "1234",
}
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)
}

在此处补充一点json.NewEncoder的小知识

json.NewEncoder Go 语言中标准库encoding/json包中的一个函数,用于创建一个新的 JSON 编码器。通过调用 json.NewEncoder(),你可以得到一个 JSON 编码器对象,然后可以使用它将 Go 结构体、映射等数据结构编码为JSON 格式的字符串。

以下是一个简单的示例,展示了如何使用 json.NewEncoder 创建编码器并进行编码:

package mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge  int
}func main() {// 创建 Person 结构体的实例p := Person{Name: "Alice", Age: 25}// 创建 JSON 编码器encoder := json.NewEncoder(os.Stdout)// 将 Person 结构体编码为 JSONerr := encoder.Encode(p)if err!= nil {fmt.Println("编码错误:", err)return}
}

在上述示例中,首先定义了一个名为 Person 的结构体,它具有 NameAge 字段。然后在 main 函数中,创建了一个 Person 结构体的实例 p

接下来,使用json.NewEncoder(os.Stdout)创建一个将输出发送到标准输出的 JSON 编码器。通过调用 encoder.Encode(p),将 p 编码为 JSON 并打印到标准输出。

请注意,调用encoder.Encode(p)进行编码为JSON时,需要进行错误检查,以确保编码过程中没有发生错误。如果有错误发生,你可以根据需要进行相应的处理。

这只是一个简单的示例,实际上,你可以根据自己的需求将编码后的 JSON 字符串保存到文件、网络传输等。

VS 封装后:

c.JSON(http.StatusOK, gee.H{"username": c.PostForm("username"),"password": c.PostForm("password"),
})

Context具体实现

针对使用场景,封装*http.Requesthttp.ResponseWriter的方法,简化相关接口的调用,只是设计Context的原因之一。对于框架来说,还需要支撑额外的功能。例如,将来解析动态路由/hello/:name,参数:name的值放在哪呢?再比如,框架需要支持中间件,那中间件产生的信息放在哪呢?Context 随着每一个请求的出现而产生,请求的结束而销毁,和当前请求强相关的信息都应由 Context 承载因此,设计 Context 结构,扩展性和复杂性留在了内部,而对外简化了接口。路由的处理函数,以及将要实现的中间件,参数都统一使用 Context 实例, Context 就像一次会话的百宝箱,可以找到任何东西。

day2-context/gee/context.go

type H map[string]interface{}type Context struct {// origin objectsWriter http.ResponseWriterReq    *http.Request// request infoPath   stringMethod string// response infoStatusCode int
}func newContext(w http.ResponseWriter, req *http.Request) *Context {return &Context{Writer: 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 = codec.Writer.WriteHeader(code)
}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")c.Status(code)c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}func (c *Context) JSON(code int, obj interface{}) {c.SetHeader("Content-Type", "application/json")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)
}func (c *Context) HTML(code int, html string) {c.SetHeader("Content-Type", "text/html")c.Status(code)c.Writer.Write([]byte(html))
}
  • 代码最开头,给map[string]interface{}起了一个别名gee.H,构建JSON数据时,显得更简洁。

  • Context目前只包含了http.ResponseWriter*http.Request,另外提供了对MethodPath这两个常用属性的直接访问,方便后续快速取用。

  • 提供了访问QueryPostForm参数的方法。

  • 提供了快速构造String/Data/JSON/HTML响应的方法。

路由(Router)

我们将和路由相关的方法和结构提取了出来,放到了一个新的文件中router.go,方便我们下一次对 router 的功能进行增强,例如提供动态路由的支持。routerhandle 方法作了一个细微的调整,即handler的参数,变成了 Context

day2-context/gee/router.go

type router struct {handlers map[string]HandlerFunc
}func newRouter() *router {return &router{handlers: make(map[string]HandlerFunc)}
}func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {log.Printf("Route %4s - %s", method, pattern)key := method + "-" + patternr.handlers[key] = handler
}func (r *router) handle(c *Context) {key := c.Method + "-" + c.Pathif handler, ok := r.handlers[key]; ok {// 注意:接收的参数由w http.ResponseWriter, r *http.Request变为了*Contexthandler(c)} else {c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)}
}

框架入口

day2-context/gee/gee.go

// HandlerFunc defines the request handler used by gee
// 接收的参数由w http.ResponseWriter, r *http.Request变为了*Context
type HandlerFunc func(*Context)// Engine implement the interface of ServeHTTP
type Engine struct {router *router
}// New is the constructor of gee.Engine
func New() *Engine {return &Engine{router: newRouter()}
}func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {engine.router.addRoute(method, pattern, 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) {// 构造了一个Context对象c := newContext(w, req)engine.router.handle(c)
}

router相关的代码独立后,gee.go简单了不少。最重要的还是通过实现了ServeHTTP接口,接管了所有的 HTTP 请求。相比第一天的代码,这个方法也有细微的调整,在调用 router.handle 之前,构造了一个Context对象。这个对象目前还非常简单,仅仅是包装了原来的两个参数,之后我们会慢慢地给Context插上翅膀。

从上面的代码不难看出,用户使用的时候是直接用Engine对象,低啊用Engine的相关方法,及时是和路由相关的也是直接使用Engine调用,但是Engine的路由底层实现其实是委托给了router对象的。

如何使用,main.go一开始就已经亮相了。运行go run main.go,借助 curl ,一起看一看今天的成果吧。

$ curl -i http://localhost:9999/
HTTP/1.1 200 OK
Date: Mon, 12 Aug 2019 16:52:52 GMT
Content-Length: 18
Content-Type: text/html; charset=utf-8
<h1>Hello Gee</h1>$ curl "http://localhost:9999/hello?name=geektutu"
hello geektutu, you're at /hello$ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
{"password":"1234","username":"geektutu"}$ curl "http://localhost:9999/xxx"
404 NOT FOUND: /xxx

原文地址:https://geektutu.com/post/gee-day2.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/872408.shtml

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

相关文章

04.阻塞赋值和非阻塞赋值

1.阻塞赋值 阻塞赋值的赋值号是用""表示,对应的电路结构往往与触发沿没有关系,只与输入电平的变化有关系.它的操作结构可以认为是只有一个步骤的操作,即计算赋号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他verilog语句的干扰,直到现行的赋值完成,才…

释放Conda通道束缚:启用自由通道恢复的终极指南

释放Conda通道束缚&#xff1a;启用自由通道恢复的终极指南 在Conda的生态中&#xff0c;通道&#xff08;channels&#xff09;是包来源的路径&#xff0c;而自由通道&#xff08;free channel&#xff09;通常指的是非限制性的包源&#xff0c;可以提供更多的包选择。有时&a…

《昇思25天学习打卡营第23天|onereal》

第23天学习内容简介&#xff1a; ----------------------------------------------------------------------------- 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 1 环境配置 配置网络线路 2 代码开发 下载权重大约需要10分钟 ------------------------------- 运…

大模型技术对学校有什么作用?

大模型技术对学校有多方面的作用&#xff0c;可以在教学、管理、决策等多个领域带来显著的改进。以下是大模型技术对学校的主要作用&#xff1a; 1. 个性化教学&#xff1a;大模型技术可以帮助教师分析学生的学习行为和历史成绩&#xff0c;从而定制个性化的教学计划和资源。这…

告别自动激活:掌握如何在Conda中禁用Base环境

告别自动激活&#xff1a;掌握如何在Conda中禁用Base环境 引言 在Python开发的世界中&#xff0c;环境管理是一个不可或缺的部分。Conda是一个强大的包管理器和环境管理器&#xff0c;它允许开发者为不同的项目创建隔离的环境&#xff0c;从而避免依赖冲突。默认情况下&#…

maven项目容器化运行之1-基于1Panel软件将docker镜像构建能力分享给局域网

一.背景 公司主机管理组的兄弟安装了1Panel(社区版v1.10.10-lts)&#xff0c;期望我们开发的小项目都通过docker来部署。我第一步要配置的就是怎么将docker镜像构建能力共享的问题&#xff0c;因为我本机是windows&#xff0c;不想再去折腾安装docker环境。 二.设置过程 个人…

ES6 对象的新增方法(十四)

1. Object.assign(target, …sources) 特性&#xff1a;将一个或多个源对象的所有可枚举属性复制到目标对象。 用法&#xff1a;用于对象属性的合并。 const obj1 { a: 1, b: 2 }; const obj2 { b: 3, c: 4 }; Object.assign(obj1, obj2);console.log(obj1); // 输出&#…

Linux桌面环境手动编译安装librime、librime-lua以及ibus-rime,提升中文输入法体验

Linux上的输入法有很多&#xff0c;大体都使用了Fcitx或者iBus作为输入法的引擎。相当于有了一个很不错的“地基”&#xff0c;你可以在这个“地基”上盖上自己的“小别墅”。而rime输入法&#xff0c;就是一个“毛坯别墅”&#xff0c;你可以在rime的基础上&#xff0c;再装修…

网络安全-网络安全及其防护措施4

16.网络故障排除 网络故障排除的定义和作用 网络故障排除是检测、诊断和解决网络问题的过程。通过系统的方法&#xff0c;确保网络的稳定性和可用性&#xff0c;减少故障对业务的影响。有效的网络故障排除可以减少停机时间&#xff0c;提高网络的可靠性和性能&#xff0c;确保…

HCNA ICMP:因特网控制消息协议

ICMP&#xff1a;因特网控制消息协议 前言 Internet控制报文协议ICMP是网络层的一个重要协议。ICMP协议用来在网络设备间传递各种差错和控制信息&#xff0c;他对于手机各种网络信息、诊断和排除各种网络故障有至关重要的作用。使用基于ICMP的应用时&#xff0c;需要对ICMP的工…

Apollo docker-compose

来源 https://www.apolloconfig.com/#/zh/deployment/quick-start-docker 路径 /usr/apollo Sql 自己复制 Vim docker-compose.yml #如果安装过了 记得删除mysql 历史文件 rm -r /var/lib/mysql version: 2.1services:apollo-quick-start:image: nobodyiam/apollo-quick…

AWS CDN新增用户ip 地区 城市 响应头

1.需要自定义cdn缓存策略 这里的策略也是先复制之前的cdn策略哈 最后复制完了 全部新增这两条标头key CloudFront-Viewer-Country CloudFront-Viewer-City 2.然后新增cdn函数&#xff0c;应用你写的这个函数 function handler(event) {var request event.request;var respon…

【grpc】内容回顾

建议先看官网了解概念&#xff0c;点击跳转到官网 proto约束文件介绍 命令 message关键字 相当于golang里的结构体 字段规则 repeated:代表切片等可重复类型 options&#xff1a;其他默认是options 消息号 message中每个字段必须有一个唯一标识号 服务定义 rpc 服务函…

python 语法学习 day9

一.编程题错题反思 1.删除列表中的奇数(易错题) num input().split() flag 0 for i in range(len(num)): num[i] int(num[i]) for i in range(len(num)): if flag ! 0: i - flag if num[i] % 2 ! 0: num.remove(num[i]) flag 1 pr…

PySide(PyQt),csv文件的显示

1、正常显示csv文件 import sys import csv from PySide6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QWidgetclass CSVTableWidgetDemo(QMainWindow):def __init__(self):super().__init__()# 创建显示控件self.widget QWidget(self)sel…

在SpringCloud中实现服务监控与告警的业务指标监控

在Spring Cloud中&#xff0c;可以使用一些工具和框架来实现服务监控与告警的业务指标监控。本文将介绍一种常用的方案&#xff0c;使用Prometheus和Grafana来实现监控指标的采集和展示&#xff0c;并结合Alertmanager实现告警功能。 Prometheus简介 Prometheus是一款开源的监控…

动手学深度学习——3.多层感知机

1.线性模型 线性模型可能出错 例如&#xff0c;线性意味着单调假设&#xff1a; 任何特征的增大都会导致模型输出的增大&#xff08;如果对应的权重为正&#xff09;&#xff0c; 或者导致模型输出的减小&#xff08;如果对应的权重为负&#xff09;。 有时这是有道理的。 例…

ENSP防火墙NAT智能选举综合实验

实验目的及其拓扑图&#xff1a; 创建我的拓扑&#xff1a; 新建修改配置fw1上的nat策略和安全策略&#xff1a; pc2ping1.1.1.1测试结果&#xff1a; 服务器映射配置&#xff1a; 配置对应安全策略&#xff1a; 配置fw2&#xff0c;子公司的NAT策略&#xff1a; 配置全局选路路…

代码随想三刷图论篇2

代码随想三刷图论篇2 104. 建造最大岛屿题目代码 110. 字符串接龙题目代码 105. 有向图的完全可达性题目代码 106. 岛屿的周长题目代码 104. 建造最大岛屿 题目 链接 代码 import java.util.*; class Main{public static void main(String[] args){Scanner sc new Scanner…

安全运营概述

安全运营概述 概述安全运营的工作对内安全运营工作对外安全运营工作品牌建设 概述 安全是一个过程&#xff0c;安全是靠运营出来的&#xff0c;公司会不断的有新业务的变更&#xff0c;新产品的发布&#xff0c;新版本的升级&#xff0c;技术架构的升级&#xff0c;底层系统的…