Go语言实现一个Http Server框架(三) Context的抽象

170 阅读3分钟

上一篇文章中我们对Server进行了抽象,但是在具体接口的实现过程中,还是存在一些问题,比如我们实现一个注册接口

image.png 重复的代码我们要写上很多遍,这个时候我们就可以对Context也就是上下文进行抽象

首先定义一个结构体,用于json的序列化和反序列化

type signUpReq struct{
    Email string `json:"email"`
    PassWord string `json:"password"`
    ConfirmedPassword string `json:"confirmed_pwd"`
}

定义一个Context结构体

type Context struct{
    W http.ResponseWriter
    R *http.Request
}

声明一个方法接收器,用于处理json字符串

func (c *Context) ReadJson(data interface{}) error {
    body,err := io.ReadAll(c.R.Body)
    if err!= nil{
       return err
    }
    return json.Unmarshal(body,data)
}

实现注册接口

func SignUp(w http.ResponseWriter,r *http.Request){
    c := web.Context(w,r)
    req := signUpReq{}
    err := c.ReadJson(req)
    if err != nil {
        fmt.Fpringf(w,"请求出错:%v\n",err)
    }
    //没有异常返回一个虚拟用户id
    fmt.Fprintf(w, "%d", 1)
    
}
此时虽然代码简洁了一些,但仍然存在问题,就是与fmt库存在强耦合,并且很难给客户端返回json格式数据,也没有响应码

写入响应

//commomResponse 定义响应结构
type commonResponse struct {  
    BizCode int `json:"biz_code"`  
    Msg string `json:"msg"`
    Data interface{} `json:"data"`
     
}
func SignUp(w http.ResponseWriter,r *http.Request){
    c := web.Context(w,r)
    req := signUpReq{}
    err := c.ReadJson(req)
    if err != nil {
        resp := &commonResponse{
            BizCode:500,
            Msg: fmt.Springf("请求出错:%v",err)
        }
        respBytes,_ := jsonMarshal(resp)
        fmt.Fpringf(w,string(respBytes))
        return
    }
}
//正确的返回逻辑,还得再写一遍
fmt.Fprintf(w, "%d", 1)

定义处理响应的方法接收器

func (c *Context) WriteJson(code int, resp interface{}) error {  
    c.W.WriteHeader(code)  
    respJson, err := json.Marshal(resp)  
    if err != nil {  
        return err  
    }  
    _, err = c.W.Write(respJson)  
    return nil  
}

辅助方法

然后我们可以提供一些辅助方法,但是要注意的是,实际场景中要考虑是否需要提供某些方法

func (c *Context) OkJson(resp interface{}) error {  
    return c.WriteJson(http.StatusOK, resp)  
}  

func (c *Context) NotFoundJson(resp interface{}) error {  
    return c.WriteJson(http.StatusNotFound, resp)  
}  

func (c *Context) ServerErrorJson(resp interface{}) error {  
    return c.WriteJson(http.StatusInternalServerError, resp)  
}  

func (c *Context) BadRequestJson(resp interface{}) error {  
    return c.WriteJson(http.StatusBadRequest, resp)  
}

此时我们的注册接口,已经可以进化成下面的样子了

func SignUpWithContext(w http.ResponseWriter,r *http.Request) {  
    c := web.Context(w,r)
    req := &signUpReq{}  

    err := ctx.ReadJson(req)  
    if err != nil {  
        c.BadRequestJson(&commonResponse{
            BizCode:500,
            Msg: fmt.Springf("请求出错:%v",err)
        })
        return  
    }  
    resp := &commonResponse{  
        BizCode: 200, 
        Msg: "ok",
        Data:1
    }  
    err = c.OkJson(resp)
    if err != nil {  
        fmt.Printf("请求出错:%v",err)  
    }  
}

我们已经对Context以及接口的实现进行了一定程度上的改造,但是仍然存在问题,就是Context是由用户进行创建的,也就是用户有着一定的自由度,而我们希望由框架来创建Context

func SignUpWithContext(ctx *Context) {  
    req := &signUpReq{}  
    err := ctx.ReadJson(req)  
    if err != nil {  
        c.BadRequestJson(&commonResponse{
            BizCode:500,
            Msg: fmt.Springf("请求出错:%v",err)
        })
        return 
    }
    resp := &commonResponse{  
        BizCode: 200, 
        Msg: "ok",
        Data:1
    }
    err = ctx.WriteJson(http.StatusOK, resp)  
    if err != nil {  
    fmt.Printf("Response err:%v", err)  
    }  
}

此时,原有的Route方法已经无法使用了,需要进行改造

改造Route方法

type Server interface{
    Route(pattern string,handlerFunc func(c *Context))
    
    Start(address string) error
}
func (s *sdkHttpServer) Route(pattern string,handlerFunc func(c *Context)){
    http.HandleFunc(pattern,func(weiter http.ResponseWriter, request *http.Request)){
        c := NewContext(writer, request)
        handlerFunc(c)
    }
}

到这里我们已经完成了以下内容

  1. 可以从http.Request中读取数据,
  2. 往http.ResponseWriter 中写入响应和数据
  3. json数据的序列化和反序列化
  4. 自定义Context上下文

下一步我们希望这个小框架能支持RESTFul Api