HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个struct的方法),如前面小节所描述的结构图,路由在框架中相当于一个事件处理器,而这个事件包括:

  • 用户请求的路径(path)(例如:/user/123,/article/123),当然还有查询串信息(例如?id=11)
  • HTTP的请求方法(method)(GET、POST、PUT、DELETE、PATCH等)

路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。

默认的路由实现

func fooHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}http.HandleFunc("/foo", fooHandler)http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})log.Fatal(http.ListenAndServe(":8080", nil))

上面的例子调用了http默认的DefaultServeMux来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点:

  • 添加路由信息
  • 根据用户请求转发到要执行的函数

Go默认的路由添加是通过函数http.Handlehttp.HandleFunc等来添加,底层都是调用了DefaultServeMux.Handle(pattern string, handler Handler),这个函数会把路由信息存储在一个map信息中map[string]muxEntry,这就解决了上面说的第一点。

Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为http.DefaultServeMux,通过DefaultServeMux.ServeHTTP函数来进行调度,遍历之前存储的map路由信息,和用户访问的URL进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。

for k, v := range mux.m {if !pathMatch(k, path) {continue}if h == nil || len(k) > n {n = len(k)h = v.h}
}

beego框架路由实现

目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制:

  • 不支持参数设定,例如/user/:uid 这种泛类型匹配
  • 无法很好的支持REST模式,无法限制访问的方法,例如上面的例子中,用户访问/foo,可以用GET、POST、DELETE、HEAD等方式访问
  • 一般网站的路由规则太多了,编写繁琐。我前面自己开发了一个API应用,路由规则有三十几条,这种路由多了之后其实可以进一步简化,通过struct的方法进行一种简化

beego框架的路由器基于上面的几点限制考虑设计了一种REST方式的路由实现,路由设计也是基于上面Go默认设计的两点来考虑:存储路由和转发路由

存储路由

针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST的方法对应到struct的方法中去,然后路由到struct而不是函数,这样在转发路由的时候就可以根据method来执行不同的方法。

根据上面的思路,我们设计了两个数据类型controllerInfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息)

type controllerInfo struct {regex          *regexp.Regexpparams         map[int]stringcontrollerType reflect.Type
}type ControllerRegistor struct {routers     []*controllerInfoApplication *App
}

ControllerRegistor对外的接口函数有

func (p *ControllerRegistor) Add(pattern string, c ControllerInterface)

详细的实现如下所示:

func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {parts := strings.Split(pattern, "/")j := 0params := make(map[int]string)for i, part := range parts {if strings.HasPrefix(part, ":") {expr := "([^/]+)"//a user may choose to override the defult expression// similar to expressjs: ‘/user/:id([0-9]+)’if index := strings.Index(part, "("); index != -1 {expr = part[index:]part = part[:index]}params[j] = partparts[i] = exprj++}}//recreate the url pattern, with parameters replaced//by regular expressions. then compile the regexpattern = strings.Join(parts, "/")regex, regexErr := regexp.Compile(pattern)if regexErr != nil {//TODO add error handling here to avoid panicpanic(regexErr)return}//now create the Routet := reflect.Indirect(reflect.ValueOf(c)).Type()route := &controllerInfo{}route.regex = regexroute.params = paramsroute.controllerType = tp.routers = append(p.routers, route)}

静态路由实现

上面我们实现的动态路由的实现,Go的http包默认支持静态文件处理FileServer,由于我们实现了自定义的路由器,那么静态文件也需要自己设定,beego的静态文件夹路径保存在全局变量StaticDir中,StaticDir是一个map类型,实现如下:

func (app *App) SetStaticPath(url string, path string) *App {StaticDir[url] = pathreturn app
}

应用中设置静态路径可以使用如下方式实现:

beego.SetStaticPath("/img","/static/img")

转发路由

转发路由是基于ControllerRegistor里的路由信息来进行转发的,详细的实现如下代码所示:

// AutoRoute
func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) {defer func() {if err := recover(); err != nil {if !RecoverPanic {// go back to panicpanic(err)} else {Critical("Handler crashed with error", err)for i := 1; ; i += 1 {_, file, line, ok := runtime.Caller(i)if !ok {break}Critical(file, line)}}}}()var started boolfor prefix, staticDir := range StaticDir {if strings.HasPrefix(r.URL.Path, prefix) {file := staticDir + r.URL.Path[len(prefix):]http.ServeFile(w, r, file)started = truereturn}}requestPath := r.URL.Path//find a matching Routefor _, route := range p.routers {//check if Route pattern matches urlif !route.regex.MatchString(requestPath) {continue}//get submatches (params)matches := route.regex.FindStringSubmatch(requestPath)//double check that the Route matches the URL pattern.if len(matches[0]) != len(requestPath) {continue}params := make(map[string]string)if len(route.params) > 0 {//add url parameters to the query param mapvalues := r.URL.Query()for i, match := range matches[1:] {values.Add(route.params[i], match)params[route.params[i]] = match}//reassemble query params and add to RawQueryr.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery//r.URL.RawQuery = url.Values(values).Encode()}//Invoke the request handlervc := reflect.New(route.controllerType)init := vc.MethodByName("Init")in := make([]reflect.Value, 2)ct := &Context{ResponseWriter: w, Request: r, Params: params}in[0] = reflect.ValueOf(ct)in[1] = reflect.ValueOf(route.controllerType.Name())init.Call(in)in = make([]reflect.Value, 0)method := vc.MethodByName("Prepare")method.Call(in)if r.Method == "GET" {method = vc.MethodByName("Get")method.Call(in)} else if r.Method == "POST" {method = vc.MethodByName("Post")method.Call(in)} else if r.Method == "HEAD" {method = vc.MethodByName("Head")method.Call(in)} else if r.Method == "DELETE" {method = vc.MethodByName("Delete")method.Call(in)} else if r.Method == "PUT" {method = vc.MethodByName("Put")method.Call(in)} else if r.Method == "PATCH" {method = vc.MethodByName("Patch")method.Call(in)} else if r.Method == "OPTIONS" {method = vc.MethodByName("Options")method.Call(in)}if AutoRender {method = vc.MethodByName("Render")method.Call(in)}method = vc.MethodByName("Finish")method.Call(in)started = truebreak}//if no matches to url, throw a not found exceptionif started == false {http.NotFound(w, r)}
}

使用入门

基于这样的路由设计之后就可以解决前面所说的三个限制点,使用的方式如下所示:

基本的使用注册路由:

beego.BeeApp.RegisterController("/", &controllers.MainController{})

参数注册:

beego.BeeApp.RegisterController("/:param", &controllers.UserController{})

正则匹配:

beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{})

golang自定义路由器设计相关推荐

  1. c语言自定义函数程序设计,ch3自定义函数设计 C语言 《解析C程序设计》.ppt

    ch3自定义函数设计 C语言 <解析C程序设计> 全局变量--外部变量 在函数外定义的变量 有效范围:从定义变量的位置开始到本源文件结束,及有extern声明的其它源文件 存储类型:缺省e ...

  2. 项目开发中自定义字段设计原则

    在开发系统过程中,做到自定义字段策略设置,目前这种功能是很多系统的标准配置,这样子可以简化后续增加字段的难度,并对自定义字段做管理. 自定义字段功能要注意到以下几点: 1.批量规划好要自定义字段的数据 ...

  3. 《M8围棋谱》自定义皮肤设计指南

    <M8围棋谱>自定义皮肤设计指南 版本:1.0 地址:http://docs.google.com/View?id=dhmvxcsd_3dxpmm6nz 作者:liigo,2009年10月 ...

  4. 【转载】看懂通信协议:自定义通信协议设计之TLV编码应用

    0. TLV 相关资料 最近研究了TLV的相关知识点,收集部分资料如下所示: 学习TLV数据结构 通信协议之序列化 看懂通信协议:自定义通信协议设计之TLV编码应用 TLV编解码Java实现 我的开源 ...

  5. LaTeX自定义封面设计

    这篇文章主要以自己的两本书籍封面设计为主,进行重点讲解,分别自定义使用 titlepage 环境和\maketitle输出漂亮的封面,并在后面继续写上三个案例,均附上完整源码,具体内容如下,文章首发微 ...

  6. 基于嵌入式Linux的SOHO路由器设计

    http://tech.fuwuqi.com.cn/networkci/routswitch/2009-08-18/5057244114034.shtml 来源:维库 作者:佚名 发布时间:2009- ...

  7. 基于嵌入式Linux系统的3G/4G路由器设计——iptables nat 模式

    1. 3G/4G路由器设计方案 本路由器的设计是基于三个模块来实现的,分别为3G模块.WiFi模块和Linux硬件平台,如图1所示.3G模块的功能是利用运营商的无线数据卡进行PPP拨号,使得路由器能通 ...

  8. Golang Failpoint 的设计与实现

    作者:龙恒 对于一个大型复杂的系统来说,通常包含多个模块或多个组件构成,模拟各个子系统的故障是测试中必不可少的环节,并且这些故障模拟必须做到无侵入地集成到自动化测试系统中,通过在自动化测试中自动激活这 ...

  9. golang自定义路由控制实现(一)

        由于本人之前一直是Java Coder,在Java web开发中其实大家都很依赖框架,所以当在学习Golang的时候,自己便想着在Go开发中脱离框架,自己动手造框架来练习.通过学习借鉴Java ...

最新文章

  1. Spring Boot 集成 JUnit5,更优雅单元测试!
  2. 文艺青年的两门必修课——绘画与音乐
  3. 文件服务器和客户模式有什么区别,客户端和服务器端编程有什么区别?
  4. CodeForces - 1066C Books Queries(思维)
  5. 后缀数组--(最长公共前缀)
  6. 如何使用 ABAP 报表将 ABAP 服务器上的 SAP UI5 应用下载到本地
  7. 基于springboot2.5.5自建启动器starter制品库
  8. 使用React和Tailwind CSS搭建项目模板
  9. 前端学习(914):offerset和style区别
  10. exec与xargs区别
  11. 精读《手写 SQL 编译器 - 性能优化之缓存》
  12. scala解析xml_Scala XML处理–文字,序列化,解析,保存和加载示例
  13. hadoop生态--Hive(4)--Hive分区中的动态分区、静态分区
  14. 算法导论第三版习题及答案
  15. 下载Linux系统中文件到本地电脑
  16. linux aria2 离线,使用aria2实现离线下载
  17. 【推荐】精选行政文书模板大全(调查报告+会议纪要+通知+通告+总结+规定等模板,共177份)
  18. DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL过程
  19. 基于cesium和mars3d海洋三维管线信息系统开发完工总结
  20. aid learning安装应用_极致安卓—Termux/Aid Learning安装宇宙最强VS Code

热门文章

  1. 【Android 组件化】路由组件 ( 注解处理器调试 )
  2. 【Flutter】Icons 组件 ( FlutterIcon 下载图标 | 自定义 svg 图标生成 ttf 字体文件 | 使用下载的 ttf 图标文件 )
  3. Linux下安装jdk(xxx.rpm,非xxx.tar.gz,请注意!)过程
  4. ASP.NET 系统对象 Request(一)
  5. javascript深入理解js闭包[转]
  6. java arraylist的问题
  7. python 用twisted 问题 zope.interface
  8. WebApplication和WebSite有什么区别?我该选择哪个?
  9. debian linux
  10. const(常量)和#define(宏定义)区别