热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

gweb总结之router

代码由此去代码结构.-router包├──middleware│├──param.go参数解析支持│├──readme.md文档│├──reqlog.go记录请求日志│├

代码由此去

代码结构

.- 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 URIMethod string // GET,POST...Fn interface{} // URI_METHOD hanlde FuncReqPool *sync.Pool // req form poolResPool *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.HandlerMethodNotAllowed 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.Pathroute, ok := foundRoute(path, req.Method)handle 404if !ok {if a.NotFound != nil {a.NotFound.ServeHTTP(w, req)} else {http.NotFound(w, req)}return}// not nil and to, ref to foundRouteif route != nil {goto Found}handle 405if !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 handlereqRes := route.ReqPool.Get()defer route.ReqPool.Put(reqRes)// parse paramsif errs := middleware.ParseParams(w, req, reqRes); len(errs) != 0 {je := new(middleware.JsonErr)Response(je, NewCodeInfo(CodeParamInvalid, ""))je.Errs = errsmiddleware.ResponseErrorJson(w, je)return}in := make([]reflect.Value, 1)in[0] = reflect.ValueOf(reqRes)Fn := reflect.ValueOf(route.Fn)Call web server handle functionout := Fn.Call(in)response to clientresp := out[0].Interface()defer route.ResPool.Put(resp)middleware.ResponseJson(w, resp)return
}

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

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

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

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

  1. schema, converts structs to and from form values.
  2. beego/validation,valid the struct

// ParseParams, parse params into reqRes from req.Form, and support
// form-data, json-body
// TODO: support parse file
func 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 requestlogReq(req)// if should parse Json body// parse json into reqResif shouldParseJson(reqRes) {data, err :&#61; getJsonData(req)if err !&#61; nil {errs &#61; append(errs, NewParamError("parse.json", err.Error(), ""))return}if err &#61; json.Unmarshal(data, reqRes); err !&#61; nil {errs &#61; append(errs, NewParamError("json.unmarshal", err.Error(), ""))return}bs, _ :&#61; json.Marshal(reqRes)ReqL.Info("pasing json body: " &#43; string(bs))goto Valid}// if has FILES field,// so parese req to get attachment filesif shouldParseFile(reqRes) {AppL.Info("should parse files")if req.MultipartForm &#61;&#61; nil || req.MultipartForm.File &#61;&#61; nil {errs &#61; append(errs, NewParamError("FILES", "empty file param", ""))return}rv :&#61; reflect.ValueOf(reqRes).Elem().FieldByName("FILES")// typ :&#61; reflect.ValueOf(reqRes).Elem().FieldByName("FILES").Type()filesMap :&#61; reflect.MakeMap(rv.Type())// parse file loopfor key, _ :&#61; range req.MultipartForm.File {file, file_header, err :&#61; req.FormFile(key)if err !&#61; nil {errs &#61; 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 &#96;FILES&#96;rv.Set(filesMap)if len(errs) !&#61; 0 {return}}// decodeif err :&#61; decoder.Decode(reqRes, req.Form); err !&#61; nil {errs &#61; append(errs, NewParamError("decoder", err.Error(), ""))return}Valid:// validv :&#61; poolValid.Get().(*valid.Validation)if ok, err :&#61; v.Valid(reqRes); err !&#61; nil {errs &#61; append(errs, NewParamError("validation", err.Error(), ""))} else if !ok {for _, err :&#61; range v.Errors {errs &#61; append(errs, NewParamErrorFromValidError(err))}}return
}

或许有人会关心shouldParseJson是怎么弄的&#xff1f;如下:

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

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

/** JSON-Body Demo*/
type HelloJsonBodyForm struct {JSON bool &#96;schema:"-" json:"-"&#96; // 注意schema标签要设置“-”Name string &#96;schema:"name" valid:"Required" json:"name"&#96;Age int &#96;schema:"age" valid:"Required;Min(0)" json:"age"&#96;
}var PoolHelloJsonBodyForm &#61; &sync.Pool{New: func() interface{} { return &HelloJsonBodyForm{} }}type HelloJsonBodyResp struct {CodeInfoTip string &#96;json:"tip"&#96;
}var PoolHelloJsonBodyResp &#61; &sync.Pool{New: func() interface{} { return &HelloJsonBodyResp{} }}func HelloJsonBody(req *HelloJsonBodyForm) *HelloJsonBodyResp {resp :&#61; PoolHelloJsonBodyResp.Get().(*HelloJsonBodyResp)defer PoolHelloJsonBodyResp.Put(resp)resp.Tip &#61; 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 &#96;schema:"-" json:"-"&#96; // 注意schema标签设置“-”和FILES的type保持一直Name string &#96;schema:"name" valid:"Required"&#96;Age int &#96;schema:"age" valid:"Required"&#96;
}var PoolHelloFileForm &#61; &sync.Pool{New: func() interface{} { return &HelloFileForm{} }}type HelloFileResp struct {CodeInfoData struct {Tip string &#96;json:"tip"&#96;Name string &#96;json:"name"&#96;Age int &#96;json:"age"&#96;} &#96;json:"data"&#96;
}var PoolHelloFileResp &#61; &sync.Pool{New: func() interface{} { return &HelloFileResp{} }}func HelloFile(req *HelloFileForm) *HelloFileResp {resp :&#61; PoolHelloFileResp.Get().(*HelloFileResp)defer PoolHelloFileResp.Put(resp)resp.Data.Tip &#61; "foo"for key, paramFile :&#61; range req.FILES {AppL.Infof("%s:%s\n", key, paramFile.FileHeader.Filename)s, _ :&#61; bufio.NewReader(paramFile.File).ReadString(0)resp.Data.Tip &#43;&#61; s}resp.Data.Name &#61; req.Nameresp.Data.Age &#61; req.AgeResponse(resp, NewCodeInfo(CodeOk, ""))return resp
}

响应(response.go)

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

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

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

// ...Call web server handle functionout :&#61; Fn.Call(in)response to clientresp :&#61; out[0].Interface()defer route.ResPool.Put(resp)middleware.ResponseJson(w, resp)

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

最后可能需要一个图来说明&#xff1f;

图片描述



推荐阅读
  • eecg的代码生成器很不错,但是可能有的时候不是那么符合我们实际项目的功能需求,这里会首先介绍jeecg原生生成的样子,以及根据需求进行的改造。Jeecg中的 ... [详细]
  • springboot 事务 抛出异常_Spring Boot(四) 异常处理
    一、参数校验错误1.注解校验注解校验的常见形式是,在JavaBean类中添加javax.validation校验注解,在控制器方法参数前添加Valida ... [详细]
  • LwIP系列内存管理(堆内存)详解
    一、目的小型嵌入式系统中的内存资源(SRAM)一般都比较有限,LwIP的运行平台一般都是资源受限的MCU。为了能够更加高效的运行ÿ ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 最近自己做一个工具最后涉及到一个存储成bmp位图的形式,由于这部分并不是整个project的重点我就从网上找了例子改了改,但是目前的问题是有很多时候都是存储的bmp全黑,我也并不知道是怎么回事。 ... [详细]
  • 使用RSACryptoServiceProvider进行公钥加密我已经在CodeProject上发表了一篇文章,解释了如何使用RSA提供程序进行加密和解密:RSA私钥加密虽然200 ... [详细]
  • 简单动态字符串redis里面很多地方都用到了字符串,我们知道redis是一个键值对存储的非关系型数据库,那么所有的key都是用字符串存储的,还有字符串类型,这些都是用字符串存储的 ... [详细]
  • 下面是一个用openssl实现获取https网页内容的demo,整个流程比较简单,主要封装的API如下staticinthttps_init(http ... [详细]
  • Ithinkthishasbeenupbefore,butcouldntfindanyanswertoit.Ifitsalreadyansweredplease ... [详细]
  • 遇到的问题golang对于基本类型初始化的处理,是自动给基本类型赋值为默认值。比如:variint在这里如果不对i做任何赋值,那么i的值为零这个特性在很多地方能够避免访问到未初始化 ... [详细]
  • [二分图]JZOJ 4612 游戏
    DescriptionInputOutputSampleInput44#****#****#*xxx#SampleOutput5DataConstraint分析非常眼熟࿰ ... [详细]
  • IPVlan 详解
    文章目录简介Ipvlan2同节点Ns互通Ns内与宿主机通信第三种方法Ns到节点外部结论Ipvlan31.同节点Ns互通Ns内与宿主机通信Ns内到外部网络总结源码分析ipvlan收包 ... [详细]
  • Proof (of knowledge) of exponentiation
    1.ProofofexponentiationProofofexponentiation是基于adaptiverootassumption(充分必要条件࿰ ... [详细]
  • 1、概念共享内存:共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同malloc()函数向不同进程返回了指向同一个 ... [详细]
  • Logistic回归主要针对输入的数据是多个,输出则是有限的数值型,多为2个分类。涉及到以下方面:1.输出yw0+w1*x1+w2*x2+..(x1,x2,是样本的 ... [详细]
author-avatar
小森林
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有