使用Go语言自制简单易用的Web框架

 更新时间:2024年01月15日 10:50:49   作者:dpwgc  
这篇文章主要为大家详细介绍了如何使用Go语言实现自制简单易用的Web框架,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

为什么写这玩意

有时我要写一些很小的应用,比如爬虫后端、服务器脚本又或者是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框架的资料请关注脚本之家其它相关文章!

相关文章

  • 如何在Go语言中高效使用Redis的Pipeline

    如何在Go语言中高效使用Redis的Pipeline

    在 Redis 中,Pipeline 就像一条流水线,它允许我们将多个命令一次性发送到服务器,下面我们就来看看如何在Go语言中高效使用Redis的Pipeline吧
    2024-11-11
  • go如何删除字符串中的部分字符

    go如何删除字符串中的部分字符

    这篇文章主要介绍了go删除字符串中的部分字符操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Golang内存模型教科书级讲解

    Golang内存模型教科书级讲解

    go官方介绍go内存模型的时候说:探究在什么条件下,goroutine 在读取一个变量的值的时,能够看到其它 goroutine 对这个变量进行的写的结果,Go内存模型规定了一些条件,在这些条件下,在一个goroutine中读取变量返回的值能够确保是另一个goroutine中对该变量写入的值
    2023-03-03
  • Golang接口型函数使用小结

    Golang接口型函数使用小结

    接口函数指的是用函数实现接口,这样在调用的时候就会非常简便,这种方式适用于只有一个函数的接口,这里以迭代一个map为例,演示这一实现的技巧,对Golang接口型函数使用知识感兴趣的朋友一起看看吧
    2022-06-06
  • Golang定时任务框架GoCron的源码分析

    Golang定时任务框架GoCron的源码分析

    本文主要介绍了Golang定时任务框架GoCron的源码分析,原生的gocron存在一些问题,如任务列表维护不当、并发操作不可预测等,经过改进的gocron解决了这些问题,感兴趣的可以了解一下
    2025-03-03
  • golang panic及处理机制

    golang panic及处理机制

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱,今天给大家介绍golang panic及处理机制,需要的朋友参考下吧
    2021-08-08
  • go使用SQLX操作MySQL数据库的教程详解

    go使用SQLX操作MySQL数据库的教程详解

    sqlx 是 Go 语言中一个流行的操作数据库的第三方包,它提供了对 Go 标准库 database/sql 的扩展,简化了操作数据库的步骤,下面我们就来学习一下go如何使用SQLX实现MySQL数据库的一些基本操作吧
    2023-11-11
  • Go基础教程系列之import导入包(远程包)和变量初始化详解

    Go基础教程系列之import导入包(远程包)和变量初始化详解

    这篇文章主要介绍了Go基础教程系列之import导包和初始化详解,需要的朋友可以参考下
    2022-04-04
  • golang版本升级的简单实现步骤

    golang版本升级的简单实现步骤

    个人感觉Go在众多高级语言中,是在各方面都比较高效的,下面这篇文章主要给大家介绍了关于golang版本升级的简单实现步骤,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • go语言中如何使用select的实现示例

    go语言中如何使用select的实现示例

    本文主要介绍了go语言中如何使用select的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05

最新评论