zoukankan      html  css  js  c++  java
  • RESTful 最佳实战

    在GitHub上看到一本不错的关于REST实战的书,很高兴分享阅读笔记。【下载地址】

    一、什么是REST(WHAT)

    REST架构描述了六种约束。(统一接口、无状态、可缓存、CS架构、分层系统、按需编码)

    统一接口

    统一接口准则定义了客户端和服务端之间的接口,简化和分离了框架结构,这样一来每个部分都可独立演化。以下是接口统一的四个原则: 

    基于资源

    不同资源需要用URI来唯一标识。返回给客户端的表征和资源本身在概念上有所不同,例如服务端不会直接传送一个数据库资源,然而,一些HTML、XML或JSON数据能够展示部分数据库记录,如用芬兰语来表述还是用UTF-8编码则要根据请求和服务器实现的细节来决定。 

    通过表征来操作资源

    当客户端收到包含元数据的资源的表征时,在有权限的情况下,客户端已掌握的足够的信息,可以对服务端的资源进行删改。 

    自描述的信息

    每条信息都包含足够的数据用以确认信息该如何处理。例如要由网络媒体类型(已知的如MIME类型①)来确认需调用哪个解析器。响应同样也表明了它们的缓存能力。 

    超媒体即应用状态引擎(HATEOAS)

    客户端通过body内容、查询串参数、请求头和URI(资源名称)来传送状态。服务端通过body内容,响应码和响应头传送状态给客户端。这项技术被称为超媒体(或超文本链接)。

    除了上述内容外,HATEOS也意味着,必要的时候链接也可被包含在返回的body(或头部)中,以提供URI来检索对象本身或关联对象。下文将对此进行更详细的阐述。

    统一接口是每个REST服务设计时的必要准则。

    无状态

    正如REST是REpresentational State Transfer的缩写,无状态很关键。本质上,这表明了处理请求所需的状态已经包含在请求本身里,也有可能是URI的一部分、查询串参数、body或头部。URI能够唯一标识每个资源,body中也包含了资源的转态(或转态变更情况)。之后,服务器将进行处理,将相关的状态或资源通过头部、状态和响应body传递给客户端。

    从事我们这一行业的大多数人都习惯使用容器来编程,容器中有一个“会话”的概念,用于在多个HTTP请求下保持状态。在REST中,如果要在多个请求下保持用户状态,客户端必须囊括客户端的所有信息来完成请求,必要时重新发送请求。自从服务端不需要维持、更新或传递会话状态后,无状态性得到了更大的延展。此外,负载均衡器无需担心和无状态系统之间的会话。

    所以状态和资源间有什么差别?服务器对于状态,或者说是应用状态,所关注的点是在当前会话或请求中要完成请求所需的数据。而资源,或者说是资源状态,则是定义了资源表征的数据,例如存储在数据库中的数据。由此可见,应用状态是是随着客户端和请求的改变而改变的数据。相反,资源状态对于发出请求的客户端来说是不变的。

    在网络应用的某一特定位置上摆放一个返回按钮,是因为它希望你能按一定的顺序来操作吗?其实是因为它违反了无状态的原则。有许多不遵守无状态原则的案例,例如三条腿的OAuth②,API调用速度限制等。但还是要尽量确保服务器中不需要在多个请求下保持应用状态。

    可缓存

    在万维网上,客户端可以缓存页面的响应内容。因此响应都应隐式或显式的定义为可缓存的,若不可缓存则要避免客户端在多次请求后用旧数据或脏数据来响应。管理得当的缓存会部分地或完全地除去客户端和服务端之间的交互,进一步改善性能和延展性。

    C-S架构

    统一接口使得客户端和服务端相互分离。关注分离意味什么?打个比方,客户端不需要存储数据,数据都留在服务端内部,这样使得客户端代码的可移植性得到了提升;而服务端不需要考虑用户接口和用户状态,这样一来服务端将更加简单易拓展。只要接口不改变,服务端和客户端可以单独地进行研发和替换。

    分层系统

    客户端通常无法表明自己是直接还是间接与端服务器进行连接。中介服务器可以通过启用负载均衡或提供共享缓存来提升系统的延展性。分层时同样要考虑安全策略。

    按需编码(可选)

    服务端通过传输可执行逻辑给客户端,从而为其临时拓展和定制功能。相关的例子有编译组件Java applets和客户端脚本JavaScript。

    遵从上述原则,与REST架构风格保持一致,能让各种分布式超媒体系统拥有期望的自然属性,比如高性能,延展性,简洁,可变性,可视化,可移植性和可靠性。

    REST快速提示

    (根据上面提到的六个原则)不管在技术上是不是RESTful的,这里有一些类似REST概念的建议。遵循它们,可以实现更好、更有用的服务:

    使用http动词表示一些含义

    任何API的使用者能够发送GET、POST、PUT和DELETE请求,它们很大程度明确了所给请求的目的。同时,GET请求不能改变任何潜在的资源数据。测量和跟踪仍可能发生,但只会更新数据而不会更新由URI标识的资源数据。

    合理的资源名

    合理的资源名称或者路径(如/posts/23而不是/api?type=posts&id=23)可以更明确一个请求的目的。使用URL查询串来过滤数据是很好的方式,但不应该用于定位资源名称。

    适当的资源名称为服务端请求提供上下文,增加服务端API的可理解性。通过URI名称分层地查看资源,可以给使用者提供一个友好的、容易理解的资源层次,以在他们的应用程序上应用。资源名称应该应该是名词,避免为动词。使用HTTP方法来指定请求的动作部分,能让事情更加的清晰。

    XML和JSON

    建议默认支持json,并且,除非花费很惊人,否则就同时支持json和xml。在理想情况下,让使用者仅通过改变扩展名.xml和.json来切换类型。此外,对于支持ajax风格的用户界面,一个被封装的响应是非常有帮助的。提供一个被封装响应,在默认的或者有单独扩展名的情况下,例如:.wjson和.wxml,表明客户端请求一个被封装的json或xml响应(请参见下面的封装响应)。

    “标准”中对json的要求很少。并且这些需求只是语法性质的,无关内容格式和布局。换句话说,REST服务端调用的json响应是协议的一部分–没在标准中没有相关描述。更多关于json数据格式可以在http://www.json.org/上找到。

    在REST服务端使用xml,xml的标准和惯例只有关于使用语法正确的标签和文本。特别地,命名空间不是,也不应该是被使用在REST服务端的上下文中。xml的返回更类似于json–简单、容易阅读,没有模式和命名空间细节呈现–仅仅是数据和链接。如果它比这更复杂,看到这个提示(xml和json)的第一段–使用xml的成本是惊人的。然而鉴于我们的经验,很少人使用xml响应。在它被完全淘汰之前,这是最后一个可被肯定的地方。

    创建适当粒度的资源

    开始的时候,模拟底层应用程序域或数据库系统的体系结构更容易去创建API。最终,你将会希望集成服务–利用多项底层资源减少通信量。在创建独立的资源之后再创建更大粒度的资源,比从更大的合集中创建较大的粒度资源来得更加容易。提供这些资源的CRUD(增删改查)功能,从使用小的、容易定义的资源,可以使创建资源变得容易。随后,你可以创建这些基于用例和减少通信量的资源。

    考虑连通性

    REST的原理之一就是连通性–通过超媒体链接实现。当在响应中返回链接时,api变的更具有自描述性,然而在没有它们时服务端依然可用。至少,接口本身可以为客户端提供如何检索数据的参考。此外,在通过POST方法创建资源时,还可以利用头位置包含一个链接。对于响应中支持分页的集合,“first”、 “last”、“next”、和“prev”链接至少是非常有用的。

    定义

    幂等性

    毫无疑问,这与听起来的正好相反,这些与某些功能紊乱的领域无关。来自维基百科:

    在计算机科学中,术语幂等用于更全面地描述一个操作,一次或多次执行该操作产生的结果是一致的。根据应用的上下文,这可能有不同的含义。例如,在方法或者子例程调用具有副作用的情况下,意味着在第一调用之后被修改的状态也保持不变。

    从REST服务端的角度来看,由于操作(或服务端调用)是幂等的,客户端可以用重复的调用而产生相同的结果–在编程语言中操作像是一个“setter”(设置)方法。换句话说,就是使用多个相同的请求与使用单个请求效果相同。注意,当幂等操作在服务器上产生相同的结果(副作用),响应本身可能是不同的(例如在多个请求之间,资源的状态可能会改变)。

    PUT和DELETE方法被定义为是幂等的。然而如何查看http请求中delete警告信息,参照下文DELETE的部分。GET、HEAD、OPTIO和TRACE方法自从被定义为安全的方法后,也被定义为幂等的。参照下面关于安全的段落。

    安全

    来自维基百科:

    一些方法(例如GET、HEAD、OPTIONS和TRACE)被定义为安全的方法,这意味着他们仅用于信息检索,不能更改服务器状态。换句话说,他们不会有副作用,除了相对来说无害的影响,例如日志、缓存。任意的GET请求,不考虑应用状态的上下文,被认为是安全的。

    简而言之,安全意味着调用的方法不会引起副作用。因此,客户端可以反复的使用安全的请求,不用担心对服务端产生副作用。这意味着服务端须坚持GET、HEAD、OPTIONS和TRACE操作的安全定义。否则,除了是服务使用者混淆,它还会导致web端,搜索引擎和其他自动代理的缓存问题–将在服务器上发生意想不到的改变。

    根据定义,安全操作是幂等的,因为他们在服务器上产生相同的结果。

    安全的方法被实现为只读操作。然而,安全并不意味着服务器必须每次都返回相同的响应。

    HTTP动词

    Http动词的主要遵循“统一接口”规则,并提供给我们对应于基于名词的资源的动作。最主要或者最常用的http动词(或者方法,正如它们被恰当地如此称呼)有POST、GET、PUT和DELETE。这些分别对应于创建、读取、更新和删除(CRUD)操作。也有许多其他的动词,但是使用频率比较低。对于使用较少的方法OPTIONS和HEAD使用得比其他动词更经常些。

    GET

    使用http的GET方法来检索(或者读)资源的表征。以“开心的”(或者正确的)方式(译者注:发出GET请求),GET方法会返回一个xml或者json格式的表征,以及一个http响应代码200(正确)。在错误情况下,它通常返回404(不存在)或400(错误的请求)。

    例如:

    GET http://www.example.com/customers/12345 
    GET http://www.example.com/customers/12345/orders 
    GET http://www.example.com/buckets/sample

    据HTTP规范的设计,GET(以及HEAD)请求仅用于读取数据而不改变数据。因此,以这种方式使用时,它被认为是安全的。也就是说,他们可以被称为没有数据修改或污染的风险的–调用1次的效果如同调用10次,甚至根本没有调用过。此外,GET(和HEAD)是幂等的,这意味着使用多个相同的请求与使用单独的请求最终拥有的结果一致。

    不要通过GET暴露不安全的操作–它永远不能修改服务器上的任何资源。

    PUT

    PUT经常被用于更新的功能。对一个已知的资源URI使用PUT,要求请求body中包含原始资源的最新更新的表征。

    然而,在资源ID是由客服端而非服务端提供的情况下,PUT同样可以被用来创建资源。换句话说,如果PUT请求中URI包含不存在的资源ID值,则用于创建资源。并且,请求body中包含一个资源表征。许多人觉得这是令人费解和迷惑的。因此如果真的需要,这种创建方式也应该被谨慎使用。

    另外,使用POST创建新的资源并在body表征中提供客户端定义的ID–应该是针对不含资源ID的URI(见以下POST的部分)。 
    例如: 
    PUT http://www.example.com/customers/12345 
    PUT http://www.example.com/customers/12345/orders/98765 
    PUT http://www.example.com/buckets/secret_stuff

    成功更新,通过PUT请求返回200(或者不返回任何内容时是204)。如果使用PUT请求创建,成功创建返回Http状态码201。响应的内容是可选的–将消耗更多的带宽。创建时,没有必要通过头部的位置返回链接,因为客户端已经设置了资源ID。请参阅下面的返回值部分。

    PUT不是一个安全操作,它在服务器上修改(或创建)状态,但它是幂等的。换句话说,如果你使用PUT创建或者更新资源并且使用相同的调用,资源仍然存在,且仍然有第一次调用后相同的状态。

    例如,如果在资源增量计数器中调用PUT,这个调用方法就不再是幂等的。这种情况有时候会发生,且可能足以使其非幂等性记录在案。不过,建议保持PUT请求的幂等性。强烈建议非幂等性的请求使用POST。

    POST

    POST请求经常被用于创建新的资源,特别是它用于创建下属资源。下属资源也就是归属于其他资源(如父资源)的资源。换句话说,当创建一个新资源,POST请求发送给父资源和服务端,负责将新资源与父资源关联,并分配一个ID(新资源URL),等等。

    例如:

    POST http://www.example.com/customers 
    POST http://www.example.com/customers/12345/orders

    成功创建,返回HTTP状态码201,返回一个位置头信息,其中带有指向最先创建的资源的链接。

    POST请求既不安全又不是幂等,因此被定义为非幂等性资源请求。使用两个相同的POST请求很可能导致两个资源包含相同的信息。

    PUT和POST的创建比较

    简而言之,建议使用POST用于创建资源。否则,当客户端负责决定新资源具有哪些URI(通过它的资源名称或ID)时,使用PUT:如果客户知道结果URI(或资源ID)是什么,则对该URL使用PUT。否则,当服务器或服务端负责决定创建的资源的URI时使用POST。换句话说,当客户端在创建之前不知道(或无法知道)结果的URI时,使用POST创建新的资源。

    DELETE

    DELETE很容易理解。它是用来根据URI标识删除资源的。

    例如:

    DELETE http://www.example.com/customers/12345 
    DELETE http://www.example.com/customers/12345/orders 
    DELETE http://www.example.com/buckets/sample

    成功删除,连同响应体返回http200(正确)状态码。响应体中可能还有删除项的表征(通常要求较多的带宽),或者封装的响应(参见下面的返回值)。换句话说,返回值可能是没有响应体、状态码204(无内容);或者JSON风格的响应体、推荐的状态码为200。

    根据HTTP规范,DELETE操作是幂等的。如果你删除一个资源,资源旧被移除了。在资源上反复调用DELETE最终导致的结果相同:资源没了。如果调用DELETE用于计数器(用于资源),DETELE调用不再是幂等的。如前面所述,只要数据没有被更新,统计和测量的用法依然可被认为是幂等的。建议非幂等性的资源请求使用POST。

    然而,这里有一个关于DELETE幂等性的警告。在一个资源上第二次调用DELETE往往会返回404(未找到)因为它已经移除了,因此不再是可找到的。这使得DELETE操作不再是幂等的。但如果资源不是被简单地标记为删除,而是被从数据库中删除,这种情况需要适当妥协。

    结合主要HTTP方法和资源URI,总结出推荐的返回值,如下表:

    http请求/用户/用户/{id}
    GET 200(正确),用户列表。使用分页、排序和过滤大导航列表。 200(正确),单个用户。404(未找到),如果ID没有找到或ID无效。
    PUT 404(未找到),除非你想在整个集合中更新/替换每个资源。 200(正确)或204(无内容)。404(未找到),如果没有找到ID或ID无效。
    POST 201(创建),带有链接到/客户/{id}的“位置”头部包含新的ID。 404(未找到)
    DELETE 404(未找到),除非你想删除整个集合–通常是不可取的。 200(OK)。404(未找到),如果没有找到ID或ID无效。

    资源命名

    除了适当地使用HTTP动词,在创建一个可以理解的、易于使用的Web服务API时,资源命名可以说是最具有争议的和最重要的概念。当资源被很好的命名,这个API是非常直观并且易于使用的。如果命名的不好,同样的API会感觉很笨拙并且难以使用和理解。下面是一些当你需要为你的新API创建资源URL时的小技巧。

    从本质上讲,一个RESTFul API最终是简单URI的集合,HTTP调用这些URI、JSON或/和用XML表示的资源,其中许多包含相关的链接。RESTful的可寻址能力主要由URI覆盖。每个资源都有自己的地址或URI–服务器可以提供的每个有趣信息,都是作为资源来公开。统一接口的原则部分地由URI和HTTP动词的组合来解决,并符合使用标准和约定。

    在决定哪些资源是在你的系统内,你需要用名词而不是用动词或操作来命名它们。换句话说,一个RESTful URI应该关联到一个作为事物的资源,而不是关联到一个动作。名词具有一些作为动词没有的属性,是另一个显著因素。

    一些资源的例子:

    • 系统的使用者
    • 学生登记的课程
    • 一个用户的帖子时间轴
    • 跟在别的用户后面的用户
    • 一篇关于骑马的文章

    服务套件中的每个资源至少有一个URI来标识。最佳的是,这个URI是有意义的并且充分描述了这个资源。URI应该遵循可预测的、具有层次结构来提高可理解性和可用性的:可预测的,含义是资源与名称是一致的;分层的,含义是数据具有关系上的结构。这不是REST规则或规范,但它增强了这个API。

    RESTful API是写给用户的。URI的名称和结构应该传达意义给用户。通常很难知道数据边界应该是什么,但是对数据的理解上,你最有可能是有目的的去找这个名称,以及作为有意义的数据返回到客户端的表征。API设计是为了你的客户端,而不是你的数据。

    假设我们描述一个订单系统包括客户、订单,行项目,产品等。考虑在这个服务中涉及的描述资源的URL:

    资源URL例子

    为了在系统中插入(创建)一个新的用户,我们可以使用:

    POST http://www.example.com/customers

    为了使用ID33245读取一个用户的信息:

    GET http://www.example.com/customers/33245

    相同的URI可以用于PUT和DELETE,分别更新和删除数据。

    这里提出了产品的URI:

    POST http://www.example.com/products

    用于创建新的产品。

    GET|PUT|DELETE http://www.example.com/products/66432

    用于分别读取、更新、删除产品66432。

    现在,有趣的地方是……如何为用户创建一个新的订单?

    一种方式可以是:

    POST http://www.example.com/orders

    这种方式可以用来创建菜单,但它抛开了用户的背景。

    因为我们想为用户创建一个菜单(记住这个关系),这个URI可能没有达到其应有的直观效果。下面这个URL可能会更清晰:

    POST http://www.example.com/customers/33245/orders

    现在我们知道我们是正在为ID33245的用户创建一个订单。 
    那以下请求返回的是什么呢?

    GET http://www.example.com/customers/33245/orders

    大概是一个ID为33245的用户所创建或拥有的订单列表。注意:我们可能选择不支持删除或者更新这个URL,因为它的操作对象是一个集合。

    那么,继续层级这个概念,下面这个请求的URI是什么样呢?

    POST http://www.example.com/customers/33245/orders/8769/lineitems

    这个可能是(为了ID为33245的用户)增加一个编号为8769的订单条目。没错!对那个URI使用GET方式,返回那个订单的所有条目。然而,如果这些条目与用户信息无关,或者和用户背景之外的信息有关,我们将会提供POST www.example.com/orders/8769/lineitems这个URI。

    先不考虑这些条目,由于所给的资源可能有多个URI,我们可能也要提供一个GET http://www.example.com/orders/8769 的URI,来支持在不知道用户ID的情况下,根据订单ID检索订单。

    更深一层:

    GET http://www.example.com/customers/33245/orders/8769/lineitems/1

    可能只返回同个订单中的第一个条目。

    现在你可以明白层级概念是怎样的了。他们不是明确的规则,只是确定这些强化的结构对你服务的用户是有意义的。在所有软件开发的工艺中,命名是成功的关键。

    看一些广泛使用API来掌握这个窍门,利用你队友直观来提炼你的API资源URI。一些API的例子:

    资源命名的反面例子

    虽然我们已经讨论过一些适当的命名资源例子,但有时看到一些反面例子也是很有益的。下面是一些非常不RESTful的资源URI,看起来是很混乱的。那些是错误行为的例子!

    首先,往往服务端会使用一个单一的URI来指定服务接口,使用查询串参数且/或用HTTP动词来指定这个请求操作。例如,更新ID12345的用户,这个带有JSON体的请求可能会是:

    GET http://api.example.com/services?op=update_customer&id=12345&format=json

    现在,你可以像上面这样做。尽管“services”的这个URL节点是一个名词,但这个URL不像URI层次一样是自描述的,不像URI层次那样对所有请求都是一样的。加上,虽然我们想要执行更新,但它使用GET当做HTTP动词。所以客户端使用这样的API,与直觉是相反的、是痛苦的(甚至是危险的)。

    以下是另外一个更新用户的操作的例子:

    GET http://api.example.com/update_customer/12345

    以及他的邪恶分身:

    GET http://api.example.com/customers/12345/update

    当你在浏览其他开发者的服务套件时,会经常看到这种用法。但值得注意的是,这些开发者试图去创建RESTful的资源名称,而且已经获得了一些进步。但是你比这更进一步–能够识别出URL中的动词短语。注意,在这个URL中我们不需要用这个“update”动词短语,因为我们可以依赖HTTP 动词去告知这个操作。阐明这个观点,正如下面这个资源URL是多余的:

    PUT http://api.example.com/customers/12345/update

    这个请求同时存在PUT和“update”,我们会混淆我们的服务对象!“update”是指这个资源吗?所以,我们花费了一些时间去打马。我相信你能理解我的意思……

    复数

    让我们来讨论一下复数和“单数”的争议…没有听过这个争议点?它确实是存在的,事实上它归结为这个问题……

    在你的层级中你的URI节点是否需要命名为单数或者复数名词?例如,在你检索用户资源的URI命名是否需要像下面这样:

    GET http://www.example.com/customer/33245

    或者:

    GET http://www.example.com/customers/33245

    两种方式都有好的理由来支撑,但是一般习惯做法是节点名总是使用复数命名,以使得你的API URI在所有的HTTP方法中保持一致。原因是基于这样的理念:客户在服务套件中是一个集合,而这个用户ID(例如33245)是指集合中其中的一个客户。

    利用这个规则,一个多节点URI例子会是这样(强调):

    GET http://www.example.com/customers/33245/orders/8769/lineitems/1

    “customers”、“orders”以及“lineitems”这些URI节点都是他们的复数形式。

    这意味着,你真正只需要两个基本的URL作为根资源。一个用于集合内资源的创建,第二个用来根据标识符获取、更新和删除资源。例如创建的情况,以customers为例,由以下的URL进行操作:

    POST http://www.example.com/customers

    以及读取、更新和删除的情况,用下面的URL操作:

    GET|PUT|DELETE http://www.example.com/customers/{id}

    正如前面提到的,给定的资源可能有多个URI,但作为一个最小完整增删改查功能,利用两个简单的URI来处理是恰当的。

    或许你会问:是否在有些情况下复数没有意义?嗯,事实上是这样的。当没有集合概念的时候(复数没有意义)。换句话说,当资源只有一个的情况下,使用单数资源名称是可以接受的–它是一个单一的资源。例如,如果有一个单一的总体配置资源,你可以使用一个单数名词来表示,如:

    GET|PUT|DELETE http://www.example.com/configuration

    注意缺少配置ID的POST动词用法。以及每个用户只有一个配置,那么这个URL可以是:

    GET|PUT|DELETE http://www.example.com/customers/12345/configuration

    另外,注意没有配置ID的非POST动词用法。尽管如此,我相信在这两种情况下POST使用可能会是有效的。

  • 相关阅读:
    《译》准备做一些 AR/增强现实的 翻译
    (转)两张Firefox OS 系统截图
    Hello World!
    centos7安装docker
    linux用户及组相关命令
    Go 系列教程 ——第 30 篇:错误处理
    Go 系列教程 ——第 29 篇:Defer
    linux远程管理相关命令
    linux文件目录相关命令
    centos7安装mysql-8.0.15
  • 原文地址:https://www.cnblogs.com/zacky31/p/8862870.html
Copyright © 2011-2022 走看看