在 Go Revel - server.go 源码分析 http://www.cnblogs.com/hangxin1940/p/3265538.html

说到revel框架很多重要的东西都Filters过滤器链中处理。

Ftilers链是整个revel框架的处理核心。

##Filters(过滤器链)

![golang_filters](http://images.cnblogs.com/cnblogs_com/hangxin1940/508415/o_golang-revel-filters.png "golang_filters")

filter.go

package revel

type Filter func(c *Controller, filterChain []Filter)

// Filters 为全局的过滤器链
// 项目初始化时Filters就会被设置
var Filters = []Filter{
PanicFilter, // 运行时异常过滤器 恢复一个 panics 运行时异常并且显示异常信息页面
RouterFilter, // 路由过滤器 根据路由器选择正确的Action
FilterConfiguringFilter, // 自定义过滤器配置器 为每一个Action增加或删除自定义过滤器
ParamsFilter, // 参数转换过滤器 将请求的参数转换为 Controller.Params
SessionFilter, // 会话过滤器 恢复和写入会话cookie
FlashFilter, // Flash过滤器 恢复和写入Flash信息cookie
ValidationFilter, // 验证过滤器 恢复保存验证错误并且从cookie中新建一个
I18nFilter, // i18n过滤器 解析请求的语言
InterceptorFilter, // 拦截器过滤器 在Action前后运行拦截器
ActionInvoker, // Action过滤器 调用Action方法
}

// NilFilter and NilChain are helpful in writing filter tests.
var (
NilFilter = func(_ *Controller, _ []Filter) {}
NilChain = []Filter{NilFilter}
)

符合`func(c *Controller, filterChain []Filter)`这两种参数形式的函数,均可以当作过滤器。

Filter是一个固定参数的方法,并且内部方法实现为级联递归调用。每次掉用,会传入controller以及当前Filters长度-1的一个切片,在方法最后会递归调用下去,直到传入的Filters切片没有元素。

通过filterconfig可以很方便的添加自定义过滤器,如源码中注释的示例:

// FilterConfigurator allows the developer configure the filter chain on a
// per-controller or per-action basis. The filter configuration is applied by
// the FilterConfiguringFilter, which is itself a filter stage. For example,
//
// Assuming:
// Filters = []Filter{
// RouterFilter,
// FilterConfiguringFilter,
// SessionFilter,
// ActionInvoker,
// }
//
// Add:
// FilterAction(App.Action).
// Add(OtherFilter)
//
// => RouterFilter, FilterConfiguringFilter, SessionFilter, OtherFilter, ActionInvoker
//
// Remove:
// FilterAction(App.Action).
// Remove(SessionFilter)
//
// => RouterFilter, FilterConfiguringFilter, OtherFilter, ActionInvoker
//
// Insert:
// FilterAction(App.Action).
// Insert(OtherFilter, revel.BEFORE, SessionFilter)
//
// => RouterFilter, FilterConfiguringFilter, OtherFilter, SessionFilter, ActionInvoker
//
// Filter modifications may be combined between Controller and Action. For example:
// FilterController(App{}).
// Add(Filter1)
// FilterAction(App.Action).
// Add(Filter2)
//
// .. would result in App.Action being filtered by both Filter1 and Filter2.
//
// Note: the last filter stage is not subject to the configurator. In
// particular, Add() adds a filter to the second-to-last place.

只需要对要过滤的`Controller`或`Action`应用到`FilterController`或`FilterAction`函数,并`Add`添加自定义过滤器就行了。

Filters的调用方式决定了过滤器链的调用是有序的,在默认的`Filters`切片中,调用的顺序既是其元素定义的顺序。

##1. PanicFilter(处理运行时异常)

PanicFilter用来处理所有运行时异常信息,它使用`recover()`来捕获所有运行时异常。它被安排为第一个过滤器是有原因的。

定义如下:

// PanicFilter wraps the action invocation in a protective defer blanket that
// converts panics into 500 error pages.
func PanicFilter(c *Controller, fc []Filter) {
defer func() {
if err := recover(); err != nil {
handleInvocationPanic(c, err)
}
}()
fc[0](c, fc[1:])
}

// This function handles a panic in an action invocation.
// It cleans up the stack trace, logs it, and displays an error page.
func handleInvocationPanic(c *Controller, err interface{}) {
error := NewErrorFromPanic(err)
if error == nil {
ERROR.Print(err, "\n", string(debug.Stack()))
c.Response.Out.WriteHeader(500)
c.Response.Out.Write(debug.Stack())
return
}

ERROR.Print(err, "\n", error.Stack)
c.Result = c.RenderError(error)
}

首先会定一个defer闭包,之后继续调用过滤器链。

这很像一个递归,确保了它以后的所有过滤器链执行完后这个defer闭包才会执行。而`PanicFilter`又是第一个被执行的过滤器,这样在运行`recover()`时,能确保它会捕获所有异常。

##2. RouterFilter(处理路由)

项目中`conf/routes`配置的路由信息,将在这里被正确的转到各个`Controller`/`Action`。

func RouterFilter(c *Controller, fc []Filter) {
// Figure out the Controller/Action
var route *RouteMatch = MainRouter.Route(c.Request.Request)
if route == nil {
c.Result = c.NotFound("No matching route found")
return
}

// The route may want to explicitly return a 404.
if route.Action == "404" {
c.Result = c.NotFound("(intentionally)")
return
}

// Set the action.
if err := c.SetAction(route.ControllerName, route.MethodName); err != nil {
c.Result = c.NotFound(err.Error())
return
}

// Add the route and fixed params to the Request Params.
c.Params.Route = route.Params

// Add the fixed parameters mapped by name.
// TODO: Pre-calculate this mapping.
for i, value := range route.FixedParams {
if c.Params.Fixed == nil {
c.Params.Fixed = make(url.Values)
}
if i (line => name of first arg to Validation func)
// E.g. "myapp/controllers.helper" or "myapp/controllers.(*Application).Action"
// This is set on initialization in the generated main.go file.
var DefaultValidationKeys map[string]map[int]string

首先,`restoreValidationErrors`方法尝试从客户端请求的cookie中恢复出错的验证信息。

然后为`Controller`实例创建一个`Validation`对象。

接着继续过滤器链的调用。

所有过滤器链调用完后,根据`Validation.keep`得值判断是否将错误的验证信息写入cookies中从而在整个绘画中保存验证信息。

通常,验证器在自定义`Controller`的`Action`中使用,根据情况可以调用`Validation.keep`来将错误信息保持在会话中。

`DefaultValidationKeys`中存储的是整个项目源码中所有调用验证的行号与名称,在`main.go`中为它赋值。在调用验证之后,默认会调用她的`apply`方法, 它会将`DefaultValidationKeys`存储的行号等详细信息组装为`ValidationResult`。

关于`DefaultValidationKeys`的生成 具体请移步 Go Revel - main函数分析 http://www.cnblogs.com/hangxin1940/p/3263775.html

##8. I18nFilter(处理国际化i18n)

判断header或cookie中是否存在语言声明字段,然后定义`controller`的`Request.Locale`对象,方便后续工作中处理国际化i18n

##9. InterceptorFilter(处理拦截器)

执行4种拦截器: `BEFORE`、`AFTER`、`PANIC`、`FINALLY`

func InterceptorFilter(c *Controller, fc []Filter) {
defer invokeInterceptors(FINALLY, c)
defer func() {
if err := recover(); err != nil {
invokeInterceptors(PANIC, c)
panic(err)
}
}()

// Invoke the BEFORE interceptors and return early, if we get a result.
invokeInterceptors(BEFORE, c)
if c.Result != nil {
return
}

fc[0](c, fc[1:])
invokeInterceptors(AFTER, c)
}

func invokeInterceptors(when When, c *Controller) {
var (
app = reflect.ValueOf(c.AppController)
result Result
)
for _, intc := range getInterceptors(when, app) {
resultValue := intc.Invoke(app)
if !resultValue.IsNil() {
result = resultValue.Interface().(Result)
}
if when == BEFORE && result != nil {
c.Result = result
return
}
}
if result != nil {
c.Result = result
}
}

第一行的`defer`执行`FINALLY`阶段的拦截器,它在方法退出前执行,第二个`defer`闭包则捕捉运行时异常,用以拦截处理`PANIC`阶段的拦截器,它会在`FINALLY`之前调用,此时能捕获绝大部分的运行时异常。

`BEFORE`、`AFTER`拦截器分别在运行过滤器链代码的前后执行,而此时整个过滤器链已剩下最后一个过滤器没调用了。

##10. ActionInvoker(由开发者处理Action请求)

到了这里,代码就交给了开发者,它会根据请求找到并执行自定义的`controller`中的`action`,这里完全有开发者做主。

func ActionInvoker(c *Controller, _ []Filter) {
// Instantiate the method.
methodValue := reflect.ValueOf(c.AppController).MethodByName(c.MethodType.Name)

// Collect the values for the method's arguments.
var methodArgs []reflect.Value
for _, arg := range c.MethodType.Args {
// If they accept a websocket connection, treat that arg specially.
var boundArg reflect.Value
if arg.Type == websocketType {
boundArg = reflect.ValueOf(c.Request.Websocket)
} else {
TRACE.Println("Binding:", arg.Name, "as", arg.Type)
boundArg = Bind(c.Params, arg.Name, arg.Type)
}
methodArgs = append(methodArgs, boundArg)
}

var resultValue reflect.Value
if methodValue.Type().IsVariadic() {
resultValue = methodValue.CallSlice(methodArgs)[0]
} else {
resultValue = methodValue.Call(methodArgs)[0]
}
if resultValue.Kind() == reflect.Interface && !resultValue.IsNil() {
c.Result = resultValue.Interface().(Result)
}
}

这里通过反射来调用之前在`RouterFilter`中生成的`contrller`实例的`Action`方法,并将转换过的请求参数传入,这里就真正进入自己构造的controller中了。

所有过滤器链处理完后,会返回至`server.go`中。 移步至 Go Revel - server.go 源码分析 http://www.cnblogs.com/hangxin1940/p/3265538.html

Go Revel - Filter(过滤器)源码分析的更多相关文章

  1. Spring Security(四) —— 核心过滤器源码分析

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...

  2. Go Revel - server.go 源码分析

    之前介绍了 Go Revel - main函数分析 http://www.cnblogs.com/hangxin1940/p/3263775.html 最后会调用 `revel.Run(*port)` ...

  3. Struts2中文乱码问题 过滤器源码分析

    整理自网上: 前几天在论坛上看到一篇帖子,是关于Struts2.0中文乱码的,楼主采用的是spring的字符编码过滤器 (CharacterEncodingFilter)统一编码为GBK,前台提交表单 ...

  4. Struts2 源码分析——过滤器(Filter)

    章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...

  5. JavaWeb过滤器Filter(附tomcat部分源码分析)

    过滤器Filter 过滤器通常对一些web资源进行拦截,做完一些处理器再交给下一个过滤器处理,直到所有的过滤器处理器,再调用servlet实例的service方法进行处理.过滤器可以对request进 ...

  6. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

  7. Envoy 源码分析--network L4 filter manager

    目录 Envoy 源码分析--network L4 filter manager FilterManagerImpl addWriteFilter addReadFilter addFilter in ...

  8. Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解

    Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...

  9. 源码分析SpringCloud Gateway如何加载断言(predicates)与过滤器(filters)

    我们今天的主角是Gateway网关,一听名字就知道它基本的任务就是去分发路由.根据不同的指定名称去请求各个服务,下面是Gateway官方的解释: https://spring.io/projects/ ...

随机推荐

  1. [转]利用Docker构建开发环境

    利用Docker构建开发环境 Posted by  makewonder on 2014 年 4 月 2 日   最近接触PAAS相关的知识,在研发过程中开始使用Docker搭建了自己完整的开发环境, ...

  2. MySQL开启federated引擎实现数据库表映射

    1.查看federated引擎是否开启 点击进入Navicat并点击键盘上F6,出现命令行界面 ,输入指令:show engines; 2.开启federated引擎 Windows系统 : 在my. ...

  3. JS中getElementByID,getElementsByName,getElementsByTagName的区别

    <input type="text" name="mynumber" id="mynum1" value="" / ...

  4. Java数据结构和算法(六):前缀、中缀、后缀表达式

    前面我们介绍了三种数据结构,第一种数组主要用作数据存储,但是后面的两种栈和队列我们说主要作为程序功能实现的辅助工具,其中在介绍栈时我们知道栈可以用来做单词逆序,匹配关键字符等等,那它还有别的什么功能吗 ...

  5. 如何安全的下载Devcon.exe文件

    devcon.exe是windows设备管理器的命令行版本,可以让你在cmd中修改设备,但是微软没有提供单独的下载,只能下载一个2G多的wdk包(windows drive kits)才行.私下使用别 ...

  6. Android AOP之路三 Android上的注解

    一.简单介绍 啥是注解.不懂的能够先看我上一篇文章. 在android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 能够更加方便快捷 安全有效的编写java代 ...

  7. (转)Unity3d使用心得(2):Unity3d 动态下载动画资源——AnimationClip 的使用 - 斯玛特琦

    引言: 在使用 Unity3d 开发微端.或者网页游戏的时候常常须要将资源打包成 AssetBundle ,然后通过 www 的方式动态的下载资源.今天要分享的是我再动态下载 Animation 骨骼 ...

  8. (原创)c++11改进我们的模式之改进代理模式,实现通用的AOP框架

    c++11 boost技术交流群:296561497,欢迎大家来交流技术. 本次要讲的时候如何改进代理模式,具体来说是动态代理模式,动态代理模式一般实现AOP框架,不懂AOP的童鞋看这里.我前面的博文 ...

  9. Lua中的模块与module函数详解

    很快就要开始介绍Lua里的“面向对象”了,在此之前,我们先来了解一下Lua的模块. 1.编写一个简单的模块 Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个table里有一些变量.一 ...

  10. Python Redis pipeline操作和Redis乐观锁保持数据一致性

    Redis是建立在TCP协议基础上的CS架构,客户端client对redis server采取请求响应的方式交互. redis 乐观锁:也可理解为版本号比较机制,主要是说在读取数据逇时候同时读取其版本 ...