热门标签 | 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;

图片描述



推荐阅读
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • Cookie学习小结
    Cookie学习小结 ... [详细]
  • 【线段树】  本质是二叉树,每个节点表示一个区间[L,R],设m(R-L+1)2(该处结果向下取整)左孩子区间为[L,m],右孩子区间为[m ... [详细]
  • 本文介绍了Go语言中正则表达式的基本使用方法,并提供了一些实用的示例代码。 ... [详细]
  • 本文介绍了如何使用Python爬取妙笔阁小说网仙侠系列中所有小说的信息,并将其保存为TXT和CSV格式。主要内容包括如何构造请求头以避免被网站封禁,以及如何利用XPath解析HTML并提取所需信息。 ... [详细]
  • 本文介绍了如何在 Spring Boot 项目中使用 spring-boot-starter-quartz 组件实现定时任务,并将 cron 表达式存储在数据库中,以便动态调整任务执行频率。 ... [详细]
  • python模块之正则
    re模块可以读懂你写的正则表达式根据你写的表达式去执行任务用re去操作正则正则表达式使用一些规则来检测一些字符串是否符合个人要求,从一段字符串中找到符合要求的内容。在 ... [详细]
  • 本文详细介绍了 Spark 中的弹性分布式数据集(RDD)及其常见的操作方法,包括 union、intersection、cartesian、subtract、join、cogroup 等转换操作,以及 count、collect、reduce、take、foreach、first、saveAsTextFile 等行动操作。 ... [详细]
  • 本文将介绍如何在混合开发(Hybrid)应用中实现Native与HTML5的交互,包括基本概念、学习目标以及具体的实现步骤。 ... [详细]
  • 一个建表一个执行crud操作建表代码importandroid.content.Context;importandroid.database.sqlite.SQLiteDat ... [详细]
  • C#实现文件的压缩与解压
    2019独角兽企业重金招聘Python工程师标准一、准备工作1、下载ICSharpCode.SharpZipLib.dll文件2、项目中引用这个dll二、文件压缩与解压共用类 ... [详细]
  • Spring Data JdbcTemplate 入门指南
    本文将介绍如何使用 Spring JdbcTemplate 进行数据库操作,包括查询和插入数据。我们将通过一个学生表的示例来演示具体步骤。 ... [详细]
  • 本文详细解析了ASP.NET 2.0中的Callback机制,不仅介绍了基本的使用方法,还深入探讨了其背后的实现原理。通过对比Atlas框架,帮助读者更好地理解和应用这一机制。 ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • T15483.【清华集训2017模拟11.26】简单路径T25484.【清华集训2017模拟11.26】快乐树T35485.【清华集训2017模拟11.26】字符串T1结论题,结论很 ... [详细]
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社区 版权所有