苦学golang半年,写了一款web服务器😊

苦学golang半年,写了一款web服务器

项目地址:github.com/fengyuan-li...

苦学golang半年,写了一款web服务器,里面包含笔者各种工程实践,大佬勿喷😊

为什么不使用Gin,而要自己开发一款web服务器呢?其实gin已经非常好了👍,笔者这里主要是想要把自己开发中的工程实践提炼出来,打造出更加顺手的兵器🏹️(现在还只是个玩具🪀,大家看个乐子就行)

那么在使用Gin中有哪些痛点呢?

  • 繁琐的路由表,在Gin中必须写路由表来映射路由,再写对应的func(ctx *gin.Context ),总感觉多写了一次,而且接口一多,看着一望无际的路由表,总感觉划分的不是很优雅,而且不太好找自己想要的接口;并且笔者在写springBoot项目时,非常喜欢用restful插件来找路由,比如输入/v1/xxx/xxx/xxx就能找到对应的路由,但是在gin的路由表不是很好找

  • 每个gin的路由都必须要手动获取参数,然后校验,校验不通过再返回错误(像下面那样),这部分逻辑感觉完全应该复用(完全忍受不了写重复代码 😠)。参数就应该交给框架解析,或者说有切面或者hook来统一完成这部分的逻辑

    go 复制代码
    // gin
    engine.Get("/v1/xxx", xxx)
    
    func xxx(ctx *gin.Context) {
      var (
        err error
        params xxxx
      )
      // Bind your param data
    	if err = ShouldBindQuery(ctx, &params); err != nil {
        ctx.AbortWithStatusJSON(http.StatusOK, "traceId", 400, "bad request"))
    		return
    	}
      // validator your param
      if err = validator.New().Struct(&params) ;err != nil {
    		ctx.AbortWithStatusJSON(http.StatusOK, "traceId", 400, "bad request"))
    		return
    	}
      // do you code
    	ctx.JSON(http.StatusOK, "traceId", "ok"))
    }
    // 相比之下,参数Jet会自动帮你注入到你的参数列表里面,并且可以定义Hook统一在参数解析完毕,调用我们自己方法之前处理参数校验的逻辑
    // Jet
    func(YourJetController) GetV1Xxx(ctx jet.Ctx, args *Xxx) (any, error) {
      // do you code
      return xxx, err
    }
  • 接下来不是Gin的缺点,毕竟Gin只是一个基础的web框架,就是笔者更喜欢MVC架构或者DDD模式开发,这里面使用到依赖注入管理生命周期是比较合适的,笔者也不喜欢用类似wire需要生成代码的方式进行依赖注入,所有笔者使用Dig进行依赖注入,反射的方式其实也只影响项目启动的时间,但是go的启动本身就很快了,看不出啥影响

  • 然后就是定义了一些常用的数据结构,例如TrieLinkedHashMap,在golang里面其实提供的数据结构挺少的,但是像LinkedHashMap用的地方其实很多,我们需要O(1)级别的查找和添加,有需要有序的集合顺序

    go 复制代码
    func TestLinkedHashMap(t *testing.T) {
        m := maps.NewLinkedHashMap[string, int]()
    
        m.Put("one", 1)
        m.Put("two", 2)
        m.Put("three", 3)
    
        m.ForEach(func(k string, v int) {
            t.Logf("%s: %d\n", k, v)
        })
    }
    
    $ go test -run TestLinkedHashMap
    one: 1
    two: 2
    three: 3
    PASS
    ok      GoKit/collection/maps   0.166s

example

下面是使用的一个例子

go 复制代码
func main() {
	//jet.Register(&DemoController{})
	xlog.SetOutputLevel(xlog.Ldebug)
	jet.AddMiddleware(jet.TraceJetMiddleware)
	jet.Run(":8080")
}

// 使用依赖注入的方式注入需要让Jet管理的Controller
func init() {
	jet.Provide(NewDemoController)
}

func NewDemoController() jet.ControllerResult {
	return jet.NewJetController(&DemoController{})
}

type BaseController struct {
	jet.IJetController
}

// 对参数进行校验,如果不通过会返回`reg_err_info`中定义的错误
func (BaseController) PostParamsParseHook(param any) error {
	if err := utils.Struct(param); err != nil {
		return errors.New(utils.ProcessErr(param, err))
	}
	return nil
}

// PostMethodExecuteHook restful 将所有请求以restful方式返回
func (BaseController) PostMethodExecuteHook(param any) (data any, err error) {
	// restful
	return utils.ObjToJsonStr(param), nil
}

// 上面的两个hook可以直接让controller继承jet.BaseJetController,这样就不用写了

type DemoController struct {
	BaseController
}

type Person struct {
	Name string `json:"name" validate:"required" reg_err_info="不能为空"` // 校验不通过会返回`reg_err_info`的内容
	Age  int    `json:"age"`
}

// 路由 get /v1/usage/{id}/week 已经可以访问了
func (j *DemoController) GetV1Usage0Week(ctx jet.Ctx, args *jet.Args) (any, error) {
	ctx.Logger().Infof("GetV1Usage0Week %v", *args)
  return map[string]any{"request_id": ctx.Logger().GenReqId(), "code": 200, data: args}
}
go 复制代码
$ curl http://localhost:8080/v1/usage/1/week
{"request_id":"H5OQ4Jg0yBtg","code":200,"message":"success","data":["1"]}

正常情况下会打印日志的全路径,我们在启动时候加上-trimpath就可以只打印项目的path

相关推荐
ServBay3 分钟前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好23 分钟前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780511 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python
Java面试题总结1 小时前
CubeSandbox 实战:从零部署到快照/克隆/回滚全体验
后端
Csvn1 小时前
Python 开发技巧:函数式编程与 lambda 进阶
后端
考虑考虑2 小时前
nginx配置ssl
运维·后端·nginx
uhakadotcom2 小时前
Python 中,`None` 不等于 `False`导致的问题
后端·面试·github
SamDeepThinking2 小时前
面向对象真正管理的,不是对象,而是依赖
java·后端·程序员
9i编程2 小时前
5.Nacos 配置中心化改造与短信 Debug 模式动态切换实践
后端·spring cloud
PinkSun2 小时前
上线第一天,Redis和Docker联手给我上了一课
后端