zoukankan      html  css  js  c++  java
  • RESTful API设计实践

    在当前程序设计语言层出不穷,技术架构五花八门的现在,系统间交互已经渗透到程序设计的方方面面。作为当前最成熟的API设计理论,RESTful API在当下得到了最广泛的应用。

    REST(Representational State Transfer,表属性状态转移)的概念源自于Roy Thomas Fielding博士在2000年发表的论文《Architectural Styles and the Design of Network-based Software Architectures》,而我们常说的RESTful则是指REST风格的API设计,即该API的设计应用了REST的思想。也就是说,基于REST的思想所设计出的API即可称之为RESTful API。但是一般情况下,我们对RESTful API的定义则更加狭隘,通常情况下,我们所属的RESTful API则是使用HTTP通信协议来传递JSON格式数据的API。

    本文中,我们主要讨论这种基于HTTP协议传递JSON格式数据的RESTful API设计的最佳实践。

     

    1.    资源

    在RESTful的世界中,一切皆为资源,就好像在面向对象的世界中,一切皆为对象一样。因此在设计RESTful API的第一步,就是对资源的抽象。这里需要我们将系统中的一切都抽象为资源,包括数据和行为。在定义了资源之后,再为资源附加上其所允许的操作,即完成了对RESTful API的定义。

    1.1   资源

    对数据的抽象相对比较直观,一般来讲,数据可以直接映射成一种资源,例如订单、存储阵列、虚拟机等。而对于行为则会显得稍微有点抽象,例如对文件的拷贝或者移动,也需要视作一种资源。在实际的API设计中,资源呈现为HTTP中的URI,通常使用资源的复数形式来定义这个URI。例如,“/hosts”表示主机这种资源,而“/hosts/1”则表示ID为“1”的主机实例。

    对于不同资源间所存在的关系,通过子资源的方式呈现。例如,“/hosts/1/switches”表示主机1所连接的交换机资源,而“/hosts/1/switches/1”表示主机1所连接的交换机1,即主机1与交换机1相连。等价地,“/switches/1/hosts/1”为交换机1与主机1相连,其所表示的含义是相同的,但是其所处的视角不同,分别以主机和交换机的视角来描述这种情况。

    而对于行为,例如主机上下电,可通过“/hosts/1/powers”来表示主机的电源情况。

    1.2   操作

    在抽象了资源之后,就需要定义对这些资源的操作,而对每种资源,最多只能应用6种操作,即创建、删除、全量修改、增量修改、查询单个资源、列出所有资源。这些操作一般通过HTTP的URI和请求方法共同表示。

    以主机资源为例:

    URI

    请求方法

    功能

    /hosts

    POST

    创建一个主机实例。

    /hosts/{id}

    PUT

    全量修改指定ID的主机信息。

    /hosts/{id}

    PATCH

    增量修改指定ID的主机信息。

    /hosts/{id}

    DELETE

    删除指定ID的主机实例。

    /hosts/{id}

    GET

    查询指定ID的主机信息。

    /hosts

    GET

    列出符合条件的主机信息,通过URL的查询参数来指定查询条件。

    以主机与交换机的关系为例:

    URI

    请求方法

    功能

    /hosts/{hostId}/switches/{switchId}

    POST

    将指定ID的主机与指定ID的交换机连接。

    /hosts/{hostId}/switches/{switchId}

    PUT

    全量修改指定ID主机与指定ID交换机的连接信息。

    /hosts/{hostId}/switches/{switchId}

    PATCH

    增量修改指定ID主机与指定ID交换机的连接信息。

    /hosts/{hostId}/switches/{switchId}

    DELETE

    断开指定ID的主机与指定ID的交换机的连接。

    /hosts/{hostId}/switches/{switchId}

    GET

    查询指定ID的主机与指定ID的交换机的连接信息。

    /hosts/{hostId}/switches

    GET

    列出指定主机所连接的交换机的连接信息,通过URL查询参数指定查询条件。

    以主机的电源为例:

    URI

    请求方法

    功能

    /hosts/{id}/powers

    POST

    为主机上电。

    /hosts/{id}/powers

    DELETE

    为主机下电。

    /hosts/{id}/powers

    GET

    查询主机的电源情况。

    在上例中,只描述了主机简单的电源情况。若需要描述更详细的电源情况,如主机上下电的历史信息、每次上下电的操作人等信息,可为上电和下电分别定义资源。

    1.2.1    全量修改与增量修改

    PUT和PATCH都可以表示修改,但分别表示全量修改与增量修改。其中的区别在于对未指定的数据的处理逻辑。

    例如,主机包含名称和注释属性,当传递的消息中仅包括主机的名称信息而不包含注释信息时,若为全量修改,则需要将注释属性设置到初始状态(无任何内容);而若为增量修改,则应仅修改名称,而不修改注释。

    另外需要注意的是,对于增量修改的API,参数中不包含指定属性和指定属性的值为null应表示不同的含义。不包含指定属性时时,其含义为不修改该属性;而若包含指定属性,且值为null,则其含义为将属性设置为null值(或初始值)。

    但是通常情况下,对参数的解析(反序列化)通常由框架进行,如Spring MVC,此时在业务逻辑中已无法区分参数中是未设置某个属性,还是将属性设置为null值,更普遍的做法是只区分null和空值(如空字符串)。但是在协议层面上,这种做法本质上是有歧义的。这里的协议层面是指直接发送一个HTTP报文。因此为了避免这种歧义,应至少在文档层面上对这种处理方式进行解释和说明。

    1.2.2    单个查询与批量查询

    再以查询主机为例,一般来讲,“/host/1”与“/hosts?id=1”都是查询ID为1的主机,但其返回结果存在差异,前者返回的是一个主机实例的信息,而后者则返回的是一个列表,其中仅包含一个主机实例的信息。虽然其关键数据一致,但返回的数据结构不同。

    通常情况,批量查询接口被称为“list”,即“列出资源信息”的含义。

     

    2.    异常

    2.1.    HTTP状态码

    在基于HTTP协议的RESTful API定义中,异常的定义应遵循HTTP的机制。在HTTP中,通过状态码来表示资源的状态。例如404表示资源不存在,410表示资源已经被删除等。常用的HTTP状态码有:

    状态码

    原因短语

    含义

    200

    OK

    请求成功。常用于GET请求。响应中应包含资源信息。

    201

    Created

    请求成功,且有一个资源根据请求的内容被创建。常用语POST请求。

    202

    Accepted

    服务端已接受该请求,但后续的处理过程可能会失败。常见于请求的异步处理。通常情况下需要返回一个凭证,用以持续跟踪请求的处理情况。常见于POST、PUT、PATCH、DELETE请求。

    204

    No Content

    响应中没有响应内容,常用于DELETE请求。

    205

    Reset Content

    客户端应重新查询资源内容。常用于PUT、PATCH请求。

    400

    Bad Request

    请求格式不正确,应用于参数的合法性校验,通常也用于参数的有效性校验。

    401

    Unauthorized

    未授权,需要进行权限认证后再进行请求。

    404

    Not Found

    所需要操作的资源不存在,常用于PUT、PATCH、DELETE、GET请求。这里需要注意的是,一般来讲,即使是需要删除某个资源,此时资源不存在,也应使用该响应码。因为客户端可能需要区分资源是否是因自己的请求而被删除。

    406

    Not Acceptable

    无法提供合适的内容。通常情况下表示服务端已准备好资源,但是无法将这些内容转换为客户端所需的格式(请求的Accept头)。

    409

    Conflict

    被请求的资源因状态冲突而无法提供服务。例如资源处于不可修改状态,而客户端提交了PUT或PATCH请求。

    410

    Gone

    表示资源已被删除。与404的区别在于,404表示当前服务端没有该资源,该资源可能从未被创建。而410则更加明确该资源曾经存在过。一般情况下这种情况会被归为404一同表示。

    500

    Internal Server Error

    服务端发生未知错误。一般情况下,这个错误码表示服务进入了未处理的异常分支,通常仅用于表示代码BUG。

    HTTP中还有很多其他的状态码,但一般不需要业务代码来感知。例如503所表示的服务不可用(Service Unavailable)通常用于服务应用的启动、升级等场景,但这个错误码通常情况下应被底层的应用程序框架或Web容器所处理。

    2.2.    异常信息

    一般来讲,在响应的HTTP状态码为4XX或5XX时,报文内容应承载异常信息,而非用户数据。而在状态码为2XX时,响应报文中应仅承载用户数据,而不体现异常信息的数据结构。

    这与高级语言中的异常体系的思路是一致的。在高级语言中,如Java、C#等,当发生异常时,会抛出一个Exception,待调用方程序捕获处理,而不会在方法的返回值中表示是否发生了异常。

     

    3.    示例

    假定我们有一个应用的部署系统,需要提供应用的创建、修改、删除、查询、部署能力,那么这些API的交互过程应按以下方式完成。

    3.1.    创建应用

    当我们需要创建一个应用时,我们应当向服务端发送以下信息:

    POST /apps HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 168

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    {

        "name": "my-app",

        "archive": "http://cloud.com/archives/my-app.iso",

        "spec": {

            "cpus": 1,

            "memory": "100MB",

            "disk": "2GB"

        }

    }

    这个请求的首部中,我们指定了HTTP报文内容是一个JSON格式的文本,以UTF-8字符集进行编码,内容长度为168个字节。同时我们需要服务端给我们的响应也是一个JSON格式的文本,以UTF-8字符集编码,期望的语言为英语,可使用gzip或deflate算法进行压缩。

    而请求内容中表示,我们所创建的应用名为“my-app”,归档件所在地址为“http://cloud.com/archives/my-app.iso”,应用所需的运行环境规格为1个CPU,100MB内存和2GB的磁盘空间。

    服务端可能给我们返回以下内容的信息:

    HTTP/1.1 201 Created

    Content-Type: application/json;charset=UTF-8

    Content-Length: 392

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:41:56 GMT

     

    {

        "app": {

            "id": "01d59735-25e6-e227-23b8-926efbf34c82",

            "name": "my-app",

            "archive": "http://cloud.com/archives/my-app.iso",

            "spec": {

                "cpus": 1,

                "memory": "100MB",

                "disk": "2GB"

            },

            "state": "available",

            "lastModifier": "Jishi Liang",

            "lastModificationTime": "2021/1/20 23:41:56"

        }

    }

    这个请求的首部中,指定了响应报文的内容是一个JSON格式的文本,以UTF-8字符集编码,长度为392个字节。使用gzip算法进行压缩,并在2021/1/20 23:41:56返回该响应。

    响应的内容中为已经创建成功的应用的信息,除请求中所提交的信息外,还额外附加了应用的唯一标识、状态、创建人、创建时间等信息。

    这里需要注意的是,响应的HTTP状态码为201(Created)。而且,一般来讲,响应中的数据会多于请求所提交的数据,并且这些数据通常是不可被修改的。

    3.2.    部署应用

    请求信息:

    POST /apps/01d59735-25e6-e227-23b8-926efbf34c82/deployments HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    请求中表示,我们需要为应用“01d59735-25e6-e227-23b8-926efbf34c82“创建一个部署服务。这里的”部署“是一个资源,该资源会对应用进行部署。

    HTTP/1.1 202 Accepted

    Content-Type: application/json;charset=UTF-8

    Content-Length: 52

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:42:21 GMT

     

    {

        "id": "ea8bc7e1-07e4-8ca7-fa72-283a46efb122"

    }

    这个响应表示,服务端已接受了请求,并提供了部署的唯一标识,以供客户端后续跟进。通常情况下这个更多会返回一个异步任务的唯一标识。

    3.3.    查询部署信息

    请求信息:

    GET /apps/01d59735-25e6-e227-23b8-926efbf34c82/deployments/ea8bc7e1-07e4-8ca7-fa72-283a46efb122 HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    这个请求中,我们通过首行的URI,告知服务端,我们需要查询应用ID为 “01d59735-25e6-e227-23b8-926efbf34c82”且部署ID为“ea8bc7e1-07e4-8ca7-fa72-283a46efb122”的部署信息。

    响应信息:

    HTTP/1.1 200 OK

    Content-Type: application/json;charset=UTF-8

    Content-Length: 260

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:43:38 GMT

     

    {

        "deployment": {

            "id": "ea8bc7e1-07e4-8ca7-fa72-283a46efb122",

            "app": {

                "id": "01d59735-25e6-e227-23b8-926efbf34c82",

                "host": "192.168.13.68",

                "port": 9019

            },

            "state": "deploying"

        }

    }

    这个响应向我们展示了当前的部署状态,应用部署在主机“192.168.13.68”上,且使用9019端口,当前依旧在部署中。

    3.4.    修改应用

    请求信息:

    PATCH /apps/01d59735-25e6-e227-23b8-926efbf34c82 HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 45

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    {

        "spec": {

            "disk": "4GB"

        }

    }

    这个请求中,我们通过PATCH请求对应用信息进行增量更新,将将所需运行环境的规格中的磁盘空间更新至4GB。所需修改的应用的唯一标识在首行中的URI中呈现。

    响应信息:

    HTTP/1.1 205 Reset Content

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:53:32 GMT

     

    这个响应告诉我们,信息已经被修改,我们需要重新查询来更新客户端所持有的数据。

    3.5.    列出应用

    请求信息:

    GET /apps?offset=0&limit=100 HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    这个请求中,我们通过首行中URI的查询参数,指定我们需要查询所有应用中的第1~100条应用信息。

    响应信息:

    HTTP/1.1 200 OK

    Content-Type: application/json;charset=UTF-8

    Content-Length: 446

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:41:56 GMT

     

    {

        "apps": [{

            "id": "01d59735-25e6-e227-23b8-926efbf34c82",

            "name": "my-app",

            "archive": "http://cloud.com/archives/my-app.iso",

            "spec": {

                "cpus": 1,

                "memory": "100MB",

                "disk": "4GB"

            },

            "state": "available",

            "lastModifier": "Jishi Liang",

            "lastModificationTime": "2021/1/20 23:53:32"

        }],

        "offset": 0,

        "limit": 100,

        "total": 1

    }

    响应中apps表示查询到的应用信息列表,offset和limit分别表示查询到的结果集在总结果集中的偏移量和限定长度,total表示总结果集中仅包含1个应用记录。

    3.6.    删除应用

    请求信息:

    DELETE /apps/01d59735-25e6-e227-23b8-926efbf34c82 HTTP/1.1

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Accept: application/json;charset=UTF-8

    Accept-Language: en-US

    Accept-Encoding: gzip, deflate

     

    这个请求中,我们通过URI中资源的唯一标识,来指定我们需要删除这个应用。

    响应信息:

    HTTP/1.1 204 No Content

    Content-Type: application/json;charset=UTF-8

    Content-Length: 0

    Content-Encoding: gzip

    Date: Wed, 20 Jan 2021 23:41:56 GMT

     

    这个响应告诉我们,这个资源已经被删除,但是服务端没有需要返回给我们的数据。

  • 相关阅读:
    CodeForces Gym 100935G Board Game DFS
    CodeForces 493D Vasya and Chess 简单博弈
    CodeForces Gym 100935D Enormous Carpet 快速幂取模
    CodeForces Gym 100935E Pairs
    CodeForces Gym 100935C OCR (水
    CodeForces Gym 100935B Weird Cryptography
    HDU-敌兵布阵
    HDU-Minimum Inversion Number(最小逆序数)
    七月馒头
    非常可乐
  • 原文地址:https://www.cnblogs.com/talefox/p/14305941.html
Copyright © 2011-2022 走看看