博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
gweb总结之router
阅读量:6988 次
发布时间:2019-06-27

本文共 8572 字,大约阅读时间需要 28 分钟。

由此去

代码结构

.- router包├── middleware│   ├── param.go     // 参数解析支持│   ├── readme.md    // 文档│   ├── reqlog.go    // 记录请求日志│   ├── response.go  // 响应的相关函数│   └── safe.go      // safe recover功能└── router.go        // 入口和request处理逻辑
整个router与gweb其他模块并不耦合,只会依赖于logger。其中
router.go是整个路由的入口的,而middleware提供一些工具函数和简单的封装。

router处理逻辑

router.go 主要做了以下工作:

  • 定义路由,及Controller注册
  • 自定义http.Handler, 也就是ApiHandler,实现ServeHTTP方法。

自定义路由Route

type Route struct {    Path    string         // req URI    Method  string         // GET,POST...    Fn      interface{}    // URI_METHOD hanlde Func    ReqPool *sync.Pool     // req form pool    ResPool *sync.Pool     // response pool}

在使用的时候使用一个map[string][]*Route结构来存储URI和Method对应的路由处理函数。脑补一下,实际的存储是这样的:

{    "/hello": [        &Route{            Path: "/hello",            Method: "GET",            Fn: someGetFunc,            ReqPool: someGetReqPool,            ResPool: someGetRespPool        },        &Route{            Path: "/hello",            Method: "POST",            Fn: somePostFunc,            ReqPool: somePostReqPool,            ResPool: somePostRespPool        },        // ... more    ],    // ... more}
用这样的结构主要是为了支持Restful API,其他的暂时没有考虑

ApiHanlder

router.go 定义了一个ApiHandler如下:

type ApiHandler struct {    NotFound         http.Handler    MethodNotAllowed http.Handler}

只是简单的包含了两个hander,用于支持404405请求。

!!!! 重点来了,我们为什么要定一个那样的路由?又怎么具体的解析参数,响应,处理请求呢?Talk is Cheap, show me the Code

func (a *ApiHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {    defer middleware.SafeHandler(w, req)    path := req.URL.Path    route, ok := foundRoute(path, req.Method)     handle 404    if !ok {        if a.NotFound != nil {            a.NotFound.ServeHTTP(w, req)        } else {            http.NotFound(w, req)        }        return    }    // not nil and to, ref to foundRoute    if route != nil {        goto Found    }     handle 405    if !allowed(path, req.Method) {        if a.MethodNotAllowed != nil {            a.MethodNotAllowed.ServeHTTP(w, req)        } else {            http.Error(w,                http.StatusText(http.StatusMethodNotAllowed),                http.StatusMethodNotAllowed,            )        }        return    }Found:     normal handle    reqRes := route.ReqPool.Get()    defer route.ReqPool.Put(reqRes)    // parse params    if errs := middleware.ParseParams(w, req, reqRes); len(errs) != 0 {        je := new(middleware.JsonErr)        Response(je, NewCodeInfo(CodeParamInvalid, ""))        je.Errs = errs        middleware.ResponseErrorJson(w, je)        return    }    in := make([]reflect.Value, 1)    in[0] = reflect.ValueOf(reqRes)    Fn := reflect.ValueOf(route.Fn)     Call web server handle function    out := Fn.Call(in)     response to client    resp := out[0].Interface()    defer route.ResPool.Put(resp)    middleware.ResponseJson(w, resp)    return}

流程正如你所想的那样。处理405,405等,然后使用路由Route,进行参数解析,校验,调用,返回响应等操作。设计参照了httprouter。关于参数解析和响应,马上就到。

参数解析和校验(param.go)

参数的解析,一开始考虑的只有GET,POST,PUT,DELETE 没有考虑JSON和文件的解析。因为一开始忙于搭框架是一方面,其次因为我用的 不支持(我也没仔细看,自己实现起来也很简单)。

这里就推荐两个我常用的golang第三方库,这也是我用于参数解析和校验的工具:

  1. , converts structs to and from form values.
  2. ,valid the struct
// ParseParams, parse params into reqRes from req.Form, and support// form-data, json-body// TODO: support parse filefunc ParseParams(w http.ResponseWriter, req *http.Request, reqRes interface{}) (errs ParamErrors) {    switch req.Method {    case http.MethodGet:        req.ParseForm()    case http.MethodPost, http.MethodPut:        req.ParseMultipartForm(20 << 32)    default:        req.ParseForm()    }    // log request    logReq(req)    // if should parse Json body    // parse json into reqRes    if shouldParseJson(reqRes) {        data, err := getJsonData(req)        if err != nil {            errs = append(errs, NewParamError("parse.json", err.Error(), ""))            return        }        if err = json.Unmarshal(data, reqRes); err != nil {            errs = append(errs, NewParamError("json.unmarshal", err.Error(), ""))            return        }        bs, _ := json.Marshal(reqRes)        ReqL.Info("pasing json body: " + string(bs))        goto Valid    }    // if has FILES field,    // so parese req to get attachment files    if shouldParseFile(reqRes) {        AppL.Info("should parse files")        if req.MultipartForm == nil || req.MultipartForm.File == nil {            errs = append(errs, NewParamError("FILES", "empty file param", ""))            return        }        rv := reflect.ValueOf(reqRes).Elem().FieldByName("FILES")        // typ := reflect.ValueOf(reqRes).Elem().FieldByName("FILES").Type()        filesMap := reflect.MakeMap(rv.Type())        // parse file loop        for key, _ := range req.MultipartForm.File {            file, file_header, err := req.FormFile(key)            if err != nil {                errs = append(errs, NewParamError(Fstring("parse request.FormFile: %s", key),                    err.Error(), ""))            }            defer file.Close()            filesMap.SetMapIndex(                reflect.ValueOf(key),                reflect.ValueOf(ParamFile{                    File:       file,                    FileHeader: *file_header,                }),            )        } // loop end        // set value to reqRes.Field `FILES`        rv.Set(filesMap)        if len(errs) != 0 {            return        }    }    // decode    if err := decoder.Decode(reqRes, req.Form); err != nil {        errs = append(errs, NewParamError("decoder", err.Error(), ""))        return    }Valid:    // valid    v := poolValid.Get().(*valid.Validation)    if ok, err := v.Valid(reqRes); err != nil {        errs = append(errs, NewParamError("validation", err.Error(), ""))    } else if !ok {        for _, err := range v.Errors {            errs = append(errs, NewParamErrorFromValidError(err))        }    }    return}

或许有人会关心shouldParseJson是怎么弄的?如下:

// shouldParseJson check `i` has field `JSON`func shouldParseJson(i interface{}) bool {    v := reflect.ValueOf(i).Elem()    if _, ok := v.Type().FieldByName("JSON"); !ok {        return false    }    return true}

这里强制设定了reqRes必须含有JSON字段,才会解析jsonbody;必须含有FILES才会解析请求中的文件。因此在写业务逻辑的时候,要写成这个样子了,这些示例都在:

/* * JSON-Body Demo */type HelloJsonBodyForm struct {    JSON bool   `schema:"-" json:"-"` // 注意schema标签要设置“-”    Name string `schema:"name" valid:"Required" json:"name"`    Age  int    `schema:"age" valid:"Required;Min(0)" json:"age"`}var PoolHelloJsonBodyForm = &sync.Pool{New: func() interface{} { return &HelloJsonBodyForm{} }}type HelloJsonBodyResp struct {    CodeInfo    Tip string `json:"tip"`}var PoolHelloJsonBodyResp = &sync.Pool{New: func() interface{} { return &HelloJsonBodyResp{} }}func HelloJsonBody(req *HelloJsonBodyForm) *HelloJsonBodyResp {    resp := PoolHelloJsonBodyResp.Get().(*HelloJsonBodyResp)    defer PoolHelloJsonBodyResp.Put(resp)    resp.Tip = fmt.Sprintf("JSON-Body Hello, %s! your age[%d] is valid to access", req.Name, req.Age)    Response(resp, NewCodeInfo(CodeOk, ""))    return resp}/* * File Hanlder demo */type HelloFileForm struct {    FILES map[string]mw.ParamFile `schema:"-" json:"-"` // 注意schema标签设置“-”和FILES的type保持一直    Name  string                  `schema:"name" valid:"Required"`    Age   int                     `schema:"age" valid:"Required"`}var PoolHelloFileForm = &sync.Pool{New: func() interface{} { return &HelloFileForm{} }}type HelloFileResp struct {    CodeInfo    Data struct {        Tip  string `json:"tip"`        Name string `json:"name"`        Age  int    `json:"age"`    } `json:"data"`}var PoolHelloFileResp = &sync.Pool{New: func() interface{} { return &HelloFileResp{} }}func HelloFile(req *HelloFileForm) *HelloFileResp {    resp := PoolHelloFileResp.Get().(*HelloFileResp)    defer PoolHelloFileResp.Put(resp)    resp.Data.Tip = "foo"    for key, paramFile := range req.FILES {        AppL.Infof("%s:%s\n", key, paramFile.FileHeader.Filename)        s, _ := bufio.NewReader(paramFile.File).ReadString(0)        resp.Data.Tip += s    }    resp.Data.Name = req.Name    resp.Data.Age = req.Age    Response(resp, NewCodeInfo(CodeOk, ""))    return resp}

响应(response.go)

gweb目的在于总结一个使用Json数据格式来进行交互的web服务结构。响应体设计如下:

{    "code": 0,     // 错误码,或许应该使用“error_code”, 不过不影响    "message": ""  // 错误消息    "user": {        "name": "yep",        // ... other    }}

结合上面的Demo,大概看出来了,响应并没什么花里胡哨的功能。只是需要将*resp使用json.Marshal转为字符串,并发送给客户端就了事。

// ...     Call web server handle function    out := Fn.Call(in)     response to client    resp := out[0].Interface()    defer route.ResPool.Put(resp)    middleware.ResponseJson(w, resp)

路由到这里也就结束了,虽然最重要,但依然比较简单。

最后可能需要一个图来说明?

图片描述

转载地址:http://vdzvl.baihongyu.com/

你可能感兴趣的文章
2.zephir基础语法
查看>>
新版Spring Jar包官方下载地址
查看>>
JDBC操作MySQL(crud)
查看>>
linux命令:iptables、modprobe装载模块、网络防火墙服务
查看>>
Linux命令:文本处理工具awk详解
查看>>
用户相关知识 Linux详解
查看>>
C++中COM调用方法
查看>>
用异步任务封装的网络请求工具类AssistantTask
查看>>
使用 LVM 技术提升 Xen 虚拟机性能的实现
查看>>
有奖话题竞拍丨新手如何学习和研究SDN?
查看>>
C++程序内存泄露检测工具
查看>>
Rails缓存
查看>>
为什么你那么努力,还是不能升职加薪?
查看>>
苹果系统从零开始--MAC OS X 教程4-Expose,Space
查看>>
2010款MacBook Pro SSD、内存升级
查看>>
解密穿戴式设备中的计步算法
查看>>
QT4实现语法高亮发现其正则表达式不够完整
查看>>
体验JBOOT(四)-- jboot-admin 篇
查看>>
Spring 下基于自定义注解拦截方法调用
查看>>
Eclipse常用快捷键
查看>>