使用Go语言自制简单易用的Web框架
作者:dpwgc
为什么写这玩意
有时我要写一些很小的应用,比如爬虫后端、服务器脚本又或者是BFF服务,需要简单的Web服务。用gin感觉有点重,go.mod里会有很多用不到的依赖,直接用httprouter又很麻烦。所以想整个简单易用的Web框架。在实现基础能力的同时,顺便集成一些方便开发的功能(便捷的Websocket连接、SSE推送、自动绑定请求数据、自动序列化返回响应数据),并且尽量不引入第三方依赖,保持极简架构。
大概的架构图
技术选型
- 底层HTTP路由:选择httprouter。
- 将请求头/表单/URI参数绑定到结构体上:选择mapstructure。
框架目前的go.mod依赖情况:
module github.com/dpwgc/easierweb go 1.21.4 require ( github.com/julienschmidt/httprouter v1.3.0 github.com/mitchellh/mapstructure v1.5.0 golang.org/x/net v0.17.0 gopkg.in/yaml.v3 v3.0.1 )
基本用法
首先,运行go get,安装一下框架:
go get github.com/dpwgc/easierweb
然后,编写路由代码,写起来和gin差不多,没有学习成本:
package main import ( "github.com/dpwgc/easierweb" "github.com/dpwgc/easierweb/middlewares" "log" "net/http" ) func main() { // 新建一个路由 router := easierweb.New() // 使用日志中间件 router.Use(middlewares.Logger()) // GET接口 router.GET("/hello/:name", hello) // 启动服务 log.Fatal(router.Run(":80")) } // 请求处理函数 func hello(ctx *easierweb.Context) { // 获取URI Path里的name参数 name := ctx.Path.Get("name") // 绑定URI Query参数到指定结构体上 req := Request{} err := ctx.BindQuery(&req) if err != nil { panic(err) } fmt.Println("request data:", req) // 响应:hello {name} ctx.WriteString(http.StatusOK, "hello "+name) } // 请求结构体 type Request struct { Name string `mapstructure:"name"` Mobile string `mapstructure:"mobile"` }
进阶改造(将繁琐的流程自动化)
实际在使用的时候,每次获取请求数据都得调用一下Bind函数,然后响应数据时又得调用下Write函数再return,感觉还是不够简单,因此加了个通过反射自动绑定请求数据到结构体、自动序列化写入响应数据的功能。
实现方式参考: github.com/MikeLINGxZ/simple-server-runner 这是一个基于Gin的自动绑定请求数据+自动写入响应数据的库。
最终成品有点像Spring Boot那种接口方法样式:
package main import ( "fmt" "github.com/dpwgc/easierweb" "github.com/dpwgc/easierweb/middlewares" "log" ) func main() { // 还是老样子,先创建一个路由 router := easierweb.New().Use(middlewares.Logger()) // 使用EasyPOST函数(EasyXXX函数内置了自动绑定+自动写入),整一个POST提交接口 router.EasyPOST("/submit", submit) // 启动服务 log.Fatal(router.Run(":80")) } // 请求处理函数(自动绑定请求数据+自动写入响应数据) // 这个请求处理函数和上文的基础版不大一样,除了上下文入参以外,还有个请求数据入参和响应数据返回体 // 当这个函数被调用时,会通过反射将POST请求的Body数据自动解析并绑定到req参数上(如果是GET请求就绑定Query参数) // 当这个函数返回时,同样通过反射获取到返回的结构体,将其序列化成Json字符串后,写入响应 func submit(ctx *easierweb.Context, req Request) *Response { // 打印req的参数 fmt.Printf("json body -> name: %s, mobile: %s \n", req.Name, req.Mobile) // 直接return结构体 return &Response{Code: 1000, Msg: "hello"} } type Request struct { Name string `json:"name"` Mobile string `json:"mobile"` } type Response struct { Code int `json:"code"` Msg string `json:"msg"` }
请求入参和响应返回值在反射赋值/取值时都做了动态化识别,可传可不传,不传req入参时就不会自动绑定请求数据,不返回Response且没有报错时就响应204,返回了error或者函数抛出异常了就返回400/500,Response可以是对象也可以是切片。
func TestAPI(ctx *easierweb.Context, req Request) (*Response, error) func TestAPI(ctx *easierweb.Context, req Request) *Response func TestAPI(ctx *easierweb.Context, req Request) error func TestAPI(ctx *easierweb.Context, req Request) func TestAPI(ctx *easierweb.Context) (*Response, error) func TestAPI(ctx *easierweb.Context) *Response func TestAPI(ctx *easierweb.Context) error func TestAPI(ctx *easierweb.Context)
实际开发中,不一定以Json格式来接收/呈现数据,所以留了个插件口子,可以让用户自定义自动绑定/写入数据的序列化与反序列化方式。
// 使用XML格式来处理自动绑定和自动写入的数据 router := easierweb.New(easierweb.RouterOptions{ RequestHandle: plugins.XMLRequestHandle(), ResponseHandle: plugins.XMLResponseHandle(), })
追加功能(常用方法封装)
我写浏览器Js爬虫经常要用Websocket来连接后端并持续传输数据,还有些表单动态赋值的需求经常要用到SSE,那就顺便给框架整个websocket和SSE的快捷使用方式,同时把连接升级、跨域、SSE请求头设置、连接关闭之类的操作全部都封装到底层,不需要使用者操心这么多事情。
func main() { // 新建路由 router := easierweb.New() // 快速使用websocket router.WS("/demoWS/:id", DemoWS) // 快速使用SSE router.SSE("/demoSSE/:id", DemoSSE) // 启动 log.Fatal(router.Run(":80")) }
// 快速使用websocket func DemoWS(ctx *easierweb.Context) { // 处理websocket连接 for { // 接收客户端消息 msg, err := ctx.ReceiveString() if err != nil { panic(err) } fmt.Println("read websocket msg:", msg) // 发送消息给客户端 err = ctx.SendString("hello") if err != nil { panic(err) } time.Sleep(1 * time.Second) // 函数返回时,websocket连接会自动关闭,不劳烦用户手动调close了 return } }
// 快速使用SSE func DemoSSE(ctx *easierweb.Context) { // 循环推送数据 for i := 0; i < 5; i++ { // SSE推送数据, data: hello, id: {i} err := ctx.Push(fmt.Sprintf("data: hello\nid: %v\n\n", i)) if err != nil { panic(err) } time.Sleep(1 * time.Second) } }
以上就是使用Go语言自制简单易用的Web框架的详细内容,更多关于Go Web框架的资料请关注脚本之家其它相关文章!