了解beego的开发者肯定知道,beego的路由设计来源于sinatra,原来是不支持自动路由的,每一个路由都要自己配置的,如:

type MainController struct {
beego.Controller
}
func (this *MainController) Get() {
this.Ctx.WriteString("hello world")
} func main() {
beego.Router("/", &MainController{})
beego.Run()
}

beego.Controller 提供所有的restful方法,Get,Post,Delete等方法,通过重写这些方法,已响应客户端不同的请求方式。

用过Node.js的同学,肯定觉得很熟悉,拿最常用的express框架来说,路由的定义方式也大抵是这样的,如:

app.get('/', index);

var index = function(req,res){
res.send("Hello,express");
};

有的同学肯定觉得为什么像php,java,asp这种服务器语言就不需要这样定义路由,自然会根据请求url,判断脚本path,进项执行并返回。

其实这些也是需要的,只不过这个工作交给了服务器软件来解决,如Apache,Nginx,IIS,Tomcat等。由这些软件提供Http服务,脚本程序本身更专注于服务的逻辑。

而如Node.js,Golang这种语言,是由自生提供Http服务,并监听某一端口。所以通过查看服务器响应Header可以看出,此类语言的server显示为express,beegoserver,而大部分网站返回头的server为Nginx,Apache等。当然,Golang,Node.js也可以通过反向代理功能(如Nginx),使真正与客户端打交道的变为这些反向代理软件,但注意的是,这并不代表Node.js等的Http服务和路由调度不工作了,他们依然接受来自反向代理软件的Http请求,并作出响应。

好了,扯了这么多,那0.9.0版本的beego提供的智能路由究竟是怎样呢?

先看一段,示例代码:

package main

import (
"github.com/astaxie/beego"
    "myapp/beego/controllers"
)
func main() {
beego.AutoRouter(&controllers.UserController{})
beego.AutoRouter(&controllers.PageController{})
//........
beego.Run()
}

控制器代码如下:

package controllers
import (
"github.com/astaxie/beego"
) type UserController struct {
beego.Controller
} type PageController struct {
beego.Controller 
}
func (this *UserController) Add() {
this.Ctx.WriteString("/user/add")
}
func (this *PageController) About() {
this.Ctx.WriteString("/page/about") 
}

有了这个AutoRouter,便不需要像以前那样逐一注册了,访问/user/add 调用UserController的Add方法,访问/page/about调用PageController的About方法。

这里需要稍微提醒两点:

1.控制器struct极其下func都必须以大写字母开头,因为在Golang里默认大写开头的为public,小写开头的为private,私有的内容无法被包外访问。

2.在使用了AutoRouter之后,原来的Router方法依然是有效的,可以继续使用。

好了,AutoRouter的使用就先介绍这里,0.9.0版本的beego还是更新和添加了不少功能的,在这里感谢Astaxie为golang项目所做的努力。

beego具体全面的使用,大家如果感兴趣的话,我以后可以抽个时间做个完成的介绍。

接下来我们来具体看看AutoRouter是怎么工作的,源码走起

beego.AutoRouter()
func AutoRouter(c ControllerInterface) *App {
BeeApp.AutoRouter(c)
return BeeApp
}
此处调用App的AutoRouter方法,如下:
func (app *App) AutoRouter(c ControllerInterface) *App {
app.Handlers.AddAuto(c)
return app
}
看下App的struct
type App struct {
Handlers *ControllerRegistor
}
可见app.Handlers就是ControllerRegistor,来看看ControllerRegistor的AddAuto方法
func (p *ControllerRegistor) AddAuto(c ControllerInterface) {
p.enableAuto = true
reflectVal := reflect.ValueOf(c)
rt := reflectVal.Type()
ct := reflect.Indirect(reflectVal).Type()
firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller"))
if _, ok := p.autoRouter[firstParam]; ok {
return
} else {
p.autoRouter[firstParam] = make(map[string]reflect.Type)
}
for i := ; i < rt.NumMethod(); i++ {
p.autoRouter[firstParam][rt.Method(i).Name] = ct
}
}
这个可以说就是智能路由的关键了,它充分利用率Golang的反射(reflect)机制。
看看这个方法都为ControllerRegistor做了什么呢?先来看看ControllerRegistor的struct
type ControllerRegistor struct {
routers []*controllerInfo
fixrouters []*controllerInfo
enableFilter bool
filters []http.HandlerFunc
enableAfter bool
afterFilters []http.HandlerFunc
enableUser bool
userHandlers map[string]*userHandler
enableAuto bool
autoRouter map[string]map[string]reflect.Type //key:controller key:method value:reflect.type
}
AddAuto方法首先将ControllerRegistor的enableAuto设置为true(具体作用稍后介绍)
然后对传入的控制器做反射,rt := reflectVal.Type() 获取传入控制器的Type
firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) 可以看出自动路由定义时,命名必须为XxxxController格式,否则是无法解析映射到路由上的。
if _, ok := p.autoRouter[firstParam]; ok { return } else { p.autoRouter[firstParam] = make(map[string]reflect.Type) }
这里如果autoRouter这个map里已经有firstParam这个键的时候,就不在映射,所以后定义的同名router是无法覆盖前面已经定义了的。
这里autoRouter是一个二维map,传入一个UserController通过
for i := ; i < rt.NumMethod(); i++ { p.autoRouter[firstParam][rt.Method(i).Name] = ct }
处理之后,autoRouter会新增autoRouter[“user”][“func1”],autoRouter[“user”][“func2”]….其中func1,func2即为UserController的全部方法。
通过AddAuto方法我们得到了包含所有AutoRouter的一个map,即autoRouter,那么怎么样将这个map注册到路由里呢?
继续八源码,在router.go文件中,为ControllerRegistor定义了一个ServeHttp的方法,这个方法比较长,愚安摘取相关的代码贴出来:
if p.enableAuto {
if !findrouter {
for cName, methodmap := range p.autoRouter { if strings.ToLower(requestPath) == "/"+cName {
http.Redirect(w, r, requestPath+"/", )
return
} if strings.ToLower(requestPath) == "/"+cName+"/" {
requestPath = requestPath + "index"
}
if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") {
for mName, controllerType := range methodmap {
if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) {
//execute middleware filters
if p.enableFilter {
for _, filter := range p.filters {
filter(w, r)
if w.started {
return
}
}
}
//parse params
otherurl := requestPath[len("/"+cName+"/"+strings.ToLower(mName)):]
if len(otherurl) > {
plist := strings.Split(otherurl, "/")
for k, v := range plist[:] {
params[strconv.Itoa(k)] = v
}
}
//Invoke the request handler
vc := reflect.New(controllerType) //call the controller init function
init := vc.MethodByName("Init")
in := make([]reflect.Value, )
ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody} in[] = reflect.ValueOf(ct)
in[] = reflect.ValueOf(controllerType.Name())
init.Call(in)
//call prepare function
in = make([]reflect.Value, )
method := vc.MethodByName("Prepare")
method.Call(in)
method = vc.MethodByName(mName)
method.Call(in)
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
if EnableXSRF {
method = vc.MethodByName("XsrfToken")
method.Call(in)
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
(r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) {
method = vc.MethodByName("CheckXsrfCookie")
method.Call(in)
}
}
if !w.started {
if AutoRender {
method = vc.MethodByName("Render")
method.Call(in)
}
}
method = vc.MethodByName("Finish")
method.Call(in)
//execute middleware filters
if p.enableAfter {
for _, filter := range p.afterFilters {
filter(w, r)
if w.started {
return
}
}
}
method = vc.MethodByName("Destructor")
method.Call(in)
// set find
findrouter = true
}
}
}
}
}
}

这里可以看到最先有一个判断if !findrouter 即如果没有找到路由匹配,才会进行智能路由匹配,所以Router的优先级是比AutoRouter要高的。

在这里再次用到了reflect,这里 ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody} 即获取http请求上下文,通过method.Call(in),

将http请求参数传给Controller内的相对应的方法。

不难看出,做了多步处理,有点类似PHP的钩子(hooks),依次经过控制器init方法->Prepare->XsrfToken->Render->Finish->Destructor等处理。

在最后set findrouter 为true,如果在这里仍没有匹配到router,接下来就404了

if !findrouter {
if h, ok := ErrorMaps[""]; ok {
w.status =
h(w, r)
} else {
http.NotFound(w, r)
}
}

所以beego的设计还是比较严谨且有效率的,在这里在此代表广大Golang初学者感谢谢大。

额,第一次写Golang的文章,感觉力不从心,说了一堆废话,忘园友们见谅!

beego 0.9.0 中智能路由AutoRouter的使用方法及源码解读的更多相关文章

  1. HttpServlet中service方法的源码解读

    前言     最近在看<Head First Servlet & JSP>这本书, 对servlet有了更加深入的理解.今天就来写一篇博客,谈一谈Servlet中一个重要的方法-- ...

  2. RxJava2 中多种取消订阅 dispose 的方法梳理( 源码分析 )

    Github 相关代码: Github地址 一直感觉 RxJava2 的取消订阅有点混乱, 这样也能取消, 那样也能取消, 没能系统起来的感觉就像掉进了盘丝洞, 迷乱… 下面说说这几种情况 几种取消的 ...

  3. Spark-1.6.0中的Sort Based Shuffle源码解读

    从Spark-1.2.0开始,Spark的Shuffle由Hash Based Shuffle升级成了Sort Based Shuffle.即Spark.shuffle.manager从Hash换成了 ...

  4. C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法

    C#调用接口注意要点   在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...

  5. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  6. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

  7. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  8. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  9. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

随机推荐

  1. SQL Server 2008 报表服务入门

    目录 报表服务的安装与配置 开发报表的过程 报表制作实例 一.报表服务的安装与配置 1. 报表服务的组件 2. 报表服务 2008 的安装 2.1在SQL Server 2008上安装报表服务有两种方 ...

  2. MySQL之连接数据库的两种方法

    方法一: package DB; import java.sql.Connection; import java.sql.DriverManager; public class Conn { // 定 ...

  3. C# 关于委托和事件的妙文

    C# 关于委托和事件的妙文: 通过一个例子详细介绍委托和事件的作用:Observer模式简介 转自:http://blog.csdn.net/susan19890313/article/details ...

  4. JS函数式编程【译】2.1 函数式编程语言

  5. js获取url及url参数的方法

    <script language="JavaScript" type="text/javascript"> function GetUrlParms ...

  6. Install GDAL in OpenSUSE 12.3 Linux

    Runtime Enviroment:Open SUSE Linux *i385 Notice:if any command disavliable ,you can copy the paramet ...

  7. Codevs 1092 不高兴的津津

    时间限制: 1 s   空间限制: 128000 KB   题目等级 : 白银 Silver 题目描述 Description 津津上初中了.妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参 ...

  8. 修改 timezone

    1.通过命令修改 1.Set Time, Date Timezone in Linux from Command Line or Gnome | Use ntp 2.Use TZ database 3 ...

  9. 《linux源代码包的编译安装》RHEL6

    linux下源代码包的编译安装其实没那么复杂. 我是win7系统装的虚拟机,就简单说下: 举个简单的例子: http://www.openssl.org/ 这是openssl的官网,下载openssl ...

  10. 一款jQuery打造的滚动条在底部滑出信息提示层

    一款jQuery打造的滚动条在底部滑出信息提示层, 当滚动鼠标滚轮,或者滚动条往下拉的时候,在右下角,弹出一个信息提示框. 有一点仿的是一个插件工具,就是网页中大家都长用到的友荐. 这款特效算一款简单 ...