【Golang学习笔记】从零开始搭建一个Web框架(一)

文章目录

    • net/Http基础
    • 框架雏形
    • 上下文

学习文档:

Go语言标准库文档中文版

7天用Go从零实现Web框架Gee教程 | 极客兔兔 (geektutu.com)

gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang).

net/Http基础

go语言的http包提供了HTTP客户端和HTTP服务端的实现。下面是一个简单的HTTP服务端:

package mainimport ("fmt""net/http"
)func main() {http.HandleFunc("/hello",func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello World")}http.ListenAndServe(":8080", nil)
}

浏览器访问127.0.0.1:8080/hello可以看到Hello World。

func ListenAndServe(addr string, handler Handler) error

在go标准库文档中得知ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。当handler参数为nil时会使用DefaultServeMux。

而DefaultServeMux是一个ServeMux类型的实例,ServeMux拥有一个SeveHTTP方法。

var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMux
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

ListenAndServe函数中handler参数是Handle接口实现的实例,其中只有ServeHTTP这一个方法。 ListenAndServe函数只要传入任何实现 ServerHTTP接口的实例,所有的HTTP请求,就会交给该实例处理。

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

框架雏形

事已至此,先创建个文件夹吧!给框架命名为kilon,目录结构如下:

myframe/├── kilon/│   ├── go.mod    [1]│   ├── kilon.go├── go.mod        [2]├── main.go

从gin框架的实现(gin/gin.go)中可以看到gin引擎主要有以下方法:

func New(opts ...OptionFunc) *Engine // 创建一个新的引擎对象
func (origin *Engine) addRoute(method, path string, handlers HandlersChain) // 向引擎对象添加路由信息
func (origin *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) // 实现ServeHttP接口
func (origin *Engine) Run(addr ...string) (err error) // 启动 HTTP 服务器并监听指定的地址和端口

在kilon.go中模仿gin.go添加下面代码:

package kilonimport ("fmt""net/http"
)
// 给函数起别名,方便作为参数调用
type HandlerFunc func(http.ResponseWriter, *http.Request)
// 引擎对象
type Origin struct {// 存储路由信息的map,key为路由信息,这里使用Method + "-" + Path的格式,value为路由绑定的方法router map[string]HandlerFunc
}
// 创建一个新的引擎对象
func New() *Origin {// make函数用于slice、map以及channel的内存分配和初始化return &Origin{router: make(map[string]HandlerFunc)}
}
// 向引擎对象添加路由信息,并绑定函数。
func (origin *Origin) addRoute(method string, pattern string, handler HandlerFunc) {// Method + "-" + Path 构造key值key := method + "-" + pattern// 插入需要绑定的函数origin.router[key] = handler
}
// 向引擎对象注册GET、POST方法的路由信息,进行封装,降低参数数量,并提高使用时的代码可读性。
func (origin *Origin) GET(pattern string, hander HandlerFunc) {origin.addRoute("GET", pattern, hander)
}
func (origin *Origin) POST(pattern string, hander HandlerFunc) {origin.addRoute("POST", pattern, hander)
}
// 实现ServeHttP接口
func (origin *Origin) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从请求中携带的参数构造pathkey := req.Method + "-" + req.URL.Path // 如果该路由信息已经注册,则调用绑定的函数if handler, ok := origin.router[key]; ok {// 调用绑定的函数handler(w, req)} else {// 路由没注册,返回 404fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)}
}
// 启动 HTTP 服务器并监听指定的地址和端口
func (origin *Origin) Run(addr string) (err error) {// 本质是调用http包的ListenAndServe启动。(实现了接口方法的 struct 可以自动转换为接口类型)return http.ListenAndServe(addr, origin)
}

至此,框架的雏形已经搭建好,实现了路由映射表、提供了用户注册方法以及包装了启动服务函数。下面在main.go中进行测试:
在go.mod [2] 中添加下面内容 (先使用指令go mod init生成go.mod):

replace kilon => ./kilon

这行代码告诉go mod 在导包时不从网上寻找包,而是使用当前目录下的kilon包。

在main.go中添加代码:

package mainimport ("fmt""kilon""net/http"
)func main() {r := kilon.New()r.GET("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello World")})r.Run(":8080") // 省略地址指监听所有网络的8080端口
}

运行指令:go mod tidy ,后可以在go.mod [2]看到:

module myframego 1.22.1replace kilon => ./kilonrequire kilon v0.0.0-00010101000000-000000000000 // 成功导入

运行代码后,访问127.0.0.1:8080/hello可以看到Hello World。

上下文

r.GET("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello World")
}) // 框架雏形路由注册

框架雏形的使用还是有点麻烦,所有的请求与响应内容还有数据格式的处理都需要用户来实现。这时候需要引入上下文的概念来解决这个问题。上下文"(context)指的是 HTTP 请求的上下文环境,它包含了当前请求的各种信息,比如请求的 URL、HTTP 方法、请求头、请求体等等。

r.GET("/hello", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Hello World",})
}) // Gin框架路由注册

在 Gin 框架中,上下文由 gin.Context( gin/context.go)类型表示,它是对 HTTP 请求的抽象,提供了丰富的方法来处理请求和响应。每当收到一个 HTTP 请求,Gin 就会创建一个新的上下文对象,然后将该对象传递给对应的处理函数。开发者可以通过这个上下文对象,方便地获取请求的相关信息(如动态路由信息,中间件信息等),以及向客户端发送响应数据。

新建context.go文件设计上下文对象,当前目录结构为:

myframe/├── kilon/│   ├── context.go│   ├── go.mod      [1]│   ├── kilon.go├── go.mod          [2]├── main.go

具体实现:

创建一个名为Context的struct,写入需要便捷获取到的请求属性

type Context struct {Writer     http.ResponseWriterReq        *http.RequestPath       string // 请求路径,从req中获取Method     string // 请求方法,从req中获取StatusCode int // 响应状态码,由用户输入
}

写入构造方法,在构造方法中从req获取到对应属性:

func newContext(w http.ResponseWriter, req *http.Request) *Context {return &Context{Writer: w,Req:    req,Path:   req.URL.Path, // 从req中获取请求路径Method: req.Method, // 从req中获取请求方法}
}

包装方法,让用户可以通过Context对象获取到req的信息与设置响应内容,而无需关心Context内部信息。

// 从req的post表单中获取指定键的值。
func (c *Context) PostForm(key string) string {return c.Req.FormValue(key)
}
// 用于从req的URL查询参数中获取指定键的值。
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) Data(code int, data []byte) {c.Status(code)c.Writer.Write(data)
}

Content-Type 是 HTTP 头部中的一个字段,用于指示发送给客户端的实体正文的媒体类型。在标准的 HTTP 协议中,Content-Type 可以有很多种取值,常见的包括:

  1. text/plain: 表示纯文本内容。
  2. text/html: 表示 HTML 格式的文档。
  3. application/json: 表示 JSON 格式的数据。
  4. application/xml: 表示 XML 格式的数据。
  5. application/octet-stream: 表示二进制流数据。
  6. image/jpeg: 表示 JPEG 格式的图片。
  7. image/png: 表示 PNG 格式的图片。

在框架雏形中如果用户想返回这些类型的格式数据,需要自己设置响应头部信息并将数据编码成对应格式。接下来在框架中包装多个方法,让用户可以简单的调用对应方法,就可以将数据以需要的格式响应。

// 设置HTTP响应头部的指定字段和值
func (c *Context) SetHeader(key string, value string) {c.Writer.Header().Set(key, value)
}
// 纯文本内容,可变参数values用于实现fmt.Sprintf的包装
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...)))
}
// HTML格式内容
func (c *Context) HTML(code int, html string) {c.SetHeader("Content-Type", "text/html")c.Status(code)c.Writer.Write([]byte(html))
}
// Json格式内容
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)}
}

当用户想调用JSON方法时,每次都需要自己定义一个接口对象再写入数据,还是不够方便。这个时候可以给接口对象起一个别名:

type H map[string]interface{}

这样就可以在调用的时候直接像gin框架那样直接gin.H{}写入json数据了。

现在Context.go中内容如下:

package kilonimport ("encoding/json""fmt""net/http"
)type H map[string]interface{}type Context struct {Writer     http.ResponseWriterReq        *http.RequestPath       stringMethod     stringStatusCode 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))
}

现在Context.go已经写好了,但是框架引擎的函数调用还是:

type HandlerFunc func(http.ResponseWriter, *http.Request)

现在需要将其换成Context对象参数。

type HandlerFunc func(*Context)

并且ServeHTTP中调用逻辑需要改写:

func (origin *Origin) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := newContext(w, req) // 创建一个Context实例key := req.Method + "-" + req.URL.Pathif handler, ok := origin.router[key]; ok {handler(c) // 现在路由注册的函数参数已经换成了Context对象} else {fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)}
}

至此上下文对象已经完成了,在main.go中测试一下:

package mainimport ("kilon""net/http"
)func main() {r := kilon.New()r.GET("/hello0", func(ctx *kilon.Context) {ctx.Data(http.StatusOK, []byte("Hello World"))})r.GET("/hello1", func(ctx *kilon.Context) {ctx.String(http.StatusOK, "Hello %s", "World")// ctx.String(http.StatusOK, "Hello World")})r.GET("/hello2", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"message": "Hello World",})})r.GET("/hello3", func(ctx *kilon.Context) {ctx.HTML(http.StatusOK, "<h1>Hello World</h1>")})r.Run(":8080")
}

代码运行后访问对应地址可以看到不同结果。

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

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

相关文章

js 函数的数据类型

在JavaScript中&#xff0c;函数是一种特殊的对象&#xff0c;可以存储在变量中&#xff0c;可以作为参数传递给其他函数&#xff0c;也可以作为函数的返回值返回。 函数的数据类型为"function"&#xff0c;可以使用typeof运算符来检测一个变量是否为函数类型&#…

Day101:漏洞发现-漏扫项目篇NucleiYakitGobyAfrogXrayAwvs联动中转被动

目录 特征类-三方Poc调用&模版Poc调用 案例1&#xff1a;单点对某特征点进行安全评估 Goby-综合类 Nuclei-较综合类 Afrog-特征类 Yakit-可特征可综合 案例2&#xff1a;新型对某特征点进行安全评估 综合类-主动漏扫&中转联动&被动联动 案例1&#xff1a;…

移动开发避坑指南——内存泄漏

在日常编写代码时难免会遇到各种各样的问题和坑&#xff0c;这些问题可能会影响我们的开发效率和代码质量&#xff0c;因此我们需要不断总结和学习&#xff0c;以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结&#xff0c;以提高大家的开发质量。本系列文章…

03-进程-网络命令-软件安装-SSH免密登录

软件安装-网络命令-SSH免密登录 一 软件安装 1 在线安装 命令&#xff1a; yum 作用&#xff1a;yum会从指定的服务器自动下载rpm包并且进行安装&#xff0c;优点是可以自动处理依赖关系&#xff0c;并且一次安装所有的软件包。 配置yum的镜像源 yum源有个统一的下载服务…

剖析 SPI 在 Spring 中的应用

一、概述 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是Java内置的一种服务提供发现机制&#xff0c;可以用来提高框架的扩展性&#xff0c;主要用于框架的开发中&#xff0c;比如Dubbo&#xff0c;不同框架中实现略有差异&#xff0c;但核心机制相同…

JavaScript中的cookie、localStorage的区别和特点

在JavaScript中&#xff0c;cookie和localStorage都是用于在客户端存储数据的方法&#xff0c;但它们有一些重要的区别和特点。 Cookie 特点&#xff1a; 存储量小&#xff1a;每个域名下的cookie数量是有限制的&#xff0c;并且每个cookie的大小也是有限制的。一般来说&…

精确号码比例放通算法的设计与实现

精确号码比例放通算法的设计与实现 引言背景问题定义算法设计1. 数据结构2. 算法流程3. 伪代码4. C语言实现 结论参考文献 引言 随着通信技术的飞速发展&#xff0c;呼叫中心和电信运营商面临着日益增长的呼叫管理需求。在某些情况下&#xff0c;为了确保服务质量或者遵守特定…

LeetCode题练习与总结:加一--66

一、题目描述 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 示例 1&#xff1a; 输入&…

浏览器从输入url到渲染的过程

URL 解析&#xff1a; 当用户在浏览器地址栏输入 URL 时&#xff0c;浏览器会首先对 URL 进行解析。这个过程包括解析协议&#xff08;如 HTTP、HTTPS&#xff09;、域名、端口号和路径等信息。 DNS 解析&#xff1a; 把从输入的URL中解析出的域名发送给 DNS 服务器进行解析&a…

方案分享 | 嵌入式指纹方案

随着智能设备的持续发展&#xff0c;指纹识别技术成为了现在智能终端市场和移动支付市场中占有率最高的生物识别技术。凭借高识别率、短耗时等优势&#xff0c;被广泛地运用在智能门锁、智能手机、智能家居等设备上。 我们推荐的品牌早已在2015年进入指纹识别应用领域&#xff…

[dvwa] sql injection

sql injection 0x01 low sql语句没有过滤 经典注入&#xff0c;通过逻辑or为真相当于select * from users where true&#xff0c;99换成1也成 用union select 对齐列数&#xff0c;查看数据库信息 1’ union select 1,2# order by探测对齐列数更方便 1’ or 11 order b…

Python数据挖掘项目开发实战:处理作者归属问题

注意&#xff1a;本文下载的资源&#xff0c;与以下文章的思路有相同点&#xff0c;也有不同点&#xff0c;最终目标只是让读者从多维度去熟练掌握本知识点。 Python数据挖掘项目开发实战&#xff1a;处理作者归属问题 一、项目背景与目标 在出版、科研等领域&#xff0c;确定…

05.MySQL索引事务

1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。 可以对表中的一列或多列创建索引&#xff0c;并指定索引的类型&#xff0c;各类索引有各自的数据结构实现 1.2 作用 数据库中的表、数据、索引之间的关系&#xff0c;类似于书架上的…

Spring AI 应用 - 智能记者

参考实现&#xff1a; https://github.com/mshumer/ai-journalist 上面是通过 Claude 配合 SERP 搜索 API&#xff0c;使用 Python 语言实现的&#xff0c;本文通过 GitHub Copilot 辅助改为了基于 Spring AI 的 Java 版本&#xff0c;本文使用的 OpenAI。 AIJournalist 实现…

Tomcat源码解析——源码环境搭建

一、源码下载 在进行源码阅读前&#xff0c;先下载源码包&#xff0c;这样便于做笔记和debug。 我所用的版本是Tomcat7.0.68&#xff0c; Tomcat7.0.68下载地址&#xff1a;Index of /dist/tomcat/tomcat-7/v7.0.68/src 所有Tomcat的源码包下载地址&#xff1a;Index of /dist/…

第6章:6.4.2 案例二:爬取成语网站数据 (MATLAB入门课程)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 本案例用到的网址为&#xff1a;成语大全列表成语大全列表https…

FFmpeg:自实现ijkplayer播放器--11音视频同步

文章目录 音视频同步时钟结构时间api实现过程音视频同步 音视频同步采用以音频为基准的方式,使用时间轴作为参考 如部分音频数据解不出来时,计算音频的pts(时间戳)与时间轴的差值,视频时间为差值加上时间轴,使得时间戳和音频一样 时间轴统是通过av_gettime_relative()获取…

c语言如何理解指针的指针?

1.啥叫指针&#xff1f; 在C语言中&#xff0c;指针是一个非常重要的概念。指针本质上是一个变量&#xff0c;它的值不是数据本身&#xff0c;而是存储数据的内存地址。指针允许程序直接访问和操作内存中的数据&#xff0c;这在很多情况下非常有用&#xff0c;比如在处理数组、…

NSA发布《在数据支柱中推进零信任成熟度》报告

4月9日&#xff0c;美国国家安全局&#xff08;NSA&#xff09;发布了题为《在数据支柱中推进零信任成熟度》的报告&#xff0c;旨在于数据安全层面提供指导&#xff0c;以增强数据整体安全性并保护静态和传输中的数据。(如下图&#xff09; 一、主要内容 报告中的建议侧重于将…

企业电子招标采购系统源码之从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理

功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查看所…