The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

OpenResty::Spec::REST_cn - OpenResty REST 协议白皮书(草案)

AUTHOR

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

VERSION

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

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

DESCRIPTION

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

DESIGN GOALS

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

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

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

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

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

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

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

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

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

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

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

DESIGN BACKGROUND

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

PROTOCOL BASIS

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

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

HTTP METHODS

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

GET

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

POST

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

PUT

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

DELETE

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

XXX not defined yet

由于一些防火墙阻止 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>

URL SYNTAX

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

The leading /=/

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

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

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

Help(TODO)

    GET /=/help

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

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"

Login

    POST /=/login

使用 MD5 后的口令。

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

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

典型的输出为

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

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

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

http://oauth.net/

角色登录

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

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

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

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

Admin 的 ACL 规则是只读的,而 Public 的 ACL 规则集是可写的.

一个例子是:

    GET /=/login/marry.Reader/myPassword

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

有关角色的更多信息,请参见 "Roles" 一节。

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> 来定位

Create Models

    POST /=/model/MyNewModel

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

一个例子是:

    POST /=/model/Bookmark

POST body

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

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

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

columns 字段为空时,服务器会返回一条警告信息,例如:

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

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

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

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

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

模型名和列名都是大小写敏感的,所以 Bookmarkbookmark 被认为是两个不同的 Model.

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

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

    POST /=/model/Bookmark?_charset=GBK

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

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

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

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

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

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

详情请见 "ERROR HANDLING" 一节。

Alter Models

Change Model Name

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

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

一个例子是:

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

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

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

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

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

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

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

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

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

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

例如, Bookmark 这个 Model 中有一个名称为 title 的列,想要修改它的 namelabel 属性,而不修改类型(type)的话。

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

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

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

一个例子是:

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

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

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

一个例子是:

    DELETE /=/model/Bookmark/comment

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

    DELETE /=/model/Bookmark/~

值得提醒的是 id 列是保留的,故它不会被删除。

Read Models

显示模型列表
    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" },
    ]

有关结果格式的讨论,请见 "DATA FORMAT" 一节.

显示指定模型的定义

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

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

    GET /=/model/<model name>

该 URL 将返回模型名为 <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()"] }
        ]
    }
显示指定的列的定义
    GET /=/model/<model name>/<column name>

一个例子是:

    GET /=/model/Bookmark/title

返回

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

Delete Models

删除指定的 Model
    DELETE /=/model/<model name>

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

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

    drop table <model name>
删除所有的 Model
    DELETE /=/model

或者

    DELETE /=/model/~

Read records

显示指定字段的记录列表
    GET /=/model/<model name>/<column name>/~

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

    select <column name>
    from <model name>

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

显示指定字段的指定取值的记录列表
    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": "" }
    ]

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

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

使用 _op 参数的时候,只能有一个查询条件,但可以添加约束信息(如 _order_by 等)。并且每次查询请求最多只能有一个 _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: "阿里巴巴中国雅虎首页" }
    ]

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

显示各列中出现某个值的记录
    GET /=/model/<model name>/~/<column value>

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

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

    select *
    from <model name>
    where <column 1> = <column value> or <column 2> = <column value> or ...
显示所有记录
    GET /=/model/<model name>/~/~

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

扩展查询语法(TODO)

通过指定 _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'

可以使用通配符 ~ 来表示没有上限或下限,例如:

    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

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

Manipulate Records

插入记录
    POST /=/model/<model name>/~/~

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

    POST /=/model/Bookmark/~/~

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 项目" }
    ]

Output

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

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

修改记录
    PUT /=/model/<model name>/<column name>/<column value>

修改指定记录的字段值。

一个例子是:

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

POST body

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

对应的伪 SQL 为:

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

用于删除指定的记录

一个例子是:

    DELETE /=/model/Bookmark/url/yahoo

对应的伪 SQL 为:

    delete from bookmarks
    where url = 'yahoo'

Namespace and Databases(TODO)

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

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

Views

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

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

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

于是后面可以通过这样的语法调用 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'"
    }

这样调用 MyQuery 的时候如果不提供参量,也不会报错:

    GET /=/view/MyQuery/~/~

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

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

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

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

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

Actions(TODO)

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

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

Action 亦可分为内建 Action (如 WebSearchSelect ) 和用户自定义 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,甚至递归调用自身。

Create Actions

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

示例:

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

Inspect Actions

    GET /=/action/<action name>

可获得指定 action 的界面

示例:

    GET /=/action/RemoveBooks

输出为它的 minisql 定义:

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

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"}

Remove Actions

    DELETE /=/action/<action name>

Built-in Actions(已实现)

RunView

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

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

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

RunAction

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

    POST /=/action/RunAction/~/~
    "DELETE '/=/model?_user=$TestAccount2&_password=$TestPass2';
    POST '/=/model/Another' {\"description\":\"a model in another account\"};
    GET '/=/model';
    GET '/=/model?_user=$TestAccount2&_password=$TestPass2'"

Roles

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

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

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

获取角色列表

    GET /=/role

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

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

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

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

获取角色信息

    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"}
        ]
    }

读取权限规则

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

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

例如当二者同时为 ~ 时返回所有的权限规则:

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

权限规则总是由两个属性构成,一是 method,用于指定允许的 HTTP 方法。 一是 resource,用于指定允许操纵的资源(或 URI)。

添加权限规则

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

角色继承(TODO)

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

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

这里 frontend.unsafe 选项指定了 carriepeople 这两个帐户是拥有对 Unsafe API 的访问权的。

URL Parameters

_charset

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

示例:

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

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

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

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

_limit
_count

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

示例:

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

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

_limit 参数为 _count 的一个别名。

_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 标准的缺省:asc。当字段中无冒号分隔的升降序声明的时候,就是升序。

_var

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

    GET /=/model.json?_var=foo

    GET /=/model?_var=foo

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

    foo=...;

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

_data

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

    POST /=/model
    <content>

可以使用

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

来代替(如果 <content> 含有 URL 特殊字符,需要进行 URL 编码)。

再比如下面这个 PUT 请求

    PUT /=/model/Foo
    <content>

改用 GET 就是

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

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

_password

该选项指定密码

示例

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

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

DATA FORMAT

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

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

TODO: .xml.rdf 暂未实现.

当后缀名未指定时,缺省为 .json

Content type

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

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

    data=<content>

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

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..."}

GRAMMAR FOR RestyScript

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

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

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

View 定义特有的部分位于

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

而 Action 特有的部分位于

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

SEE ALSO

OpenResty::Spec::Overview, OpenResty::Tutorial::GettingStarted_cn, OpenResty.