RESTful Web API 实践

 

REST 概念来源

 

  网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备...)。 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致 API 构架的流行,甚至出现"API First"的设计思想。

2000 年,Roy Thomas Fielding 博士在他那篇著名的博士论文 《Architectural Styles and the Design of Network-based Software Architectures》中提出了几种软件应用的架构风格。 REST 作为其中的一种架构风格在这篇论文的第5章中进行了概括性的介绍。

REST 是一种很笼统的概念,它代表一种架构风格。 对于多个 Web 应用采用的架构,我们只能说其中某一个比其它的更具有 REST 风格, 而不能简单粗暴地说:“它采用了 REST 架构而其它的没有”。 为了将 REST 真正地落地,Lenoard Rechardson & Sam Ruby 在《RESTful Web Services》一书中 提出了一种名为“面向资源的架构(ROA: Resource Oriented Architecture)”。 该书中介绍了一些采用 ROA 架构的 Web 服务应该具备的基本特征。

RESTful Web API 设计只是 REST 风格架构设计中的一个环节,并没有统一的标准。下面介绍的是一些比较没有争议的实践原则。

实践原则

1.版本化你的 API

  应该尽量将 API 部署在专用域名之下。

    https://api.example.com

  如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下。

    https://example.org/api/

  应该将 API 的版本号放入 URL。

    https://api.example.com/v1/

  另一种做法是,将版本号放在 HTTP 头信息中,但不如放入 URL 方便和直观。Github 采用这种做法。

2.URI 使用名词和 ID 而不是动词

  在 RESTful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词, 而且所用的名词往往与数据库的表格名对应。 一般来说,数据库中的表都是同种记录的"集合"(collection),所以 API 中的名词也应该使用复数。

举例来说,有一个 API 提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals
    https://api.example.com/v1/employees

3.使用 HTTP 协议标准动词改变状态

  使用 PUT, POST 和 DELETE 方法 而不是 GET 方法来改变状态。 对于资源的具体操作类型,由 HTTP 动词表示。

  常用的 HTTP 动词有下面五个(括号里是对应的 SQL 命令)。

    GET(SELECT):从服务器取出资源(一项或多项)。
    POST(CREATE):在服务器新建一个资源。
    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    DELETE(DELETE):从服务器删除资源。

  还有两个不常用的 HTTP 动词。

    HEAD:获取资源的元数据。
    OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

  下面是一些例子。

    GET /zoos:列出所有动物园
    POST /zoos:新建一个动物园
    GET /zoos/ID:获取某个指定动物园的信息
    PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
    PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
    DELETE /zoos/ID:删除某个动物园
    GET /zoos/ID/animals:列出某个指定动物园的所有动物
    DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

4.过滤信息不涉及状态改变

  如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。 常用的过滤包括分页、排序等等。 下面是一些常见的参数。

    ?limit=10:指定返回记录的数量
    ?offset=10:指定返回记录的开始位置。
    ?page=2&per_page=100:指定第几页,以及每页的记录数。
    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    ?animal_type_id=1:指定筛选条件

  参数的设计允许存在冗余,即允许 API 路径和 URL 参数偶尔有重复。 比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

5.使用 Http 头声明请求和响应的格式

  在客户端和服务端,双方都要知道通讯的格式,格式在 HTTP-Header 中指定

    Content-Type 定义请求格式,比如 URL 格式
    Accept 定义系列可接受的响应格式,JSON,XML 等等

  这个并不强制,实践中也可以通过参数来指定返回内容的格式

6.优先使用 JSON 格式返回结果

  当设计 API 返回结果时,优先使用 JSON 格式,但不强制。 当请求的 URL 表明的是一个集合时,则返回结果为数组形式。 例如:

        {
            "data":[
                {
                    "login": "jerry1",
                    "id": 1,
                    "avatar_url": "https://...."
                },
                {
                    "login": "jerry2",
                    "id": 2,
                    "avatar_url": "https://...."
                },
                {
                    "login": "jerry3",
                    "id": 3,
                    "avatar_url": "https://....",
                }
            ]
        }

  当请求为单个对象时,则返回结果为 MAP 形式。例如,访问 https://api.test.com/users/test 时:

        {
            "login": "jerry4",
            "id": 1,
            "avatar_url": "https://avatars.githubusercontent.com/u/1?v=3"
        }

7.使用超媒体链接来组织文档

  Hypermedia as the Engine of Application State 超媒体作为应用状态的引擎,超文本链接可以建立更好的文本浏览体验。

  RESTful API 最好能做到 Hypermedia,即返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。

  比如,当用户向 api.example.com 的根目录发出请求,会得到这样一个文档。

        {
            "link": {
                "rel":   "collection https://www.example.com/zoos",
                "href":  "https://api.example.com/zoos",
                "title": "List of zoos",
                "type":  "application/vnd.yourformat+json"
            }
        }

  上面代码表示,文档中有一个 link 属性,用户读取这个属性就知道下一步该调用什么 API 了。 rel表示这个 API 与当前网址的关系(collection 关系,并给出该 collection 的网址), href 表示 API 的路径,title 表示 API 的标题,type 表示返回类型。

Hypermedia API 的设计被称为 HATEOAS。Github 的 API 就是这种设计,访问 api.github.com 会得到一个所有可用 API 的网址列表。

        {
            "current_user_url": "https://api.github.com/user",
            "authorizations_url": "https://api.github.com/authorizations",
            // ...
        }

从上面可以看到,如果想获取当前用户的信息,应该去访问 api.github.com/user,然后就得到了下面结果。

        {
            "message": "Requires authentication",
            "documentation_url": "https://developer.github.com/v3"
        }

8.使用 Http 状态码处理错误

  如果你的 API 没有错误处理是很难的,只是返回 500 和出错堆栈不一定有用

  Http 状态码提供 70 个出错,我们只要使用 10 个左右:

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与 401 错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求 JSON 格式,但是只有 XML 格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

  使用详细的错误包装错误:

        {
            "errors":[
                {
                    "userMessage": "Sorry, the requested resource does not exist",
                    "internalMessage": "No car found in the database",
                    "code": 34,
                    "more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"
                }
            ]
        }



just here , jerry

posted @ 2019-02-14 10:57  jerry-Tom  阅读(677)  评论(0编辑  收藏  举报