原文出处:http://gorevel.cn/docs/manual/index.html

Revel 框架手册介绍。

Revel 从Rails 和 Play! 中吸收了许多成熟的设计思想, 许多相同的思想被用到了框架的设计和接口中。

Revel 通过简单的约定来支持 MVC 设计模式,轻量、开发效率高。

MVC

  • 模型 描述基本的数据对象,特定的查询和更新逻辑。
  • 视图 一些模板,用于将数据呈现给用户。
  • 控制器 执行用户的请求,准备用户所需的数据,并指定模板进行渲染。

一些不错的MVC结构概述,像 Play! 框架 与Revel框架完全匹配。

请求的生命周期

下面是一个请求处理的基本流程:

概要:

  • Revel 暴漏一个单独的 http.Handler, 负责实例化控制器 (请求的上下文),并沿着过滤器链传递请求。
  • Filters 链接到一个请求处理链,由水平相关的功能组成,比如请求记录,cookie策略,授权等。大部分内建功能都是由过滤器实现的。
  • Actions 是处理用户输入和产生请求结果的的函数。

HTTP Handler

Revel 构建自顶级 Go HTTP server, 他为每一个到来的请求创建一个 go-routine (轻量线程),用于处理并发。

Revel什么也不做,只是把请求交给过滤器链处理,完成之后,将结果写到响应中。

默认情况下, Revel 处理程序注册到 "/" 接受所有的请求连接。然而, 应用程序可以自由的重写此行为 – 例如, 可以使用现有的 http.Handlers 而不是Revel来重新实现此功能。具体请参考 FAQ

过滤器

过滤器 实现了Revel的大部分请求处理功能,过滤器有一个简单的易于嵌套的接口。

“过滤器链” 是一个函数数组, 每一个都会去执行下一个,直到最后一个过滤器执行了控制器方法。例如, 过滤器链中的第一个过滤器是 RouterFilter, 它决定哪个操作接受请求并保存到控制器。

总之, 过滤器和过滤器链就像机架一样。

控制器和方法

每一个 HTTP 请求,执行一个 action, 处理请求并写入响应。 相关的 actions 被分组到 controllers中. Controller 类型包含相关字段和方法,作为每个请求的上下文。

作为 HTTP 请求处理的一部分,Revel 实例化你的控制器,并设置嵌入revel.Controller的所有的属性。 Revel 不在请求之间共享实例。


Controller 是直接 或 间接嵌入 *revel.Controller 的一个struct。

type AppController struct {  *revel.Controller}

Action 是 Controller 的方法。符合下面的条件:

  • 名称是导出的(首字母大写)
  • 返回 revel.Result 类型

例如:

func (c AppController) ShowLogin(username string) revel.Result {    ..    return c.Render(username)}

程序调用 revel.Controller.Render 渲染一个模板, 传递给模板一个username参数。revel.Controller 有许多方法处理revel.Result, 程序也可以自己创建处理方法。

Results

Result 符合下面的接口:

type Result interface {    Apply(req *Request, resp *Response)}

通常, 什么也不响应,直到 action 和所有的过滤器返回。此时,Revel写入响应的headers和cookies。(例如设置会话cookie), 然后调用 Result.Apply 写入实际响应内容。

按照go 命令行工具的要求将Revel和Revel应用程序安装到 GOPATH。 (参考 “GOPATH 环境变量” go 命令 文档)

项目结构示例

gocode                  GOPATH 目录  src                   GOPATH src 目录    revel               Revel 安装目录      ...    sample              Revel应用程序根目录      app               MVC目录        controllers     控制器          init.go               models          模型        routes                  views           模板      tests             测试      conf                      app.conf        默认配置        routes          路由定义      messages          国际化      public            静态文件        css             CSS files        js              Javascript files        images          Image files

app

app 存放源代码和模板。

  • app/controllers
  • app/models
  • app/views

Revel 约定:

  • 模板文件放到 app/views目录
  • 控制器文件放到 app/controllers目录

此外,Revel监视 app/ 目录,当发现文件变动时,会自动重新编译。app/ 目录以外的依赖关系不会被监视,在必要的时候由开发人员重新执行编译。

Revel在app目录的init()函数开始的时候会导入 app/中的所有依赖包或者 ( 模块)。

controllers/init.go 用于注册拦截器 interceptor。同一个包的源文件中init() 函数的执行无序的, 所以收集所有的拦截器定义到同一个文件中,便于开发者指定拦截器的执行顺序(也可以用于顺序敏感的初始化)。

conf

conf 目录包含了Revel应用程序的配置文件,有两个主要的配置:

  • app.conf, 主配置文件,包含了标准配置参数。
  • routes, 路由定义文件。

messages

messages 目录包含了本地化消息文件。

public

静态资源文件存放到 public 目录,由Web server 直接提供静态文件支持。 通常包含三个标准的子目录 images, CSS 和 JavaScript。

目录的名字可以随意,使用的时候只需要与路由对应起来就好。

Revel 使用 Go 模板, 在下面两个目录中查找模板:

  • 应用程序的 views 目录 (包括所有的子目录)
  • Revel的 templates 目录.

比如有一个控制器 Hello ,方法名为 World, Revel 会查找名字为 views/Hello/World.html的模板。模板名字不区分大小写,所以 views/hello/world.html 与 views/HeLlO/wOrLd.HtMl都是匹配的模板.

Revel 提供了错误页面模板 (在开发模式中友好的显示编译错误), 开发者也可以重写这些模板,比如app/views/errors/500.html.

渲染上下文

Revel 使用 RenderArgs map 渲染模板。除了开发者传送的数据, Revel 也提供一些有用的数据:

  • “errors” - 验证错误(map,请参考文档 Validation.ErrorMap
  • “flash” - 上个请求flash的数据

模板功能

Go 提供了一些 模板函数。Revel 也增加了一些模板函数。请阅读下面的文档 或 查看源代码.

eq

一个简单的 “a == b” 测试.

例如:

<div class="message {{if eq .User "you"}}you{{end}}">

set

在当前模板上下文中设置一个变量

例如:

{{set . "title" "Basic Chat room"}}<h1>{{.title}}</h1>

append

添加变量到一个数组中, 或者在模板上下文中创建一个数组

例如:

{{append . "moreScripts" "js/jquery-ui-1.7.2.custom.min.js"}}{{range .moreStyles}}  <link rel="stylesheet" type="text/css" href="/public/{{.}}">{{end}}

field

input 字段辅助函数.

给出一个字段名, 函数会生成包含下面成员的结构:

  • Id: 字段Id, 转换为一个HTML字段的ID。
  • Name: 字段名称
  • Value: 当前上下文中字段的值
  • Flash: 带回的字段值
  • Error: 字段错误消息(如果有错误)
  • ErrorClass: 原始的字符串 “hasError”, 如果没有错误就是一个 ””.

浏览 godoc.

例如:

{{with $field := field "booking.CheckInDate" .}}  <p class="{{$field.ErrorClass}}">    <strong>Check In Date:</strong>    <input type="text" size="10" name="{{$field.Name}}" class="datepicker" value="{{$field.Flash}}">    * <span class="error">{{$field.Error}}</span>  </p>{{end}}

option

使用辅助函数生成 HTML option 字段。

例如:

{{with $field := field "booking.Beds" .}}<select name="{{$field.Name}}">  {{option $field "1" "One king-size bed"}}  {{option $field "2" "Two double beds"}}  {{option $field "3" "Three beds"}}</select>{{end}}

radio

使用辅助函数生成 HTML radio input 字段

例如:

{{with $field := field "booking.Smoking" .}}  {{radio $field "true"}} Smoking  {{radio $field "false"}} Non smoking{{end}}

nl2br

将换行符转换成 HTML 的 break.

例如:

You said:<div class="comment">{{nl2br .commentText}}</div>

pluralize

一个辅助的复数函数

例如:

There are {{.numComments}} comment{{pluralize (len comments) "" "s"}}

raw

输出原生的、未转义的文本

例如:

<div class="body">{{raw .blogBody}}</div>

Including

Go 模板允许你在模板中包含其他模板,比如:

{{template "header.html" .}}

注意: * 相对路径是 app/views

温馨提示

Revel 应用程序有效利用 Go 模板,请看看下面的例子:

  • revel/samples/booking/app/views/header.html
  • revel/samples/booking/app/views/Hotels/Book.html

使用辅助函数,为模板设置标题和额外的样式。

例如:

<html>  <head>    <title>{{.title}}</title>    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    <link rel="stylesheet" type="text/css" media="screen" href="/public/css/main.css">    <link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">    {{range .moreStyles}}      <link rel="stylesheet" type="text/css" href="/public/{{.}}">    {{end}}    <script src="/attachments/image/cimg/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>    <script src="/attachments/image/cimg/script>    {{range .moreScripts}}      <script src="/attachments/image/cimg/{{.}}" type="text/javascript" charset="utf-8"></script>    {{end}}  </head>

在模板中这样使用:

{{set . title "Hotels"}}{{append . "moreStyles" "ui-lightness/jquery-ui-1.7.2.custom.css"}}{{append . "moreScripts" "js/jquery-ui-1.7.2.custom.min.js"}}{{template "header.html" .}}

自定义模板函数

应用程序可以注册自定义模板函数

例如:

func init() {    revel.TemplateFuncs["eq"] = func(a, b interface{}) bool { return a == b }}

“拦截器”是框架执行一个方法之前或之后被调用的函数。它允许 面向方面编程, 作用如下:

  • 请求记录
  • 错误处理
  • 状态保持

在 Revel 中, 有两种形式的拦截器:

  1. 函数拦截器:请参考 InterceptorFunc 接口.

    • 不能挂接到某个特定的控制器方法
    • 可以应用到所有的、任意的控制器上
  2. 方法拦截器:一个不带参数、并返回一个 revel.Result的控制器方法

    • 只能拦截控制器方法
    • 可以修改被调用的控制器

拦截器的执行顺序与添加位置相关

拦截时间

在一个请求生命周期内,可以注册四种拦截时间:

  1. BEFORE: 在请求被路由到以后,并且session, flash, 参数解析之后、控制器方法被调用之前执行拦截。
  2. AFTER: 在请求返回了一个结果, 但是结果被应用之前执行拦截。如果出现了panic,拦截不会被调用。
  3. PANIC: 在控制器方法中或应用结果时出现panic退出后被拦截。
  4. FINALLY: 在控制器方法执行完毕并且结果被应用之后被拦截。

结果

拦截器通常返回 nil, 在这种情况下,需要继续处理请求,不能中断。

返回一个非 nil revel.Result的效果, 取决于拦截器被调用的时间:

  1. BEFORE: 没有进一步的拦截器被调用, 也不是一个控制器方法。
  2. AFTER: 所有拦截器仍然可以运行。
  3. PANIC: 所有拦截器仍然可以运行。
  4. FINALLY: 所有拦截器仍然可以运行。

在任何情况下,返回的结果都将附加到任何现有的结果上:

BEFORE:返回的结果是保证是最终的。

AFTER:它可能是一个进一步的拦截器,可以返回自己的结果。

例如

函数拦截器

下面是定义和注册函数拦截器的一个简单例子。

func checkUser(c *revel.Controller) revel.Result {    if user := connected(c); user == nil {        c.Flash.Error("请先登录")        return c.Redirect(App.Index)    }    return nil}func init() {    revel.InterceptFunc(checkUser, revel.BEFORE, &Hotels{})}

方法拦截器

方法拦截器有两种方式的签名:

func (c AppController) example() revel.Resultfunc (c *AppController) example() revel.Result

下面是个同样的例子,只能拦截一个控制器。

func (c Hotels) checkUser() revel.Result {    if user := connected(c); user == nil {        c.Flash.Error("请先登录")        return c.Redirect(App.Index)    }    return nil}func init() {    revel.InterceptMethod(Hotels.checkUser, revel.BEFORE)}

过滤器是Revel框架的中间件 – 是组成请求处理管道的独立的功能。他们执行框架的所有功能。

过滤器类型是一个简单的函数:

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

每个过滤器负责调用过滤器链中的下一个过滤器。下面是个默认的过滤器栈:

// Filters 是默认的全局过滤器集。// 可以在程序初始化时设置它。var Filters = []Filter{    PanicFilter,             // 从恐慌中恢复,并显示一个错误页面。    RouterFilter,            // 负责解析路由,并选择正确的控制器方法。    FilterConfiguringFilter, // 用于添加/删除每个动作过滤的钩子。    ParamsFilter,            // 解析参数到 Controller.Params 中。    SessionFilter,           // 恢复和写入会话 cookie。    FlashFilter,             // 恢复和写入 flash cookie。    ValidationFilter,        // 恢复保存验证错误并保存新的Cookie中。    I18nFilter,              // 解析请求语言。    InterceptorFilter,       // 执行拦截器。    ActionInvoker,           // 调用控制器。}

过滤器链配置

全局配置

程序可以在 init() 中重写 revel.Filters 变量,来配置过滤器链 (默认在 app/init.go)。

func init() {    // Filters 是默认的全局过滤器集。    revel.Filters = []Filter{        PanicFilter,             // 从恐慌中恢复,并显示一个错误页面。        RouterFilter,            // 负责解析路由,并选择正确的控制器方法。        FilterConfiguringFilter, // 用于添加/删除每个动作过滤的钩子。        ParamsFilter,            // 解析参数到 Controller.Params 中。        SessionFilter,           // 恢复和写入会话 cookie。        FlashFilter,             // 恢复和写入 flash cookie。        ValidationFilter,        // 恢复保存验证错误并保存新的Cookie中。        I18nFilter,              // 解析请求语言。        InterceptorFilter,       // 执行拦截器。        ActionInvoker,           // 调用控制器。    }}

每个请求沿着过滤器链从上到下依次执行。

Per-Action configuration

尽管所有的请求都被发往过滤器链 revel.Filters, Revel 也提供了 过滤器配置, 允许开发者根据操作或控制器添加、插入、删除过滤器。

此功能通过 FilterConfiguringFilter 实现, 它本身就是一个过滤器.

实现一个过滤器

保持过滤器链能够依次执行

Filters 负责依次调用下一个过滤器来依次处理请求。这通常需要完成下面的表达式:

var MyFilter = func(c *revel.Controller, fc []revel.Filter) {    // .. 做一些预处理 ..    fc[0](c, fc[1:]) // 执行下一个过滤器    // .. 做一些后期处理 ..}

获取控制器类型

Filters 接受一个 *Controller 类型的参数, 而不是被调用的实际的控制器类型。如果过滤器需要访问实际的控制器类型,可以这样实现:

var MyFilter = func(c *revel.Controller, fc []revel.Filter) {    if ac, err := c.AppController.(*MyController); err == nil {        // 判定存在一个 *MyController 实例...    }    fc[0](c, fc[1:]) // 执行下一个过滤器}

注意:这种模式往往说明拦截器可能是实现所需功能的好的机制的一个指标。

Revel 提供Websockets支持。

处理一个 Websocket 连接:

  1. 添加一个 WS 类型的路由。
  2. 添加一个接受 *websocket.Conn 参数的控制器方法.

举个栗子, 在 routes 文件中添加路由:

WS /app/feed Application.Feed

添加一个控制器方法,接受*websocket.Conn 参数:

import "code.google.com/p/go.net/websocket"func (c App) Feed(user string, ws *websocket.Conn) revel.Result {    ...}

自定义Controller 是一个直接或间接嵌入了 *revel.Controller 的struct。

典型用法:

type AppController struct {  *revel.Controller}

*revel.Controller 在你自定义的struct中必须是第一个嵌入的类型

revel.Controller 用于请求的上下文,包含了请求与响应数据,请到 the godoc 查看完整内容, 下面是一个定义 (以及辅助类型的定义):

type Controller struct {    Name          string          // 控制器名称, 比如: "Application"    Type          *ControllerType // 控制器类型描述    MethodType    *MethodType     // 控制器方法描述    AppController interface{}     // 控制器实例    Request  *Request    Response *Response    Result   Result    Flash      Flash                  // 用户 cookie, 在请求之后清空    Session    Session                // Session, 保存在cookie中,签名。    Params     *Params                // URL和表单中的参数(包扩 multipart).    Args       map[string]interface{} // 每个请求的暂存空间    RenderArgs map[string]interface{} // 传递给模板的参数    Validation *Validation            // 数据验证帮助器}// 统一的请求参数包装// 包括:// - URL 查询字符串// - Form 表单字段// - File 文件上传type Params struct {    url.Values    Files map[string][]*multipart.FileHeader}type Request struct {    *http.Request    ContentType string}type Response struct {    Status      int    ContentType string    Headers     http.Header    Cookies     []*http.Cookie    Out http.ResponseWriter}

作为HTTP请求处理的一部分,Revel实例化一个控制器,设置所有revel.Controller嵌入的属性, 因此, Revel 不在请求之间共享实例,对于每个请求的处理,控制器都是独立的。

Revel使用文本文件提供国际化翻译支持。Revel 支持语言翻译文件化, 自动区域查询, cookie重写、嵌套的消息与参数。

词汇表

  • Locale(语言环境): 包含 语言 和 区域两个部分,指示用户的语言偏好,例如 en-US
  • Language(语言): locale 的语言部分, 例如 en。 语言标识符请参考 ISO 639-1 codes
  • Region(区域): locale 的区域部分, 例如 US。 区域标识符请参考 ISO 3166-1 alpha-2 codes

示例程序

Revel 处理消息文件和国际化的方法与其他框架类似。如果你想立马上手, 可以参考示例程序 revel/samples/i18n,里面包含所有的基础知识。

消息文件

国际化消息在消息文件中定义。这些消息在渲染视图模板(或程序的其他地方)时使用。当创建新的消息文件时,需要遵循几个规则:

  • 所有的消息文件应存放在应用程序根目录的messages目录中。
  • 文件扩展名与 language 一致,并且应为 ISO 639-1 code
  • 消息文件应该是 UTF-8 编码。虽然不是强制规定, 最好还是遵循这个约定。
  • 消息文件必须是 goconfig 文件 支持多有的 goconfig 特性

组织消息文件

消息文件的文件名​​没有任何限制; 只要扩展名合法就行。每种语言的文件数量也没有限制。当应用程序启动时,Revel会解析messages语言目录中所有的扩展名合法的消息文件,并根据语言将它们合并。这意味着你可以自由组织你的消息文件。

例如,您可能希望采取传统的方法,在一个文件中定义所有的:

/app    /messages        messages.en        messages.fr        ...

或者是为每种语言创建多个文件,并根据分类组织他们:

/app    /messages        labels.en        warnings.en        labels.fr        warnings.fr        ...

重要注意事项: 在同一语言的多个文件中,虽然技术上可以定义相同的消息key,但是这将导致不可预知的行为。当每种语言定义了多个文件时,请注意保持您的key唯一,这样文件合并后,key将不会被覆盖!

消息 key 和 值

消息文件是一个 goconfig 文件。这意味着,消息应按照key-value格式定义:

key=value

举个栗子:

greeting=Hello greeting.name=Robgreeting.suffix=, welcome to Revel!

goconfig 文件被分成多个 sections默认的段 总是存在, 并且包含未指定段时定义的消息。例如:

key=value[SECTION]key2=value2

key=value 消息消息被隐式放置在默认段中,因为它没有定义在一个指定的段中。

消息文件的所有消息应在定义默认的段中,除非它们是用于某个特定区域(见Regions获取更多消息)。

注意: 段是 goconfig 功能.

区域

特定区域的消息应当在相同的段中定义。Region-specific messages should be defined in sections with the same name. 例如,假设我们要对所有讲英语的用户显示 "Hello", 对英国用户显示 "Hey" ,对讲美国用户显示 "Howdy"。 为了做到这一点,我们可以定义下面的消息文件 greeting.en:

greeting=Hello[GB]greeting=Hey[US]greeting=Howdy

对于定义了英语 (en) 作为他们的首选语言时,Revel 会将greeting 解析成 Hello。只有在用户的区域被明确定义的情况下,比如en-GB 或 en-US ,Revel 才会用对应段中的消息去解析 greeting

重要提示: 如果定义了一个不合法的区域,技术上是允许的,但是它们永远不会被解析。

引用 和 参数

引用

文件中的消息也可以引用其他消息。这使得用户可以从一个或多个其它的消息组成一个单一的消息。引用消息的语法是 %(key)s. 例如:

greeting=Hello greeting.name=Robgreeting.suffix=, welcome to Revel!greeting.full=%(greeting)s %(greeting.name)s%(greeting.suffix)s

注意:

  • 引用是 goconfig 的功能.
  • 因为消息文件可以合并,多以只要它们被定义为同一种语言,就可以引用其他文件中的消息。

参数

消息支持一个或多个参数。在消息中参数使用与 fmt 包相同的规则去解析。 例如:

greeting.name_arg=Hello %s!

参数按照给定的顺序进行解析,参考 解析消息.

解析客户端语言环境(locale)

为了找出用户喜欢的语言环境,Revel会在以下地方查找一个可用的语言环境:

  1. 语言 cookie

    对于每次请求,框架会查找应用程序配置 (i18n.cookie)。如果找到了,它的值被假定为当前的语言环境。当一个cookie已经发现,所有其他解析方法将被跳过。

  2. Accept-Language HTTP 头

    对于每次请求,Revel 会自动解析 HTTP 头信息的 Accept-Language。其中的每个语言环境根据 HTTP 规范在当前Request实例中获取和存储。此信息用来为稍后的消息解析函数确定当前语言环境。

    更多信息请参考 Parsed Accept-Language HTTP header.

  3. 默认语言

    当以上所有方法都没有找到可用的客户端语言环境时,框架将使用配置文件中定义的默认语言i18n.default_language

如果有的消息解析失败,则返回一个包含原始消息名的特殊格式的字符串。

注意: Accept-Language 头信息 总是 被解析并保存到当前 Request中, 即使它已经存在。在这种情况下,头信息中的值即使从未被消息解析功能使用,但是仍可以在需要他们的时候使用。

检查当前语言环境

应用程序可以从Request内部使用 Request.Locale 属性访问当前语言环境。例如:

func (c App) Index() revel.Result {    currentLocale := c.Request.Locale    c.Render(currentLocale)}

在模板中, 就可以从 renderArgs 中的 currentLocale 变量中访问当前语言环境。 例如:

    <p>Current preferred locale: {{.currentLocale}}</p>

解析 Accept-Language HTTP 头信息

如果程序需要访问 Accept-Language HTTP 头信息, 可以从 Controller 实例的 Request中获取。 AcceptLanguages 字段(AcceptLanguage实例的切片)包含所有从各自的头信息中解析的值, 最合适的值是切片中的第一个。例如:

func (c App) Index() revel.Result {    // 获取所有解析到的AcceptLanguage的字符串表示形式    c.RenderArgs["acceptLanguageHeaderParsed"] = c.Request.AcceptLanguages.String()    // 返回最合适AcceptLanguage实例    c.RenderArgs["acceptLanguageHeaderMostQualified"] = c.Request.AcceptLanguages[0]    c.Render()}

更多信息请参考 HTTP 规范.

解析消息

消息可以在 控制器 或 模板中解析。

控制器

控制器中使用 Message(message string, args ...interface{}) 函数使用当前语言环境解析消息:

func (c App) Index() revel.Result {    c.RenderArgs["controllerGreeting"] = c.Message("greeting")    c.Render()}

模板

在模板中使用 模板函数 msg 解析当前语言环境的消息。例如:

    <p>Greetings without arguments: {{msg . "greeting"}}</p>    <p>Greetings: {{msg . "greeting.full.name" "Tommy Lee Jones"}}</p>

注意: msg 函数的签名是 msg . "message name" "argument" "argument". 如果没有参数, 可以忽略。

配置

文件选项描述
app.confi18n.cookie语言cookie的名称。应使用Revel cookie的前缀,以避免cookie名称冲突。
app.confi18n.default_language默认的语言环境,在没有首选区域的情况下使用。

路由定义在一个单独的 routes 文件中.

路由定义规则是:

(METHOD) (URL Pattern) (Controller.Action)

下面演示路由的定义:

# conf/routes# 这个文件定义了应用程序的所有路由 (优先级按照先后顺序)GET    /login                 App.Login              # 一个简单的路由GET    /hotels/               Hotels.Index           # 一个简单的路由,带不带斜线后缀都一样GET    /hotels/:id            Hotels.Show            # 绑定到一个URI参数idWS     /hotels/:id/feed       Hotels.Feed            # WebSocketsPOST   /hotels/:id/:action    Hotels.:action         # 自动匹配路由到一个控制器的多个方法GET    /public/*filepath      Static.Serve("public") # 映射到 /public/下的静态文件
  • /:controller/:action :controller.:action # 捕获所有控制器方法; 自动生成 URL

接下来我们一个个看。最后在看看如何实现 反转路由 – 生成URL调用一个特定的方法

一个简单的路由

GET    /login                 App.Login

直接将一个URL精确匹配到一个控制器方法。

斜线后缀

GET    /hotels/               Hotels.Index

/hotels 和 /hotels/ 路由都会调用 Hotels.Index 方法。反转 Hotels.Index产生的URL则会包含斜线后缀。

斜线后缀无法用于区分两个同名路由,/login 和 /login/是一样的

URL 参数

GET    /hotels/:id            Hotels.Show

Revel可以从URL重提取参数并绑定到控制器方法。:id 变量会尝试匹配除了斜线后缀外的任意值。举个栗子,/hotels/123和 /hotels/abc 都与这个路由匹配。

提取的参数保存到 Controller.Params map中, 绑定到控制器方法Show中. 举个栗子:

func (c Hotels) Show(id int) revel.Result {    ...}

func (c Hotels) Show() revel.Result {    var id string = c.Params.Get("id")    ...}

func (c Hotels) Show() revel.Result {    var id int    c.Params.Bind(&id, "id")    ...}

通配符

GET    /public/*filepath            Static.Serve("public")

路由器识别第二种通配符 。通配符必须放到路由的最后一个元素前, 匹配所有以*之前路径开头的URL

这里的路由将匹配 “/public/”和以他开头的所有路径

Websockets

WS     /hotels/:id/feed       Hotels.Feed

Websockets 以同样的方式接受请求, 它使用 WS标识符

相应的方法签名如下:

func (c Hotels) Feed(ws *websocket.Conn, id int) revel.Result {    ...}

静态文件服务

GET    /public/*filepath            Static.Serve("public")GET    /favicon.ico                 Static.Serve("public","img/favicon.png")

对于使用2个参数的Static.Serve, 在  和 , 之间不允许有空格,due to how encoding/csv works.

对于静态资源服务, Revel 有static 模块支持, 包含一个单一的带两个参数的静态 控制器

  • 前缀 (字符串) - 指定到静态文件根目录的相对或绝对路径
  • 文件路径 (字符串) - 指定文件的相对路径。

(可以参考 代码结构布局图 了解更多信息)

固定参数

路由可以绑定一个或多个参数到控制器方法中,举个栗子:

GET    /products/:id     ShowList("PRODUCT")GET    /menus/:id        ShowList("MENU")

参数通过在路由中的位置绑定到参数名,这里,列表中的字符串将被绑定到方法的第一个参数中。

下面是几种有用的情况:

  • 一组相似功能的方法
  • 相同功能的方法,但工作在不同的模式
  • 相同功能的方法,但操作不同的数据类型

自动路由

POST   /hotels/:id/:action    Hotels.:action
  • /:controller/:action :controller.:action

URL 的提取也可以用于确定调用哪个控制器方法。匹配规则不区分大小写

第一个例子匹配下面的路由

/hotels/1/show    => Hotels.Show/hotels/2/details => Hotels.Details

第二个例子匹配应用程序中的任意的方法

/app/login         => App.Login/users/list        => Users.List

由于匹配控制器和方法不区分大小写,下面的路由也匹配

/APP/LOGIN         => App.Login/Users/List        => Users.List

使用自动路由作为一个捕获所有的方法(例如文件中的最后一个路由),用于快速挂接方法,to non-vanity URLs,尤其是在与反向路由器一起使用是很有用的。

反转路由

对于以下几种情况,使用反转路由生成URLs是一个很好的方法,

  • 避免拼写错误
  • 编译器确保反向路由有正确的数量和参数类型。
  • 修改路由,仅需要在routes文件中修改一次

构建程序时,Revel自动生成一个路由包app/routes,一条语句就可以使用:

routes.Controller.Action(param1, param2)

上面的语句使用Controller.Action和参数返回一个字符串类型的 URL,下面给出一个完整的例子:

import (    "github.com/revel/revel"    "project/app/routes")type App struct { *revel.Controller }// 生成一个表单页面func (c App) ViewForm(username string) revel.Result {    return c.Render(username)}// 处理提交的表单func (c App) ProcessForm(username, input string) revel.Result {    ...    if c.Validation.HasErrors() {        c.Validation.Keep()        c.Flash.Error("Form invalid. Try again.")        return c.Redirect(routes.App.ViewForm(username))  // <--- 反转路由    }    c.Flash.Success("Form processed!")    return c.Redirect(routes.App.ViewConfirmation(username, input))  // <--- 反转路由}

局限性: Only primitive parameters to a route are typed due to the possibility of circular imports. Non-primitive parameters are typed as interface{}.

Revel 提供了一个服务器端、临时的、低延迟存储的缓存库。对于频繁访问数据库中缓慢变化的数据,使用缓存一个很好的方法,并且它也可以用于实现用户session (如果基于cookie的session不足).

参考 缓存接口

过期时间

缓存有三种过期时间:

  • time.Duration,指定一个过期时间。
  • cache.DEFAULT, 默认过期时间(1小时)。
  • cache.FOREVER, 永不过期。

重要提示:调用者不能依赖于存在于缓存中内容,因为数据是不持久保存,重新启动后,会清除所有缓存数据。

序列化

缓存读写接口自动为调用者序列化任何类型的的值。有以下几种方式:

  • 如果是 []byte类型, 数据保持不变。
  • 如果是 整数类型, 数据被保存为 ASCII。
  • 其他类型,使用 encoding/gob进行编码。

实现

缓存通过以下几种方式进行配置:

  • 分布式memcached 主机缓存
  • 单独 redis 主机缓存
  • 本地内存缓存

配置

app.conf中配置缓存:

  • cache.expires - 一个字符串,time.ParseDuration 类型,指定一个过期时间 (默认1小时)
  • cache.memcached -一个布尔值,是否开启memcached缓存 (默认不开启)
  • cache.redis -一个布尔值,是否开启redis缓存 (默认不开启)
  • cache.hosts - 缓存的主机列表(多个主机使用逗号分隔),cache.memcached开启后有效。

例如

下面是常见操作的一个例子。请注意,如果不需要调用的结果来处理请求,调用者可以在新的goroutine调用缓存操作。

import (    "github.com/revel/revel"    "github.com/revel/revel/cache")func (c App) ShowProduct(id string) revel.Result {    var product Product    if err := cache.Get("product_"+id, &product); err != nil {        product = loadProduct(id)        go cache.Set("product_"+id, product, 30*time.Minute)    }    return c.Render(product)}func (c App) AddProduct(name string, price int) revel.Result {    product := NewProduct(name, price)    product.Save()    return c.Redirect("/products/%d", product.id)}func (c App) EditProduct(id, name string, price int) revel.Result {    product := loadProduct(id)    product.name = name    product.price = price    go cache.Set("product_"+id, product, 30*time.Minute)    return c.Redirect("/products/%d", id)}func (c App) DeleteProduct(id string) revel.Result {    product := loadProduct(id)    product.Delete()    go cache.Delete("product_"+id)    return c.Redirect("/products")}

Session 用法

缓存有一个全球性的key空间 - 使用它作为一个session存储,调用方应采用session UUID的优点,如下图所示:

cache.Set(c.Session.Id(), products)// 然后在随后的请求中使用它err := cache.Get(c.Session.Id(), &products)

Revel 尽可能让客户端传来的参数转换成Go语言的数据类型变得简单。这种从字符串转换成另外一种类型被称为“数据绑定”。

参数

所有的请求参数被收集到一个单独的 Params 对象中. 包括:

  • URL 路径参数
  • URL 查询参数
  • 表单字段 (Multipart or not)
  • 文件上传

Params对象定义在 (godoc)中:

type Params struct {    url.Values    Files map[string][]*multipart.FileHeader}

嵌入的 url.Values (godoc) 提供了简单值的查询支持, 但是开发者可以更方便的使用Revel的数据绑定支持,提取参数到任意的数据类型。

控制器方法参数绑定

参数可以直接绑定到控制器方法,例如:

func (c AppController) Action(name string, ids []int, user User, img []byte) revel.Result {    ...}

在控制器方法执行之前, Revel 通过变量名称绑定器解析参数到指定的数据类型,如果解析参数失败, 参数将被解析到目标数据类型的初始值。

绑定器

使用 Revel 的绑定器绑定一个参数到指定的数据类型 (godoc),它集成了Params对象。例如:

func (c SomeController) Action() revel.Result {    var ids []int    c.Params.Bind(&ids, "ids")    ...}

支持的数据类型有:

  • Int
  • Bool
  • Point
  • Slice
  • Struct
  • time.Time
  • 文件上传:*os.File, []byte, io.Reader, io.ReadSeeker

数据类型绑定的语法描述如下,详细内容描述也可以参考 源代码

Booleans

字符串 “true”, “on”, 和 “1” 被绑定到 true,其他的为 false.

Slices

切片绑定有两种语法:有序和无序

有序:

?ids[0]=1&ids[1]=2&ids[3]=4

绑定结果为 []int{1, 2, 0, 4}

无序:

?ids[]=1&ids[]=2&ids[]=3

绑定结果为 []int{1, 2, 3}

注意: 只有有序切片可以绑定到一个 []Struct:

?user[0].Id=1&user[0].Name=rob&user[1].Id=2&user[1].Name=jenny

Struct

Struct简单的使用一个 . 进行绑定:

?user.Id=1&user.Name=rob&user.Friends[]=2&user.Friends[]=3&user.Father.Id=5&user.Father.Name=Hermes

绑定到下面的struct类型:

type User struct {    Id int    Name string    Friends []int    Father User}

注意: struct中的字段必须是导出的(首字母大写)。

Date / Time

内置的 SQL 标准时间格式 [“2006-01-02”, “2006-01-02 15:04”]

使用 Revel官方模式 简单的添加时间格式到 TimeFormats 变量:

func init() {    revel.TimeFormats = append(revel.TimeFormats, "01/02/2006")}

文件上传

文件上传参数可以绑定到以下几种类型:

  • *os.File
  • []byte
  • io.Reader
  • io.ReadSeeker

它是 Go的 multipart 包 的一个包装器. 文件保存在内存中,如果文件大小超过10MB(默认值), 就会被保存到一个临时文件中。

注意: 绑定 os.File类型,会保存到临时文件 (如果没有的话),所以效率低。

自定义绑定器

应用程序可以定义绑定器。

自定义绑定器需要实现 binder 接口并注册自定义类型:

var myBinder = revel.Binder{    Bind: func(params *revel.Params, name string, typ reflect.Type) reflect.Value {...},    Unbind: func(output map[string]string, name string, val interface{}) {...},}func init() {    revel.TypeBinders[reflect.TypeOf(MyType{})] = myBinder

Revel 自带参数验证功能:

  • 提供验证上下文,用来收集和管理验证错误(通过key 和 message)。
  • 辅助函数用来检查数据,并把错误信息带到上下文中。
  • 一个模板函数,从验证上下文通过key获取错误信息。

示例应用程序提供了一些深入理解参数验证的例子。

内联错误消息

下面演示使用内联错误消息验证字段

func (c MyApp) SaveUser(username string) revel.Result {    // Username (required) 至少 4 - 15 个字符.    c.Validation.Required(username)    c.Validation.MaxSize(username, 15)    c.Validation.MinSize(username, 4)    c.Validation.Match(username, regexp.MustCompile("^w*$"))    if c.Validation.HasErrors() {        // 在flash上下文中保存验证错误并重定向        c.Validation.Keep()        c.FlashParams()        return c.Redirect(Hotels.Settings)    }    // All the data checked out!    ...}
  1. username字段验证条件 (Required必填, MinSize最小长度, MaxSize最大长度, Match匹配一个正则表达式).
  2. 每个验证条件返回一个 ValidationResult,如果验证没有通过,验证结果会被保存到验证上下文中。
  3. 作为构建应用程序的一部分,Revel记录被验证的变量的名称,并使用它作为在验证上下文的默认key(之后可以通过key获取验证错误消息)。
  4. Validation.HasErrors() 如果验证没有通过,返回 true
  5. Validation.Keep() 告诉 Revel 序列化 验证错误消息到 Flash cookie中.
  6. Revel 重定向到 Hotels.Settings 方法.

Hotels.Settings 方法渲染一个模板:

{{/* app/views/Hotels/Settings.html */}}...{{if .errors}}Please fix errors marked below!{{end}}...<p class="{{if .errors.username}}error{{end}}">    Username:    <input name="username" value="{{.flash.username}}"/>    <span class="error">{{.errors.username.Message}}</span></p>

它做了三件事:

  1. 检查 errors map 中是否存在key为 username的错误字段.
  2. 带回 username的字段值
  3. 在input下面显示错误消息(如果没有指定验证字段出错后的错误消息,则会显示验证函数默认的错误消息)

注意:模板函数 field 使用了验证错误框架,使模板的编写变得更加方便。

置顶的错误消息

如果错误消息都显示在一个地方,模板就变得简单了 (比如,放到页面顶部的一个红色的盒子中.)

下面的例子与上面有两个不同:

  1. 我们为字段指定了一个验证错误消息 Message,而不是使用验证函数默认的错误信息
  2. 我们在页面的上方打印所有的错误消息

代码如下:

func (c MyApp) SaveUser(username string) revel.Result {    // Username (必填) 至少 4 - 15 字符.    c.Validation.Required(username).Message("Please enter a username")    c.Validation.MaxSize(username, 15).Message("Username must be at most 15 characters long")    c.Validation.MinSize(username, 4).Message("Username must be at least 4 characters long")    c.Validation.Match(username, regexp.MustCompile("^w*$")).Message("Username must be all letters")    if c.Validation.HasErrors() {        // 保存错误信息到 flash 上下文中并重定向        c.Validation.Keep()        c.FlashParams()        return c.Redirect(Hotels.Settings)    }    // All the data checked out!    ...}

模板如下:

{{/* app/views/Hotels/Settings.html */}}...{{if .errors}}<div class="error">    <ul>    {{range .errors}}        <li> {{.Message}}</li>    {{end}}    </ul></div>{{end}}...

Revel 支持两种 基于 cookie 存储机制

// 一个签名 cookie (不超过4kb).// 限制: Keys may not have a colon in them.type Session map[string]string// 在每个请求中,Flash 获取并重写cookie。// 它允许数据每次跨越存储到一个页面。It allows data to be stored across one page at a time.// 它通常用来实现成功或错误消息。// 比如: Post/Redirect/Get : http://en.wikipedia.org/wiki/Post/Redirect/Gettype Flash struct {    Data, Out map[string]string}

Session

Revel的 “session” 是一个加密签名存储的字符串 map。

影响如下:

  • 大小不超过 4kb。
  • 所有数据被保存为一个序列化的字符串。
  • 用户可以查看、修改所有数据 (未加密)。

session cookie 的默认过期时间是浏览器关闭。 可以在app.config修改session.expires配置来指定一个有效期时间。格式是time.ParseDuration.

Flash

Flash 支持单独使用字符串存储。这对于实现 Post/Redirect/Get 模式是很有用的, 或临时显示 “操作成功!” 或 “操作失败!” 消息。

下面是一个例子:

// 一个设置页面func (c App) ShowSettings() revel.Result {    return c.Render()}// 处理页面提交数据func (c App) SaveSettings(setting string) revel.Result {    // 验证用户输入    c.Validation.Required(setting)    if c.Validation.HasErrors() {        // 设置被带回的flash cookie错误信息        c.Flash.Error("Settings invalid!")        // 在flash cookie中保存验证错误        c.Validation.Keep()        // 复制所有给定的参数(URL, Form, Multipart)到flash cookie        c.FlashParams()        return c.Redirect(App.ShowSettings)    }    saveSetting(setting)    // 设置flash cookie成功消息    c.Flash.Success("Settings saved!")    return c.Redirect(App.ShowSettings)}

例子主要功能如下:

  1. 用户获取设置页面。
  2. 用户提交一个表单 (POST)。
  3. 应用程序处理用户请求,保存错误或成功的消息到flash中,然后重定向设置页面 (REDIRECT)。
  4. 用户获取设置页面, 显示flash中的消息。 (GET)

主要使用了两个函数:

  1. Flash.Success(message string) 是 Flash.Out["success"] = message简写方式
  2. Flash.Error(message string) 是 Flash.Out["error"] = message简写方式

在模板中使用key来获取Flash 消息。例如, 通过简写函数获取成功和错误消息:

{{.flash.success}}{{.flash.error}}

控制器方法必须返回一个revel.Result, 用来处理响应结果,其接口定义如下:

type Result interface {    Apply(req *Request, resp *Response)}

revel.Controller 使用以下方法来处理响应结果:

  • Render, RenderTemplate - 渲染模板, 传送参数.
  • RenderJson, RenderXml - 序列化结构体到 json 或 xml.
  • RenderText - 返回一个明文响应.
  • Redirect - 重定向到一个控制器方法 或 URL
  • RenderFile - 返回一个文件, 一般用于响应文件下载.
  • RenderError - 渲染 errors/500.html 模板来返回一个 500 错误.
  • NotFound - 渲染 errors/404.html 模板来返回一个 404 错误.
  • Todo - 返回一个存根响应(500)

此外,开发者可以自定义revel.Result.

设置状态码/内容类型

每个内建的 Result 都有一个默认的状态码和内容类型,简单的设置相关属性就可以修改这些默认值:

func (c App) Action() revel.Result {    c.Response.Status = http.StatusTeapot    c.Response.ContentType = "application/dishware"    return c.Render()}

渲染

控制器渲染方法 (e.g. “Controller.Action”), mvc.Controller.Render 做了两件事:

  1. 添加参数到控制器的 RenderArgs 中, 使用变量的名字作为字典的key.
  2. 使用传送的参数渲染模板 “views/Controller/Action.html”。

如果不成功 (比如,没有找到模板), 将返回 ErrorResult.

这允许开发人员编写:

func (c MyApp) Action() revel.Result {    myValue := calculateValue()    return c.Render(myValue)}

在模板中引用变量 “myValue”。这通常比构建一个明确的map更方便,因为在许多情况下,数据将被作为局部变量处理。

注意: Revel 通过调用的控制器方法名来确定使用哪个模板,查找参数名。因此, c.Render() 只可称为由操作。

RenderJson / RenderXml

应用程序可以调用 RenderJson 或 RenderXml 并传送任意类型的变量 (通常是一个 struct). Revel 会使用 json.Marshal orxml.Marshal对变量进行序列化操作.

如果 app.conf 配置文件中 results.pretty=true, 将使用 MarshalIndent 进行序列化, 生成漂亮的缩进,方便使用。

重定向

一个辅助函数是用于生成重定向。它有两种方式使用:

  1. 重定向到一个控制器方法(不带参数):
    return c.Redirect(Hotels.Settings)

这种形式是非常有用的,因为它提供了一定程度的路由类型安全性和独立性(自动生成URL)。

  1. 重定向到一个格式化字符串:
    return c.Redirect("/hotels/%d/settings", hotelId)

通常用来传送参数.

它返回 302 (临时重定向) 状态码.

添加你自己的 Result

下面是一个添加简单结果的例子。

创建此类型:

type Html stringfunc (r Html) Apply(req *Request, resp *Response) {    resp.WriteHeader(http.StatusOK, "text/html")    resp.Out.Write([]byte(r))}

在一个控制器方法中使用它:

func (c *App) Action() revel.Result {    return Html("<html><body>Hello World</body></html>")}

状态码

每一个Result 都会设置一个默认的状态码,你也可以重新设置默认的状态代码:

func (c *App) CreateEntity() revel.Result {    c.Response.Status = 201    return c.Render()}

Revel 模块介绍

模块是一些包,可以集成到Revel程序中。Revel允许多个Revel程序(或第三方代码)共享控制器、模板、资源和其他代码。

模块中文件的布局应当与Revel应用程序文件结构一致。“托管”应用程序会按以下方式将它们合并:

  1. module/app/views 的所有模板,会被添加到模板加载器的搜索路径中
  2. module/app/controllers 的所有控制器, 将被视为你的应用程序中的控制器。
  3. 资源文件通过 Static.ServeModule("modulename","public") 提供
  4. 路由通过 module:modulename 被添加到你的程序中

启用一个模块

为了将模块添加到您的应用程序,需要在app.conf 中添加一行配置:

module.mymodulename = go/import/path/to/module

如果导入路径为空,将禁用模块:

module.mymodulename =

举个栗子, 启用测试运行模块:

module.testrunner = github.com/revel/revel/modules/testrunner

Revel提供了一个测试框架,可以很容易地编写和运行针对您的应用程序的功能测试。

应用程序带有一个简单的测试骨架以便快速上手测试。

Revel 测试框架概要

测试代码保存在测试目录中:

corp/myapp    app/    conf/    public/    tests/    <----

一个简单的测试如下所示:

type AppTest struct {  revel.TestSuite}func (t *AppTest) Before() {    println("Set up")}func (t *AppTest) TestThatIndexPageWorks() {    t.Get("/")    t.AssertOk()    t.AssertContentType("text/html")}func (t *AppTest) After() {    println("Tear down")}

上面的例子中展示了:

  • 测试套件是嵌入revel.TestSuite的一个struct
  • Before() 、 After() 在每个测试方法之前和之后被调用,如果有的话。
  • revel.TestSuite 帮助发出请求到你的应用程序,和对响应的断言。
  • 如果一个断言失败,产生了恐慌,将被测试工具捕获。

你可以用两种方式运行这个测试:

  • 交互方式,Web浏览器,开发过程中非常有用。
  • 非交互方式,命令行,对于持续构建整合有用。

开发一个测试套件

要创建自己的测试套件,需要定义一个嵌入了revel.TestSuite类型的struct, 它提供了一个HTTP客户端和一些辅助方法发出请求到应用程序。

type TestSuite struct {    Client       *http.Client    Response     *http.Response    ResponseBody []byte}// 一些请求方法func (t *TestSuite) Get(path string)func (t *TestSuite) Post(path string, contentType string, reader io.Reader)func (t *TestSuite) PostForm(path string, data url.Values)func (t *TestSuite) MakeRequest(req *http.Request)// 一些断言方法func (t *TestSuite) AssertOk()func (t *TestSuite) AssertContentType(contentType string)func (t *TestSuite) Assert(exp bool)func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{})

参考godoc

所有的请求方法类似:

  1. 接受一个路径 (比如 /users/)
  2. 向应用程序服务器发出一个请求
  3. 存储 Response 中的成员
  4. 读取完整的响应到ResponseBody 成员中

如果开发人员希望使用一个定制的HTTP客户端,而不是默认的http.DefaultClient,应当在Before() 方法之前替换它。

断言失败后,会抛出恐慌并被测试工具捕获,并将错误列出。

运行测试套件

为了运行测试,testrunner 模块必须被激活。需要在 app.conf文件中配置:

module.testrunner = github.com/revel/revel/modules/testrunner

您还必须导入测试模块的路由,在你的 routes 文件中加入下面的内容:

module:testrunner

配置完后,测试就可以交互或非交互方式运行。

运行交互式测试

要利用 Revel 的热编译功能,交互式测试运行提供了快速编辑刷新周期。

例如,开发人员从浏览器中访问 /@tests:

然后,增加一个测试方法:

func (t AppTest) TestSomethingImportant() {    t.Get("/")    t.AssertOk()    t.AssertContentType("text/xml")}

然后,刷新浏览器,看看新的测试:

运行测试:

嗯哼,,,行不通哦,,,修改代码使用“text/html” 替换 “text/xml”类型。

    t.AssertContentType("text/html")

然后,重新运行测试:

成功啦!

运行非交互式测试

Revel 命令行工具 提供了一个 test 命令,允许在命令行中运行测试。

下面是一个示例会话:

$ revel test github.com/revel/revel/samples/booking dev~~ revel! http://revel.github.com/revel~INFO  2012/11/09 19:21:02 revel.go:237: Loaded module testrunnerOpen DBListening on port 9000...INFO  2012/11/09 19:21:06 test.go:95: Testing Booking example (github.com/revel/revel/samples/booking) in dev modeGo to /@tests to run the tests.1 test suite to run.AppTest                 PASSED        0sAll Tests Passed.

您还可以运行单个测试套件,或套件内的方法,用句点分隔参数:

$ revel test github.com/revel/revel/samples/booking dev ApplicationTest$ revel test github.com/revel/revel/samples/booking dev ApplicationTest.TestThatIndexPageWorks

在控制台测试套件只有一个简单的合格/不合格显示。更详细的结果写入到文件系统:

$ cd src/github.com/revel/revel/samples/booking$ find test-resultstest-resultstest-results/app.logtest-results/AppTest.passed.htmltest-results/result.passed

它写三点不同:

  1. 应用程序的标准输出和标准错误重定向到 app.log
  2. 每个测试套件有一个HTML文件被写入,说明测试通过或失败。
  3. 无论 result.passed 或 result.failed 被写入, 这取决于整体的成功。

对于整合持续构建测试,有两点建议:

  1. 检查返回码,0代表测试成功,否则为非0值。
  2. 测试运行后要求存在 result.success, 或禁止 result.failed存在。

注意事项

Revel 做了什么:

  • 扫描嵌入TestSuite类型 (transitively) 的源代码
  • 在生成的 main.go 文件中,为 revel.TestSuites 类型的变量设置一个列表
  • 按要求,使用反射来查找所有以“Test”开头的TestSuite类型的方法,并调用它们来运行测试。
  • 从错误或失败的断言捕获恐慌,显示错误。

当 testrunner 模块激活后,测试代码才会被构建。

开发计划

改进测试框架:

  • 固定存储测试数据。
  • 日志写入到一个文件中(而不是 stderr / stdout)也应该被重定向到 test-results/app.log

Revel 支持计划任务(异步执行), 运行在请求流程的外部。比如,更新缓存数据的周期性任务,或发送电子邮件的临时任务。

Revel 计划任务激活

该框架是一个可选模块,默认是禁用的。要将它激活,需要在配置文件中添加该模块:

module.jobs = github.com/revel/revel/modules/jobs

此外,为了访问计划任务的监控页面,需要将下面的内容添加到路由文件中:

module:jobs

这条语句将插入 /@jobs 路由

选项

有两个选项来限制计划任务。

这个例子显示了它们的默认值。

jobs.pool = 10                # 允许同时运行的任务数jobs.selfconcurrent = false   # 一个任务只允许一个实例

启动计划任务

应用程序启动时, 使用revel.OnAppStart 注册一个函数来运行一个任务。Revel 在服务启动之前,会连续启动这些任务。 请注意,此功能实际上并未使用计划任务模块,它被用来提交任务,但并不阻止服务器的启动。

func init() {    revel.OnAppStart(func() { jobs.Now(populateCache{}) })}

周期性任务

任务可以被指定在任意时间运行。使用的时间表有两个选项:

  1. 一个cron时间格式
  2. 固定时间间隔

Revel 使用 cron library 来解析时间表和任务。cron库的说明 提供了时间格式的详细描述。

计划任务通常使用 revel.OnAppStart 钩子进行注册,但也可以在以后的任何时间注册。

下面是一些例子:

import (    "github.com/revel/revel"    "github.com/revel/revel/modules/jobs/app/jobs"    "time")type ReminderEmails struct {    // 过滤}func (e ReminderEmails) Run() {    // 查询数据库    // 发送电子邮件}func init() {    revel.OnAppStart(func() {        jobs.Schedule("0 0 0 * * ?",  ReminderEmails{})        jobs.Schedule("@midnight",    ReminderEmails{})        jobs.Schedule("@every 24h",   ReminderEmails{})        jobs.Every(24 * time.Hour,    ReminderEmails{})    })}

时间表

您可以在 app.conf文件中配置时间表,并在任何地方引用。这可以为 crontab 格式提供易于重用和有用的说明。

在 app.conf定义你的时间表,:

cron.workhours_15m = 0 */15 9-17 ? * MON-FRI

使用一个cron规范指定时间表,就可以在任何地方引用它。

func init() {    revel.OnAppStart(func() {        jobs.Schedule("cron.workhours_15m", ReminderEmails{})    })}

注意: cron 时间表的名字必须以 “cron.”开头

临时任务

有时候在响应用户的一个操作时,还要处理一些事情。在这种情况下,模块可以在某个时间运行一个任务。

模块提供的唯一控制是等待多长时间运行任务。

type AppController struct { *revel.Controller }func (c AppController) Action() revel.Result {    // 处理请求    ...    // 立即发送电子邮件(异步)    jobs.Now(SendConfirmationEmail{})    // 或者,一分钟后发送电子邮件(异步)。    jobs.In(time.Minute, SendConfirmationEmail{})}

注册函数

通过使用jobs.Func类型包装一个func()函数,来注册一个任务。例如:

func sendReminderEmails() {    // 查询数据库    // 发送电子邮件}func init() {    revel.OnAppStart(func() {        jobs.Schedule("@midnight", jobs.Func(sendReminderEmails))    })}

任务状态

模块提供了一个状态页面,用来显示任务的运行状态(IDLE 或 RUNNING), 以及之前和下次运行时间。

Revel 支持四类日志信息:

  • TRACE - 调试信息
  • INFO - 一般信息
  • WARN - 警告信息
  • ERROR - 错误信息

下面是在Revel中使用日志的例子:

now := time.Now()revel.TRACE.Printf("%s", now.String())

日志记录器默认使用 go 日志.

日志记录器在 app.conf中配置。例如:

app.name = sampleapp[dev]log.trace.output = stdoutlog.info.output  = stdoutlog.warn.output  = stderrlog.error.output = stderrlog.trace.prefix = "TRACE "log.info.prefix  = "INFO  "log.trace.flags  = 10log.info.flags   = 10[prod]log.trace.output = offlog.info.output  = offlog.warn.output  = log/%(app.name)s.loglog.error.output = log/%(app.name)s.log

开发环境中:

  • 显示详细日志
  • info 或 trace信息以app.conf中定义的前缀显示信息

生产环境中:

  • info 和 trace 日志将被忽略
  • 警告 和 错误信息被写入 log/sampleapp.log 文件

根据 标记常量修改日志格式,。例如, 01:23:23 /a/b/c/d.go:23 Message 格式,使用标记 Ltime | Llongfile = 2 | 8 = 10

开发状态:

  • 如果日志目录log不存在,Revel 会自动创建日志目录。

版本控制

nathany上已经给出了Go包版本控制的许多信息。然而, 那时还没有一个包版本管理的社区标准。因此, 只能由开发者确保软件安全与可重复构建。

如果你使用Revel构建应用程序, 开发者应避免由于不兼容造成的问题。你的构建过程不应当使用go get获取Revel的主分支。

处理这种情况最简单的方法是签出Revel和所有依赖包到你的代码库中。如果你使用git, 可以把这些库作为你项目的子代码库。

或者, 试试下面链接中的软件包管理器。

Revel 部署概要

几种常见的部署方法如下:

  1. 本地编译代码,然后复制到服务器上运行
  2. 在服务器上拉取代码,然后编译、运行
  3. 使用 Heroku 进行部署

使用命令行演示互动部署 - 一般将web服务器作为守护程序运行。常用工具有:

本地编译

目标机器上不需要安装Go语言环境。Revel命令行工具 提供了package包命令,用来编译和压缩应用程序, 如下所示:

# 本地测试运行$ revel run import/path/to/app.. 测试程序 ..# 打包程序$ revel package import/path/to/app打包的文件准备好了: app.tar.gz# 复制到目标机器$ scp app.tar.gz target:/srv/# 在目标机器上运行$ ssh target$ cd /srv/$ tar xzvf app.tar.gz$ bash run.sh

如果您的本地机器与目标机器的架构相同,那么不会有什么问题。否则,你需要参考交叉编译来构建指定平台架构的程序。

增量部署

由于静态链接的二进制程序带有完整的资源文件,可能会变得相当大,所以支持增量部署。

# 构建应用程序到一个临时目录$ revel build import/path/to/app /tmp/app# 将临时目录 Rsync 到服务器上的主目录$ rsync -vaz --rsh="ssh" /tmp/app server# 连接到服务器,并重新启动应用程序...

Rsync 支持ssh完整的复制操作。例如, 下面是一个更复杂的操作:

# 一个使用自定义证书、登录名和目标目录的例子$ rsync -vaz --rsh="ssh -i .ssh/go.pem" /tmp/myapp2 ubuntu@ec2-50-16-80-4.compute-1.amazonaws.com:~/rsync

在服务器上构建

这种方法依赖你的版本控制系统来分发、更新代码。你需要在服务器上安装Go语言环境。好处是,你避免了潜在的交叉编译。

$ ssh server... 安装Go语言环境 ...... 配置存储库 ...# 进入你的应用程序所在的目录 (GOPATH环境变量), 拉取代码, 并运行。$ cd gocode/src/import/path/to/app$ git pull$ revel run import/path/to/app prod

Heroku

Revel 维护了一个 Heroku Buildpack, 允许一条命令即可部署代码,请参考 自述文件 使用说明

交叉编译

为了创建一个交叉编译环境,我们需要从源代码构建。参考 从源代码安装Go 获取更多信息。你必须正确设置 $PATH 和 $GOPATH 环境变量, 否则,如果已经有一个二进制的Go语言包存在,你会陷入严重的错误。

当Go编译器安装成功后,通过指定 GOOS 和 GOARCH 目标环境来建立交叉编译环境。参考 可用的环境变量 获取更多信息。

$ cd /path/to/goroot/src$ GOOS=linux GOARCH=amd64 ./make.bash --no-clean$ GOOS=windows GOARCH=386 ./make.bash --no-clean

在新的环境中安装Revel,然后设定目标架构,打包应用程序。

$ GOOS=linux GOARCH=amd64 revel package import/path/to/app

然后,将压缩包复制到目标平台。

Revel 配置文件概述

app.conf 是Revel程序的配置文件,它使用 goconfig 语法,类似微软的 INI 文件。

下面是个例子:

app.name=chatapp.secret=pJLzyoiDe17L36mytqC912j81PfTiolHm1veQK6Grn1En3YFdB5lvEHVTwFEaWvjhttp.addr=http.port=9000[dev]results.pretty=truewatch=truelog.trace.output = offlog.info.output  = stderrlog.warn.output  = stderrlog.error.output = stderr[prod]results.pretty=falsewatch=falselog.trace.output = offlog.info.output  = offlog.warn.output  = %(app.name)s.loglog.error.output = %(app.name)s.log

每个段是一种 运行模式。最上面的 key (不在任何段内)对所有的运行模式有效。这使得默认值在所有模式中适用,并且根据需要被重写。[prod] 段仅用于 生产 模式。

新建的Revel程序中默认定义了 dev 和 prod 模式, 你也可以自定义你需要的段。 程序启动时,根据 (命令行工具)“revel run” 提供的参数来选择运行模式。

自定义属性

开发者可以自定义key,并通过 revel.Config 变量 访问它们。这里公开了一些简单的 api

内建属性

应用程序设置

app.name

应用程序名称,用于控制台输出和开发web页。

例如:

app.name = Booking example application

默认值: 无值


app.secret

密钥用于密码操作 (revel.Sign)。Revel 在内部使用它签署session cookies。设置为空将禁用签名。

使用 revel new新建项目时,它被设置为一个随机的字符串。

例如:

app.secret = pJLzyoiDe17L36mytqC912j81PfTiolHm1veQK6Grn1En3YFdB5lvEHVTwFEaWvj

默认值: 无值

HTTP settings

http.port

监听端口

例如:

http.port = 9000

http.addr

监听ip地址

Linux中, 空字符串代表通配符 – Windows中, 空字符串被转换为 "localhost"

默认值: ””


harness.port

Specifies the port for the application to listen on, when run by the harness. For example, when the harness is running, it will listen on http.port, run the application on harness.port, and reverse-proxy requests. Without the harness, the application listens on http.port directly.

默认情况下,会选择一个随​​机的空闲端口。这仅仅是必要的,由该程序限制插座访问的环境中运行时设置。By default, a random free port will be chosen. This is only necessary to set when running in an environment that restricts socket access by the program.

Default: 0


http.ssl

如果为真, Revel Web服务器将自行配置为接受SSL连接。这需要一个 X509 证书和一个 key 文件。

默认值: false

http.sslcert

指定 X509 证书文件的路径

默认值: ””

http.sslkey

指定 X509 证书 key的路径

默认值: ””

响应结果

results.chunked

确定模板渲染时是否使用 分块编码。分块编码可以减少发送到客户端的第一个字节的时间(在整个模板已经完全呈现数据之前)。

默认值: false


results.pretty

配置 RenderXml 和 RenderJson 生成缩进格式的 XML/JSON. 例如:

results.pretty = true

默认值: false

国际化 (i18n)

i18n.default_language

为消息翻译指定默认​​的语言,如果客户端请求的语言环境未确认。如果不指定,则返回一个虚拟的信息。

例如:

i18n.default_language = en

默认值: ””


i18n.cookie

指定存储用户语言环境的cookie名称

默认值: “%(cookie.prefix)_LANG” (参考 cookie.prefix)

监视

Revel 监视项目改动,并支持几种类型文件的热重载。启用监视:

watch = true

如果为假, 禁用监视, 并忽略其他相关的监视配置 watch.* (适用于生产环境)

默认值: true


watch.templates

如果为真, Revel 监视模板变化,必要时重新加载他们。

默认值: true


watch.routes

如果为真, Revel 监视 routes 文件的变化,必要时重新加载。

默认值: true


watch.code

如果为真, Revel 监视Go代码改动,必要时重新编译代码(作为反向代理运行)。

app/ 目录(包括子目录)下的代码都被监视。

默认值: true

Cookies

Revel 组件默认使用下面的 cookies:

  • REVEL_SESSION
  • REVEL_LANG
  • REVEL_FLASH
  • REVEL_ERRORS

cookie.prefix

Revel 使用这个属性作为 Revel-produced cookies前缀。这样可以在同一台主机上运行多个REVEL应用程序。

例如,

cookie.prefix = MY

则对应的 cookie 名称如下:

  • MY_SESSION
  • MY_LANG
  • MY_FLASH
  • MY_ERRORS

默认值: “REVEL”

Session

session.expires

Revel 使用这个属性设置session cookie的有效期。 Revel 使用 ParseDuration 解析字符串,默认值是 30 天。也可以设置为会话结束时过期。 请注意,客户端的行为依赖于浏览器的设置,所以结果并不总是保证。

模板

template.delimiters

指定模板左右分隔符
必须这样指定分隔符 “左分隔符 右分隔符”

默认值: “{{ }}”

格式化

format.date

指定默认的日期格式,Revel在两个地方使用它:

  • 绑定日期参数到 time.Time (参考 binding)
  • 在模板中使用 date 模板函数输出日期 (参考 模板函数)

默认值: “2006-01-02”


format.datetime

指定默认的日期时间格式,Revel在两个地方使用它:

  • 绑定日期参数到 time.Time (参考 binding)
  • 在模板中使用 datetime 模板函数输出日期 (参考 模板函数)

默认值: “2006-01-02 15:04”

数据库

db.import

指定DB模块的 database/sql 驱动程序导入路径。

默认值: ””


db.driver

指定 database/sql 驱动程序名称 (在sql.Open中使用).

默认值: ””


db.spec

指定 database/sql 数据源名称 (在 sql.Open中使用).

默认值: ””

构建

build.tags

Build tags 构建程序的时候使用。

默认值: ””

日志

TODO

缓存

cache 模块是一个简单的堆或分布式缓存接口

cache.expires

设置缓存过期时间。在程序中调用者使用常量cache.DEFAULT获取。

它是接受一个time.ParseDuration 字符串。

(目前还不能指定默认值为 FOREVER)

默认值: “1h” (1 小时)


cache.memcached

如果为真, 缓存模块使用 memcached 来代替内存缓存。

默认值: false


cache.hosts

一个逗号分隔的 memcached 主机列表。缓存条目使用确定的主机名缓存key自动分片到可用的主机中。主机可能会多次列出,以增加共享的缓存空间。

默认值: ””

计划任务

计划任务 模块允许你运行计划任务或者临时任务

时间表

时间表可以通过key来配置。

cron.schedulename = @hourly

时间表的计划时间可以在执行器中提交任务时使用。例如:

jobs.Schedule("cron.schedulename", job)

jobs.pool

允许同时允许的任务数量。例如:

jobs.pool = 4

如果为 0, 则没有数量限制

默认值: 10


jobs.selfconcurrent

如果为真, 允许一个任务运行,即使是该任务的实例仍在进行中。

默认值: false

模块

模块 通过指定导入路径将模块添加到应用程序中。例如:

module.testrunner = github.com/revel/revel/modules/testrunner

开发计划

  • 允许使用命令行参数配置值或以其他方式在命令行中指定值。

Build and Run

为了使用Revel,必须构建Revel命令行工具:

$ go get github.com/revel/revel/revel

现在运行它:

$ bin/revel~~ revel! http://revel.github.com/revel~usage: revel command [arguments]The commands are:    new         create a skeleton Revel application    run         run a Revel application    build       build a Revel application (e.g. for deployment)    package     package a Revel application (e.g. for deployment)    clean       clean a Revel application's temp files    test        run all tests from the command-lineUse "revel help [command]" for more information.

关于每条命令的含义,请参阅该工具的内置帮助功能。

如何将已有的http.Handlers整合到Revel中?

概念图中, http.Handler 用于处理用户的请求。Revel的处理是非常简单的,它只是创建控制器实例,并将请求传递给过滤器链。

应用程序可以通过重写默认的处理程序整合现有http.Handlers:

func installHandlers() {    var (        serveMux     = http.NewServeMux()        revelHandler = revel.Server.Handler    )    serveMux.Handle("/",     revelHandler)    serveMux.Handle("/path", myHandler)    revel.Server.Handler = serveMux}func init() {    revel.OnAppStart(installHandlers)}

拦截器、过滤器和模块之间是什么关系?

  1. 模块是可以插入到程序中的包。他们可以在多个Revel程序(或第三方源)中共享控制器、视图、资源和其他代码。

  2. 过滤器是可挂接到请求处理管道的函数。他们一般作为一个整体处理技术在应用程序中使用,来垂直分隔应用程序逻辑。

  3. 拦截器是封装数据和行为一种方便的方式,因为嵌入类型导入它的拦截器和字段。这使得拦截器可以很好的处理一些事情,比如验证登录cookie并保存这些信息到一个字段。拦截器可以应用到一个或多个控制器。