=encoding UTF-8

=head1 NAME

OpenResty::Spec::REST_cn - OpenResty REST 协议白皮书（草案）

=head1 AUTHOR

Agent Zhang (章亦春) <agentzh@yahoo.cn>
Carrie Zhang (张皛珏)

=head1 VERSION

    CREATED:            Nov 19, 2007
    LAST MODIFIED:      Jul 18, 2008
    VERSION:            0.20

=head1 LICENSE

  Copyright (c)  2007  Yahoo! China (中国雅虎公司).
  Permission is granted to copy, distribute and/or modify this document
  under the terms of the GNU Free Documentation License, Version 1.2
  or any later version published by the Free Software Foundation;
  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
  Texts. A copy of the license can be found at

    http://www.gnu.org/licenses/fdl.html

=head1 DESCRIPTION

本文定义了 OpenResty 基于 REST 风格的 web service API 协议组。

=head2 DESIGN GOALS

=over

=item *

确保对于简单的需求，API 足够简单；同时又能满足很复杂的高级需求。

"Make simple things easy, hard things possible" -- Larry Wall

API 应能映射到绝大部分常见的 SQL 请求语句 ，但应能阻止 SQL injection 的发生。

=item *

API 应该足够直观和友好，应尽量做到 DWIM (Do What I mean).

=item *

确保 API 能够在绝大多数支持 HTTP 1.1 和 cookie 的环境中使用。包括但不限于网页中的 Javascript, 应用程序中的 Perl, C/C++, Java, C#, VB，
以及纯命令行工具 wget 和 curl 等等。

普通网页中的 Javascript 代码应能通过 AJAX 跨域方式访问 100% 的 API.

=item *

来自客户的 API 访问请求应保持无状态。单个请求中应包含所有信息（除了用户身分认证可以存储在 cookie 中供反复使用以外）。

=item *

来自服务器的数据格式（XML，JSON，YAML）应能由客户自由控制，同时客户能指定编码(charset).

=item *

API 在形式上必须保持统一和一致。同时单次返回的结果中应包含足够的导航信息，帮助用户进行后续请求，以取得与之相关的其他数据。

=item *

出于安全性方面的考虑，数据存储应采用类似 wiki 和 Subversion 版本控制方式，应提供接口允许用户拄撤销和恢复临近一段时间的操作或数据.

=item *

API 应支持自省能力。用户可以通过 API 获得有关 Models, Actions, 和 API 本身的帮助信息.

=back

=head2 DESIGN BACKGROUND

本 API 的设计基于美国 Best Practical 公司的 Jifty 框架中所包含的 REST Web Service 接口设计 ( L<http://search.cpan.org/perldoc?Jifty::Manual::TutorialRest> )。

=head1 PROTOCOL BASIS

我们的 API 基于最基本的 HTTP 1.1/1.0 协议。特别地，我们充分利用了 GET, POST, PUT, DELETE 这几种基本的 HTTP 方法来简化我们的 API。

在本文中，除非特别说明，将总是假设使用 C<http://> 模式。

=head1 HTTP METHODS

本接口使用下列 HTTP 1.1 方法:

=over

=item GET

一般用于查询和读取操作，类似 SQL 语言中的 C<select> 语句。

=item POST

一般用于新建对象和插入数据，类似 SQL 语言中的 C<create> 语句和
C<insert into> 语句.

=item PUT

一般用于修改对象的属性和已有的数据记录。类似 SQL 语言中的
C<update> 和 C<alter> 语句.

=item DELETE

一般用于删除对象和已有的数据记录. 类似 SQL 语言中的 C<delete>
和 C<drop> 语句。

=item HEAD

XXX not defined yet

=back

由于一些防火墙阻止 PUT 和 DELETE 请求，而且对于跨域 AJAX 调用而言，
DELETE 和 PUT 也较难实现，OpenResty 为 PUT 和 DELETE 提供了相应的变形接口。
例如

    PUT /=/model/Foo
    <content>

等价于下面这个 POST 请求：

    POST /=/put/model/Foo
    <content>

类似地，下面这个 DELETE 请求

    DELETE /=/model/~

等价于下面这个 GET 请求：

    GET /=/delete/model/~

为方便起见，POST 请求也具有对应的 GET 变形：

    GET /=/post/...?_data=<content>

等价于

    POST /=/...
    <content>

=head1 URL SYNTAX

所有的 URL 都是大小写敏感的。

=head2 The leading C</=/>

为使我们的 API 能够在 URL 上复用已有的域名，如 C<www.yahoo.cn>，同时避免污染已有的 URL 名字空间，所有的 URL 都以 C</=/> 起始。

如果域名不存在与常规网页 URL 冲突的风险，前导 C</=/> 可省略，或替换为其他形式，比如 C</webservice/> 或者 C</~/> 之类。

在本文中，将总是使用 C</=/> 前导，以强调这些 URL 是特殊的（从某种意义上说），并保持一致性。

=head2 Help(TODO)

    GET /=/help

[猜想：该命令可以返回一个无结构的纯文本帮助，或者是有结构的帮助目录列表。]

=head2 Version

    GET /=/version

返回当前 OpenResty 的基本配置，版本号以及版权信息。一个典型的输出为：

 "OpenResty 0.3.10 (revision 1695) with the PgFarm (op901000) backend.\n
  Copyright (c) 2007-2008 by Yahoo! China EEEE Works, Alibaba Inc.\n"

=head2 Login

    POST /=/login

使用 MD5 后的口令。

一种方式是： GET /=/login/<user_name>/<password>

这里的 C<< <password> >> 为口令 MD5 后的形式。

典型的输出为

    {
        "success":1,
        "session":"B9405E32-522E-52DE-A5DF-493F0B05A219",
        "account":"agentzh",
        "role":"Admin"
    }

登录获取的 session ID 将被存储于 cookie 中，供后续请求使用。

未来 OAuth 支持可能也会认真考虑：

L<http://oauth.net/>

=head3 角色登录

OpenResty 支持所谓的“角色”概念。一个 OpenResty 用户在注册时可以定义若干个“角色”,每个“角色”的权限各不相同，而都可以单独登录，并拥有不同的密码。

例如注册用户 C<marry> 可以拥有 C<Admin>, C<Reader>, C<User> 等多种角色，系统默认初始化两个角色：C<Admin> 和 C<Public>，这两个角色是不能删除的，它们的基本属性也不能修改。

登录的实体是用户名。一般由帐户名和角色名两部分组成。每个角色都可以单独登录。当 C<marry> 帐户的 Reader 角色登录时，使用的用户名为 C<marry.Reader>。

事实上，当 C<marry> 直接作为用户名使用时，就相当于以 C<marry.Admin> 用户名登录，而 C<Admin> 角色拥有最高的权限。

C<Admin> 的 ACL 规则是只读的，而 C<Public> 的 ACL 规则集是可写的.

一个例子是：

    GET /=/login/marry.Reader/myPassword

这里的 C<myPassword> 是经过一遍 MD5 之后的形式。

有关角色的更多信息，请参见 L</Roles> 一节。

=head2 Models

模型是一种抽象的数据库表格。它一般直接映射到数据库的物理表（未来亦可能映射到虚拟的存储结构，如果电子信箱的 inbox.)

模型按创建者，可以分为内置和用户自定义两种类型。

所有与 Model 相关的接口，其 URL 都满足下列几种形式：

    /=/model                                            操纵模型列表
    /=/model/<model_name>                               操纵指定的模型，名为 <model_name>
    /=/model/<model_name>/<column_name>                 操纵指定模型 <model_name> 的指定列<column_name>
    /=/model/<model_name>/<column_name>/<column_value>  操纵指定模型<model_name>中的数据记录，
                                                        并由 <column_name> 和 <column_value> 来定位

=head3 Create Models

    POST /=/model/MyNewModel

新创建的 Model 的 schema 在 POST 的 content body 中通过对应格式( 比如 JSON )的 schema 说明.

一个例子是：

    POST /=/model/Bookmark

I<POST body>

    {
        "name": "Bookmark",
        "description": "我的书签",
        "columns":
          [
            { "name": "url", "type": "text", "label": "书签网址" },
            { "name": "title", "type": "text", "label": "书签标题" },
            { "name": "description", "type": "text", "label": "书签描述" }
          ]
    }

一次 POST 请求只能指定创建单个 Model. Model 的声明包含两部分，一是模型名，即 C<name> 字段，一是列声明，即 C<columns> 字段。

C<columns> 字段可以为一空数组，或者完全省略，此时模型中没有任何可用列。用户可以稍后通过 C<< POST /=/model/<model name>/<column name> >> 来添加新列。

当 C<columns> 字段为空时，服务器会返回一条警告信息，例如：

    {"success":1,"warning":"No 'columns' specified for model \"Foo\"."}

每个模型必须提供一个非空的 C<description> 属性，同时模型各列的定义必须包含 C<label> 这一属性，而且必须为非空。

请求的 POST 内容中可以不指定模型的 C<name> 属性，因为它已经出现在了请求的 URL 中，如这里的 "Bookmark". 如果用户在 JSON 数据中也指定了 C<name>，则会被忽略。

模型名必须以字母开头，后跟若干字母，下划线或数字。推荐模型名总以大写字母开头，比如 C<Bookmark>, C<MyMusic> 等等。

列名必须以字母开头，后跟若干字母，下划线或数字，与模型名的命名规则相似。但不同的是，推荐列名总是由小写字母开头，例如 C<book_name>, C<gender>, 等等。

模型名和列名都是大小写敏感的，所以 C<Bookmark> 和 C<bookmark> 被认为是两个不同的 Model.

任何模型都将拥有一个默认列，名为 C<id>，用于在一个模型中唯一地标识某一条数据记录（或者说数据行）。若用户自己在模型中指定了 C<id> 列，则服务器会将之忽略，并将给出一条警告信息。值得一提的是，C<Id>, C<ID>, 和 C<iD> 也是保留的列名。

默认情况下，URL 及请求数据中的非 ASCII 字符都按 UTF-8 编码处理。如若需要使用其他编码，如 GBK, Big5, 和 Latin1 的话，需要显式地通过 C<_charset> 参数指定，例如：

    POST /=/model/Bookmark?_charset=GBK

任何情况下，URL 和请求数据中的编码必须一致，比如必须同为 UTF-8，或者同为 GBK.

如果无法确定编码应该是什么，可以使用 C<_charset=guessing> 或者 C<_charset=guess>，服务器端会根据传入编码进行判断。

实际定义一个 Model 时，服务器会根据预定义的一些约束条件对用户所创建的字段数目和类型进行限制。而对于某些特殊情况，具体的约束也可能会随实现的不同而有所区别。


B<TODO:> 定义模型时，可以通过与 column 同级的 unique 参数，来定义此 Model 中需要建立唯一约束的列。也可以在 column 中写在各个列定义里. 当 unique 值为true 时该列数据有唯一约束；否则此列数据没有唯一约束。不做 unique 的各列，默认无唯一约束。

当模型已存在时，服务器返回出错信息，例如：

    { "success":0, "error":"Model \"Bookmark\" already exists." }

详情请见 L</ERROR HANDLING> 一节。

=head3 Alter Models

=over

=item Change Model Name

可以通过下面的接口修改已有模型的名字：

    PUT /=/model/<old_name>
    { "name": "<new_name" }

一个例子是：

    PUT /=/model/Bookmark
    { "name": "MyBookmark" }

如果新的模型名与已存在的另一个 Model 同名，则服务器会报错，例如：

    { "success":0, "error":"Model \"MyBookmark\" already exists." }

=item Change Model Description

可以通过下面的接口修改已有模型的描述：

    PUT /=/model/<model_name>
    { "descripton": "<new_description" }

修改模型的名字和描述可以放在单次 HTTP 请求中，例如下面这个例子：

    PUT /=/model/Bookmark
    { "name": "MyBookmark", "description": "这可是我的书签哦！" }

=item Change Model Columns

如果想对已有模型中模一列的名字、类型，或标签做修改，可以使用如下接口：

    PUT /=/model/Bookmark/title
    { "name": "bookmark_name", "type": "varchar(20)", "label": "书签名" }

可以同时修改 Model 中某列的一个或多个属性。

例如, C<Bookmark> 这个 Model 中有一个名称为 C<title> 的列，想要修改它的 C<name> 和 C<label> 属性，而不修改类型（type）的话。

    PUT /=/model/Bookmark/title
    { "name": "bookmark_"name"", "label": "书签名" }

=item Add Model Columns

可以通过下面的接口添加模型的列：

    POST /=/model/<model_"name">/<new_column_"name">
    { "type": "<type>", "label": "<label>" }

一个例子是：

    POST /=/model/Bookmark/comment
    { "type": "text", "label": "书签评论" }

=item Delete Model Columns

可以通过下面的接口删除模型的列：

    DELETE /=/model/<model_name>/<column_name>

一个例子是：

    DELETE /=/model/Bookmark/comment

删除所有的列可以使用下面的命令：

    DELETE /=/model/Bookmark/~

值得提醒的是 C<id> 列是保留的，故它不会被删除。

=back

=head3 Read Models

=over

=item 显示模型列表

    GET /=/model

返回的数据为一无序列表，其内容为用户所有可见的模型的名字，以及对应的 URL。

一个 JSON 格式的例子为：

    GET /=/model

典型的返回内容为：

    [
        { "description": "My favorite bookmark", "name": "Bookmark", "src": "/=/model/Bookmark" },
        { "description": "My favorite music", type: "model", "name": "Music", "src": "/=/model/Music" },
        { "description": "My frequently accessed blog", "name": "Blog", "src": "/=/model/Blog" },
    ]

有关结果格式的讨论，请见 L</DATA FORMAT> 一节.

=item 显示指定模型的定义

值得指出的是，模型的定义并不完全等同于真实的数据库物理表的定义. 模型是一种抽象的概念。

在模型名的命名上，一般取为首字母大写的名词单词，如 C<Bookmark>, C<Book>, C<Music> 等等，而不像数据库表格一般取成 C<bookmarks>, C<books>, C<music> 这样的形式。

    GET /=/model/<model name>

该 URL 将返回模型名为 C<< <model name> >> 的定义。

一个 JSON 的例子如下：

    GET /=/model/Bookmark

返回为：

    {
      "name": "Bookmark",
      "description": "My favorite bookmark",
      "columns":
        [
          { "name": "id", "type": "serial", "default": null },
          { "name": "title", "type": "text", "default": "No title" },
          { "name": "url", "type": "text", "default": null },
          { "name": "description", "type": "text", "default": null },
          { "name": "created", "type": "timestamp (0) with time zone", "default": ["now()"] }
        ]
    }

=item 显示指定的列的定义

    GET /=/model/<model name>/<column name>

一个例子是：

    GET /=/model/Bookmark/title

返回

    { "name": "title", "type": "text", "default": "No title" }

=back

=head3 Delete Models

=over

=item 删除指定的 Model

    DELETE /=/model/<model name>

该命令将删除名为 C<< <model_name> >> 的模型。

在功能上相当于下面这条 SQL 语句：

    drop table <model name>

=item 删除所有的 Model

    DELETE /=/model

或者

    DELETE /=/model/~

=back

=head3 Read records

=over

=item 显示指定字段的记录列表

    GET /=/model/<model name>/<column name>/~


逻辑上相当于下面这行 SQL 语句：

    select <column name>
    from <model name>

注意此处以及下文所给出的 SQL 语句也并非真实的 SQL，用来解释 API URL 的语义。

=item 显示指定字段的指定取值的记录列表

    GET /=/model/<model name>/<column name>/<column value>

其功能上相当于下面这条 SQL 语句：

    select *
    from <model name>
    where <column name>=<column value>

一个具体的体子是：

    GET /=/model/Bookmark/id/1

相当于

    select *
    from Bookmark
    where id = 1

服务器返回的结果类似于：

    [
        { "id": 1, "title": "Revision 34: /trunk", "url": "http://svn.openfoundry.com/xulapp/trunk", "description": "" }
    ]

如果 C<column value> 中含有 URL 特殊字符，例如 C<?>, C<%>, C<&> 之类，
则应该进行转义。例如 C<?> 用 C<%2E> 代替。

如果不希望用 C<=> 执行精确匹配的话，可以通过指定 C<_op> 选项来指定其他运算符，包括 C<contains>, C<gt>, C<ge>, C<lt>, C<le>, C<eq>, 与 C<ne>.

使用 C<_op> 参数的时候，只能有一个查询条件，但可以添加约束信息（如 C<_order_by> 等）。并且每次查询请求最多只能有一个 C<_op> 的查询限制。

下面是一个例子：

    GET /=/model/Bookmark/title/Yahoo?_op=contains

近似于

    select *
    from Bookmark
    where title like '%Yahoo%'

典型的返回结果如下：

    [
        { id: 56, title: "Yahoo News", url: "http://news.yahoo.com", description: "美国雅虎网站" },
        { id: 57, title: "Yahoo中国", url: "http://cn.yahoo.com", description: "阿里巴巴中国雅虎首页" }
    ]

值得提醒的是，虽然，C<_op=contains> 非常方便，但存在效率上的问题，比如很难高效地使用 DB 索引。因此，对于真实的应用，应该尽量考虑使用 tsearch2 这样的全文索引模块。OpenResty 提供了对 tsearch2 的全面支持.

=item 显示各列中出现某个值的记录

    GET /=/model/<model name>/~/<column value>

这里的星号 C<~> 是“通配符”(wildcard).

该查询相当于下面的 SQL 语句：

    select *
    from <model name>
    where <column 1> = <column value> or <column 2> = <column value> or ...

=item 显示所有记录

    GET /=/model/<model name>/~/~

值得一提的是，查询 C<< GET /=/model/<model name>/<column name>/~ >> 的效果
也是显示所有记录。

=item 扩展查询语法(TODO)

通过指定 C<_extended=1> 选项，可以启用扩展查询语法。下面是几个例子：

    GET /=/model/Bookmark/id/1,3,52..72?_extended=1

相当于下面这句 SQL 查询：

    select *
    from Bookmark
    where id = 1 or id = 3 or id between 52 and 72

又如：

    GET /=/model/Timetable/arrival_time/18:32..20:59,5:07..8:40?_extended=1

相当于下面这句 SQL 查询：

    select *
    from Timetable
    where arrival_time between '18:32' and '20:59'

可以使用通配符 C<~> 来表示没有上限或下限，例如：

    GET /=/model/Person/height/1.86..~?_extended=1

表示选取身高在 1.86 以上的 Persion，相当于下面这句 SQL:

    select *
    from Person
    where height > 1.86

如果要选取身高小于 1.65 的人，则可以这么写：

    GET /=/model/Person/height/~..1.65?_extended=1

=back

更高级的检索需求 (比如 table join 和 group by) 等等应该通过 Views 和 Actions 来完成.

=head3 Manipulate Records

=over

=item 插入记录

    POST /=/model/<model name>/~/~

该命令用于向指定模型上传若干新记录。一个例子是：

    POST /=/model/Bookmark/~/~

I<POST body>

    [
        { "title": "Yahoo News", "url": "http://news.yahoo.com", "description": "美国雅虎网站" },
        { "title": "Yahoo中国", "url": "http://cn.yahoo.com", "description": "阿里巴巴中国雅虎首页" },
        { "title": "Revision: /trunk", "url": "http://svn.openfoundry.org/xulapp/trunk", "description": "我的 XUL::App 项目" }
    ]

I<Output>

    { "success": 1, "rows_affected": 3, "last_row": "/=/model/Bookmark/id/3" }

具体的实现会对一次插入的记录数目进行限制。

=item 修改记录

    PUT /=/model/<model name>/<column name>/<column value>

修改指定记录的字段值。

一个例子是：

    PUT /=/model/Bookmark/url/yahoo?_op=contains

I<POST body>

    { "title": "My Yahoo Home", "description": "As title" }

对应的伪 SQL 为：

    update bookmarks
    set title = 'My Yahoo Home', description = 'As title'
    where url like '%yahoo%'

=item 删除记录

用于删除指定的记录

一个例子是：

    DELETE /=/model/Bookmark/url/yahoo

对应的伪 SQL 为：

    delete from bookmarks
    where url = 'yahoo'

=back

=head3 Namespace and Databases(TODO)

模型的名字可以写成名字空间修饰的形式，比如 C<Foo.Bar.Baz>. 从逻辑上讲, C<Foo> 相当于一个数据库，或者说是模型的集合。

[猜想: 模型检索时应提供名字空间级别上的搜索。]

=head2 Views

OpenResty 中的视图 (Views) 与数据库中的视图比较近似，但不同的是，它具体应用时可以通过动态传递参数来实现真正的数据库操作。比如：

    POST /=/view/MyQuery
    {
      "description": "This is my first view",
      "definition": "select * from CComment where name = $name and parent_id = $parent_id"
    }

此时便创建了一个名为 C<MyQuery> 的 OpenResty 视图对象。
该视图返回的结果是由一个带特殊变量的 RestyScript (minisql) 语句来指定的。
由 C<$> 起始的变量都是 MyQuery 这个视图的参数。例如上面这个例子中，
C<$name> 和 C<$parent_id> 都是参数变量。

于是后面可以通过这样的语法调用 C<MyQuery>:

    GET /=/view/MyQuery/name/Foo?parent_id=Blah

或者等价地：

    GET /=/view/MyQuery/parent_id/Blah?name=Foo

视图参量可以带缺省值，例如：

    POST /=/view/MyQuery
    {
      description: "This is my second view",
      definition: "select * from CComment where name = $name|'Foo' and parent_id = $parent_id|'Blah'"
    }

这样调用 C<MyQuery> 的时候如果不提供参量，也不会报错：

    GET /=/view/MyQuery/~/~

这与前面的两种 MyQuery 调用方式是等效的。

虽然视图对象的功能都可以使用 C<RunView> 这个内建 Action 来完成，但它却拥有以下优点：

=over

=item 1.

服务器可以只在创建视图的时候解析 RestyScript 语句一次，而在随后的视图调用中提高处理速度。

=item 2.

视图调用中会自动对参量进行 quote 处理，而客户无需自己在拼 minisql 串的时候自己去做。

=item 3.

方便通过 C<Role> 的 URL 规则来限制客户端的权限，从而不必直接将 RestyScript 解释器界面（即 C</=/action/RunView/~/~> ) 暴露给客户端。

=back

=head2 Actions

"动作(Action)"是一种抽象的功能实体。Action 在概念上与编程语言中的函数相近。

每一个 Action 都有一个界面，界面一般由若干个 parameters 组成。就像每一个 model
都由若干个 columns 组成一样。

Action 亦可分为内建 Action (如 C<RunAction> 和 C<RunView> ) 和用户自定义 Action 两大类.

Action 的定义是全功能的 RestyScript 语言（不同于 View 定义中只能使用 RestyScript 中的 SQL select 语句）。

Action 定义中有 SQL 的 select, update 和 delete 语句以及 HTTP 请求（并可带 JSON 作为 HTTP POST 和 PUT 请求的内容体）。

Action 的目的在于使多个操作捆绑成一个整体，而不是所有的操作都能用 sql 来表达（比如创建一个 OpenResty 的 role）， 也不是所有操作都能用 REST API 来表达，比如 delete from ... where ...，在 minisql 中允许 REST 操作的另一个有趣的 feature 是，action 可以调用其他的 action，甚至递归调用自身。

=head3 Create Actions

用户自定义的 Action 在概念上类似于数据库中存储过程，并将通过 OpenResty 自己定义的 minisql 语言中的 C<update> 和 C<delete> 语句来表达其功能.

示例：

    POST /=/action/RemoveBookmarks
    { "description": "Action for removing bookmarks",
      "parameters":[ {"name":"pattern", "label":"Pattern", "type":"literal"} ],
      "definition":
        "delete from Bookmark where url like $pattern andescription like $pattern" }

=head3 Inspect Actions

    GET /=/action/<action name>

可获得指定 action 的界面

示例：

    GET /=/action/RemoveBooks

输出为它的 minisql 定义：

    {
        "description": "Action for removing bookmarks",
        "parameters":[{"name":"pattern","label":"Pattern","type":"literal"}],
        "definition": "delete from Bookmark where url like $pattern description like pattern"
    }

=head3 Call Actions

    GET /=/action/<action name>/<parameter1>/<value1>?<parameter2>=<value2>&<parameter3>=<value3>&...

以指定的参数调用 actions. 示例：

    GET /=/action/RemoveBookmarks/pattern/Hello,world

返回结果是

    {"success":1}

对于无参数的 Action 调用时使用

    GET /=/action/<action name>/~/~

的形式。

此外还可以使用 POST 方法来调用一个 Action:

    POST /=/action/RemoveBookmarks/~/~
    {"pattern":"Hello,world"}

=head3 Remove Actions

    DELETE /=/action/<action name>

=head3 Built-in Actions

=over

=item RunView

RunView 提供了 View 定义一个解释器界面。下面是一个例子：

    POST /=/action/RunView/~/~
    "select * from Music, Bookmark where Music.url = Bookmark.url"

View 中使用的 RestyScript 代码与 SQL 的语法非常近似，不同的是它是大小写敏感的。
所有的关键字都必须为小写。在 minisql 中可以直接引用利用 C</=/model>
接口定义的 Model 名和列名.

=item RunAction

RunAction 与 RunView 类似，但它是为 Action 定义提供了一个解释器界面。例如：

    POST /=/action/RunAction/~/~
    "DELETE '/=/model?_user=agentzh&_password=blahblahblah';
    POST '/=/model/Another' {\"description\":\"a model in another account\"};
    GET '/=/model';
    GET '/=/model?_user=eeee&_password=foofoofoo'"

=back

=head2 Roles

OpenResty 通过角色 (Role) 来实现子用户和权限分配功能。
一个 OpenResty 注册用户可以拥有多个角色，她可以通过 C</=/role> 这个 URL
对她的角色进行管理，其中包括添加和删除角色，对角色权限进行分配，
指定角色的登录方式（是通过口令，验证码图片，还是匿名）。

在 OpenResty 中，角色对象是一种特殊的 Model，C</=/role> 与 C</=/model>
在接口上有许多相似之处。

每一个角色实体都被视为一个规则 Model. 所有 Model 的查询，修改，
和删除操作都同样适用于 Role.

=head3 获取角色列表

    GET /=/role

服务器返回的是一个角色列表。每一个项目描述了对应角色的名称，描述，和 URL。
例如：

  [
    { name: "Admin", description: "Administrator", src: "/=/role/Admin" },
    { name: "Public", description: "Anonymous", src: "/=/role/Public" }
    { name: "Commenter", description: "评论用户", src: "/=/role/Commenter" }
  ]

其中 C<Admin> 和 C<Public> 这两个角色是 OpenResty 用户在注册时
自动创建的，而且不能删除。用户直接使用帐户名，比如 marry 登录时，
相当于使用 C<marry.Admin> 这个用户名来登录。

C<Admin> 角色的 ACL 规则列表中拥有所有可能的模式。而 C<Public> 角色的 ACL 列表默认为空。

=head3 获取角色信息

    GET /=/role/<role name>

返回一个哈希结构，其中包括角色名，角色描述，父亲角色(TODO)，登录方式，等等字段。
一个典型的例子是：

    GET /=/role/Admin
    {
        "name":"Admin",
        "description":"Administrator",
        "login":"password",
        "columns":[
            {"label":"HTTP method","name":"method","type":"text"},
            {"label":"Resource","name":"url","type":"text"}
        ]
    }

=head3 读取权限规则

    GET /=/role/<role name>/<column>/<value>

C<< <column> >> 和 C<< <value> >> 的含义与 Model 的 URL 记法中的一致。

例如当二者同时为 C<~> 时返回所有的权限规则：

    GET /=/role/Commenter/~/~
    [
        { "method": "POST", "url": "/=/model/Comment/~/~" }
    ]

权限规则总是由两个属性构成，一是 C<method>，用于指定允许的 HTTP 方法。
一是 C<resource>，用于指定允许操纵的资源（或 URI）。

在

=head3 添加权限规则

    POST /=/role/<role name>/~/~
    [ rule1, rule2, ... ]

=head3 角色继承(TODO)

子角色永远只拥有其创建时刻父角色的 ACL 规则集的一个快照，因此子角色一旦创建之后，其 ACL 规则集便与父角色一致，但从此以后子角色可以自由地修改它自己的 ACL 规则集，并对父角色无任何影响；反之亦然：即使父角色随后发生了任何变化，对其已有的子角色也不会有任何影响？

=head2 Unsafe Operations

    POST /=/unsafe/do
    "<pg sql>"

    POST /=/unsafe/select
    "<pg sql>"

Unsafe API 提供了直接通过 Web 执行 Pg/SQL 的接口。由于这种方式危险性极大，故对于非信任帐户，Unsafe API 即使是对 Admin 角色也是关闭的。开启 Unsafe API 的权限，需要在 OpenResty 服务器的配置文件中显式地进行设定，例如：

    [frontend]
    ...
    unsafe=carrie people

这里 C<frontend.unsafe> 选项指定了 C<carrie> 和 C<people> 这两个帐户是拥有对 Unsafe API 的访问权的。

=head2 URL Parameters

=over

=item _charset

指定输入／输出数据和 URL 本身所使用的字符集。

示例：

    GET /=/model/Bookmark/title/Yahoo?_charset=GBK

=item _offset

该选项仅作用于 GET 请求返回列表的情形。它用于指定在返回的列表中跳过起始的记录的数目。例如

    GET /=/model/Foo/~/~?_offset=10

将返回第十一条及其以后的记录列表。C<_offset> 多与 C<_count> 选项一起使用，以实现分页功能。

=item _limit

=item _count

该选项仅作用于 GET 请求返回列表的情形。它用于指定返回的条目数，缺省为 OpenResty 服务器规定的返回行数的上限, 500。其最大值亦为 500。

示例：

   GET /=/model/Postbook/body/People?_count=20

C<_count> 的取值须为小于上限的正整数，否则服务器将报错。

C<_limit> 参数为 C<_count> 的一个别名。

=item _order_by

指定排序项（可指定多个，以逗号分隔). 例如：

    GET /=/model/Bookmark/title/Yahoo?_order_by=title:asc,url:desc

在语义上近似于下面这条 SQL 语句：

    select *
    from bookmarks
    where title = 'Yahoo'
    order by title asc, url desc

升降序的缺省是 SQL 标准的缺省：C<asc>。当字段中无冒号分隔的升降序声明的时候，就是升序。

=item _var

该选项仅适用于 JSON 格式，用于生成一个 JS 变量赋值语句。例如：

    GET /=/model.json?_var=foo

或

    GET /=/model?_var=foo

服务器将返回类似下面的数据：

    foo=...;

这里的 C<...> 是没有指定 C<_var> 选项时服务器返回的内容。

=item _data

该选项用于 POST 和 PUT 方法的 GET 变形接口，例如

    POST /=/model
    <content>

可以使用

    GET /=/post/model?_data=<content>

来代替（如果 C<< <content> >> 含有 URL 特殊字符，需要进行 URL 编码）。

再比如下面这个 PUT 请求

    PUT /=/model/Foo
    <content>

改用 GET 就是

    GET /=/put/model/Foo?_data=<content>

=item _user

该选项指定用户名，用户名一般由帐户名和角色色两部分组成，比如 C<marry.Admin>.

=item _password

该选项指定密码

示例

    GET /=/model/Bookmark/id.xml?_user=foo&_password=bar

这里的 C<bar> 需为密码的 MD5 形式。

=back

=head1 DATA FORMAT

服务器返回的数据格式可以由用户通过 URL 后缀来控制。

支持的格式后缀为 C<.json>, C<.xml>, 和 C<.yaml>. 它们分别对应 JSON 格式，XML/RDF 格式，和 YAML 格式。
它们的别名为 C<.js>, C<.rdf>, 和 C<.yml>.

B<TODO>: C<.xml> 和 C<.rdf> 暂未实现.

当后缀名未指定时，缺省为 C<.json>。

=head1 Content type

HTTP 响应的头部中的 C<Content-Type> 总是 C<text/plain>.

HTTP 请求头部中的 C<Content-Type> 推荐是 C<text/plain>. 如果 PUT 和 POST
请求的 C<Content-Type> 被设为 C<application/x-www-form-urlencoded>,
则 content 部分应该写作

    data=<content>

XXX specify what happens when it is "multipart/form-data"

=head1 ERROR HANDLING

当操作成功时，服务器返回的数据如果用 JSON 格式表达，一般为

    {"success":1}

如果操作虽然成功，但有警告的场合，会是：

    {"success":1,"warning":"Some warning goes here..."}

对于插入 Model 记录的操作，服务器会返回一些额外的字段，比如：

    {"success":1,"rows_affected":5,"last_row":"/=/model/Foo/id/3"}

如果操作失败，则是下面这个样子：

    {"success":0,"error":"Error message goes here..."}

=head1 GRAMMAR FOR RestyScript

View 定义中使用的 RestyScript 语言是 Action 定义中的一个子集，即类似 SQL 的 select 语句。

它们公共部分的语法定义请参见用 Haskell Parsec 表达的 parser 实现：

L<http://svn.openfoundry.org/openapi/trunk/haskell/src/RestyScript/Parser.hs>

View 定义特有的部分位于

L<http://svn.openfoundry.org/openapi/trunk/haskell/src/RestyScript/Parser/View.hs>

而 Action 特有的部分位于

L<http://svn.openfoundry.org/openapi/trunk/haskell/src/RestyScript/Parser/Action.hs>

=head1 SEE ALSO

L<OpenResty::Spec::Overview>, L<OpenResty::Tutorial::GettingStarted_cn>, L<OpenResty>.

