NAME

OpenAPI REST Protocol Specification [draft] - OpenAPI REST 协议白皮书(草案)

AUTHOR

Agent Zhang (章亦春) <agentzh@yahoo.cn>

VERSION

    CREATED:       Nov 19, 2007
    LAST MODIFIED: Dec 3, 2007
    VERSION:       0.03

DESCRIPTION

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

DESIGN GOALS

DESIGN BACKGROUND

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

PROTOCOL BASIS

我们的 API 将基于最基本的 HTTP 1.1 协议。特别地,我们将充分利用 HEAD, 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 语句。

HEAD

GET 相近,但对于返回列表型数据的读取操作而言, 仅返回第一个列表项。类似 SQL 语言中的 select top 1.

URL SYNTAX

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

The leading /=/

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

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

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

Help

    GET /=/help

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

Login

    POST /=/login

登录验证,目前有两种选择:

  1. 使用明文传输口令

    此种方法使用简单,尤其对于 wget, curl 这样的工具友好。缺点是安全性不高。

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

  2. 使用加密传输口令

    此种方法使用复杂,需要握手过程,以及加密库的支持。

    POST 请求的内容需要经过加密处理。比如:

        POST /=/login/<user_name>

    POST body

        adsfkwk3223df23245rt3423dfds2d

    这里的 POST body 中存放着经过加密的密码.

    XXX 具体的加密算法

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

[猜想: 在轻量级应用中使用明文,以降低接口使用门槛,适应普通用户。对于数据安全性高的企业级应用,使用加密传输的方法。]

OAuth 支持也应认真考虑:

http://oauth.net/

角色登录

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

例如用户 Marry 可以拥有 admin, reader, user, public 等多种角色,每个角色都可以单独登录。当 Marry 的 reader 角色登录时,使用的帐户名为 Marry.reader. 事实上,Marry 在作为帐户名使用时,就相当于以 Marry.admin 角色登录,因为 admin 角色拥有最高的权限。

一个例子是:

    GET /=/login/Marry.reader/myPassword

有关角色的更多信息,请参见 "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: "string", label: "书签网址" },
            { name: "title", type: "string", label: "书签标题" },
            { name: "description", type: "string", label: "书签描述" }
          ]
    }

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

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

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

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

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

虽然服务器会存储模型名和列名的大小字,但是内部处理时是不区分大小写的。所以并不允许 Bookmarkbookmark 这两个 Model 同时存在。

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

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

    POST /=/model/Bookmark?charset=GBK

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

具体的实现会对一个 Model 所含的字段数目和类型进行约束。

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

    { 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: "书签名" }

一次可以只修改其中的一个或者两个属性。例如:

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

如果我们并不想修改 title 列的类型(type)的话。

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

Read Models

显示模型列表
    GET /=/model

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

一个 JSON 格式的例子为:

    GET /=/model

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

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

这里的 writablereadable 属性标记了模型的读权限和写权限。读写权限是用户通过"角色" (Role) 来分配的。

显示指定模型的 schema

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

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

    GET /=/model/<model name>

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

一个 JSON 的例子如下:

    GET /=/model/Bookmark

    {
      name: "Bookmark",
      description: "My favorite bookmark",
      columns:
        [
          { name: "id", type: "serial", src: "/=/model/Bookmark/id", writable: 1, readable: 1 },
          { name: "title", type: "string", src: "/=/model/Bookmark/string", writable: 1, readable: 1 },
          { name: "url", type: "string", src: "/=/model/Bookmark/url", writable: 1, readable: 1 },
          { name: "description", type: "string", src: "/=/model/Bookmark/description", writable: 1, readable: 1 },
        ]
    }

Delete Models

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

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

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

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

Read records

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

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

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

    select <column name>
    from <model name>

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

对于超过 10 条的返回结果,API 总是返回前十条。翻页功能可通过 page 参数来控制,例如

    GET /=/model/Bookmark/title/*?page=2

    [
        { title: "XUL::App - Jifty way of doing XUL" },
        { title: "中文搜索 同时用三家引擎 searchall" },
        { title: "Revision 32: trunk/"},
    ]
显示指定字段的指定取值的记录列表
    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: "" }
    ]

如果不希望用 = 执行精确匹配的话,可以通过指定 op 选项来指定其他运算符,比如 like, >, 和 <.

下面是一个例子:

    GET /=/model/Bookmark/title/Yahoo%?op=like

近似于

    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: "阿里巴巴中国雅虎首页" }
    ]
扩展查询语法

通过指定 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) 应由 /=/action/ModelSearch 来完成.

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=like

POST body

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

对应的伪 SQL 为:

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

[猜想:PUT 请求亦可专用于上传二进制的多媒体数据。]

删除记录

用于删除指定的记录

一个例子是:

    DELETE /=/model/Bookmark/url/yahoo?op=like

对应的伪 SQL 为:

    delete from bookmarks
    where url like "yahoo"

Namespace and Databases

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

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

Actions

"动作(Action)"是一种抽象的功能实体。典型的 Action 的例子有"网页搜索", "模型搜索", 以及"邮件发送"等等。 Action 在概念上与编程语言中的函数相近。

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

Action 一般都拥有返回值, 比如“网页搜索”这个动作会返回一个结果列表,而"邮件发送"这个Action 只会返回 true 或者 false.

Action 亦可分为内建 Action (如 WebSearch 和 ModelSearch ) 和用户自定义 Action 两大类.

Create Actions

警告:这一部分是高度猜想性质。

用户自定义的 Action 在概念上等同于数据库中的视图(view),并将通过 OpenAPI 自己定义的 miniSQL 语言来表达其功能.

miniSQL 将不支持嵌套语句,比如嵌套型 select 语句.

示例:

    POST /=/action

POST body

    [
      {
        name: "My SVN bookmarks",
        body: "select * from Bookmark where url like "%svn.%" or description like "%SVN%""
      },
      {
        name: "My Yahoo bookmarks",
        body: "select * from Bookmark where url like "%yahoo%" and title like "%yahoo%""
      }
    ]

Inspect Actions

    GET /=/action/<action name>

可获得指定 action 的界面

示例:

    GET /=/action/WebSearch

Call Actions

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

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

    GET /=/action/WebSearch/query/Hello,world
    [
        { title: "Hello world program - Wikipedia, the free encyclopedia", url: "en.wikipedia.org/wiki/Hello_world_program", summary: "..." },
        { title: "Helloworld.com", url: "www.helloworld.com/", summary: "..." },
        { title: "...", url: "...", summary: "..." },
        ...
    ]

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

    GET /=/action/<action name>/dummy/nil

的形式。

Remove Actions

    DELETE /=/action/<action name>

Built-in Actions

ModelSearch

ModelSearch 提供了用 MiniSQL 对 Models 进行高级查询的方式。下面是一个例子:

    POST /=/action/ModelSearch/lang/miniSQL

POST body

    "select * from Music, Bookmark where Music.url = Bookmark.url"
WebSearch
    GET /=/action/WebSearch
    [
        { name: "query", type: "string" }
    ]

    GET /=/action/WebSearch/query/Hello,world
    [
        { title: "Hello world program - Wikipedia, the free encyclopedia", url: "en.wikipedia.org/wiki/Hello_world_program", summary: "..." },
        { title: "Helloworld.com", url: "www.helloworld.com/", summary: "..." },
        { title: "...", url: "...", summary: "..." },
        ...
    ]

Roles

Administration

    GET/POST/DELETE /=/admin/...

XXX needs work...

DATA FORMAT

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

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

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

在绝大多数情况下,服务器返回的都为列表型数据。当数据量较大,出现分页的时候,列表数据的最后一个元素总为一特殊的结构:

    { name: "_nextpage", src: "/=/url/to/the/next/page" }

一个例子是:

    GET /=/model/Bookmark/id/*?page=2

    [
        { id: 11 },
        { id: 12 },
        { id: 13 },
        ...
        { id: 20 },
        { type: "meta", next: "/=/model/Bookmark/idpage=3" }
    ]

Parameters

charset

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

示例:

    GET /=/model/Bookmark/title/Yahoo?charset=GBK
page

用于指定页码,从 0 起始。缺省为 1.

示例:

    GET /=/model/Bookmark/title/Yahoo?page=4

超出页码范围时,返回空列表.

order_by

指定排序项(可指定多个,以逗号分隔). 例如:

    GET /=/model/Bookmark/title/Yahoo?op=like&order_by=title,url

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

    select *
    from bookmarks
    where title like "Yahoo"
    order by title, url
user

该选项指定用户名

password

该选项指定密码

示例

    GET /=/model/Bookmark/id.xml?user=foo&password=bar

注:以明文方式传递用户名和密码在安全性较高的场合是不允许使用的。

ERROR HANDLING

XXX Needs work...

GRAMMAR FOR miniSQL

miniSQL 语言是标准 SQL 一个核心子集. 其基本语法如下:

    miniSQL: statement

    statement: select_stmt
             | update_stmt
             | delete_stmt

    select_stmt: "select" pattern_list "from" models postfix_clause_list
               | "select" pattern_list "from" models

    pattern_list: pattern "," pattern_list
                | pattern

    pattern: aggregate alias
           | aggregate
           | column

    aggregate: func "(" column ")"
    func: "max"
        | "min"
        | "count"
        | "sum"

    column: qualified_symbol
          | symbol
    qualified_symbol: symbol "." symbol
    symbol: /[A-Za-z]+/

    alias: symbol

    postfix_clause_list: postfix_clause postfix_clause_list
                       | postfix_clause

    postfix_clause: where_clause
                  | group_by_clause
                  | order_by_clause

    where_clause: "where" conditon

    condition: disjunction
    disjunction: conjunction "or" disjuntion
               | conjunction
    conjunction: comparison "and" conjunction
               | comparison
    comparison: column operator literal
              | column operator column
              | "(" condition ")"

    operator: ">"
            | ">="
            | "<"
            | "<="
            | "<>"
            | "="
            | "like"

    literal: string
           | integer
    string: '"' symbol '"'
          | "'" symbol "'"
    integer: /\d+/

    group_by_clause: "group by" column_list

    column_list: column "," column_list
               | column

    order_by_clause: "order by" column_list

    delete_stmt: "delete" "from" symbol where_clause
    update_stmt: "update" symbol set_clause where_clause
               | "update" symbol set_clause

    set_clause: "set" symbol "=" literal