以RESTful方式在资源上调用服务器端方法

Kir*_*met 136 api rest url api-design restful-architecture

请记住,我对REST有基本的了解.假设我有这个网址:

http://api.animals.com/v1/dogs/1/
Run Code Online (Sandbox Code Playgroud)

现在,我想让服务器让狗吠.只有服务器知道如何执行此操作.假设我想让它在一个CRON工作上运行,这使得狗在永恒的剩余时间内每隔10分钟就会吠叫一次.这个电话是什么样的?我有点想这样做:

网址请求:

ACTION http://api.animals.com/v1/dogs/1/
Run Code Online (Sandbox Code Playgroud)

在请求正文中:

{"action":"bark"}
Run Code Online (Sandbox Code Playgroud)

在你为了构建我自己的HTTP方法而生气之前,请帮助我,让我更好地了解如何以RESTful方式调用服务器端方法.:)

编辑澄清

关于"树皮"方法做什么的更多澄清.以下是一些可能导致不同结构化API调用的选项:

  1. bark只是发送一封电子邮件给dog.email并且没有记录.
  2. bark向dog.email发送电子邮件,并将dog.barkCount增加1.
  3. 当树皮发生时,树皮会创建一个新的"树皮"记录,并带有bark.timestamp记录.它还将dog.barkCount递增1.
  4. bark运行系统命令从Github下载最新版本的狗代码.然后它向dog.owner发送一条短信告诉他们新的狗代码正在制作中.

acd*_*ior 270

为什么要以RESTful设计为目标?

RESTful原则带来了使网站变得容易(让随机的人类用户 "浏览"它们)到Web服务API设计的功能,因此程序员可以轻松使用它们.REST并不好,因为它是REST,它很好,因为它很好.这很好,主要是因为它很简单.

普通HTTP(没有SOAP信封和单URI重载POST服务)的简单性,有些人可能称之为"缺乏功能",实际上是它的最大优势.最后,HTTP要求您具有可寻址性无状态性:两个基本的设计决策使HTTP可以扩展到今天的大型站点(和大型服务).

但REST不是银色的bulltet:有时RPC样式("远程过程调用" - 如SOAP)可能是合适的,有时其他需求优先于Web的优点.这可以.我们不喜欢的是不必要的复杂性.程序员或公司常常为普通旧HTTP可以正常处理的作业引入RPC样式的服务.结果是,HTTP被简化为一个巨大的XML有效载荷的传输协议,解释了"真正"发生了什么(不是URI或HTTP方法给出了关于它的线索).生成的服务太复杂,无法调试,除非您的客户端具有开发人员预期的确切设置,否则将无法运行.

同样,Java/C#代码也不是面向对象的,只是使用HTTP并不能使设计成为RESTful.在应该调用的动作和远程方法方面,人们可能会急于考虑他的服务.难怪这将主要以RPC样式服务(或REST-RPC-hybrid)结束.第一步是思考不同.RESTful设计可以通过多种方式实现,一种方式(最简单的,有些人可能会说)是根据资源而不是动作考虑您的应用程序:

  • 而不是考虑行动("在地图上搜索地点"),
  • 根据该操作的结果进行思考("匹配搜索条件的地图上的地点列表").

我将在下面举例说明.(REST的另一个关键方面是使用HATEOAS - 我不在这里刷它,但我在另一篇文章中快速谈论它.)

关于第一个设计

我们来看看拟议的设计:

ACTION http://api.animals.com/v1/dogs/1/
Run Code Online (Sandbox Code Playgroud)

首先,我们不应该考虑创建一个新的HTTP动词(ACTION).一般来说,这是不可取的,原因如下:

  • (1)只给出服务URI,"随机"程序员如何知道ACTION动词存在?
  • (2)如果程序员知道它存在,他将如何知道它的语义?这个动词是什么意思?
  • (3)人们期望动词具有哪些属性(安全性,幂等性)?
  • (4)如果程序员有一个只处理标准HTTP动词的非常简单的客户端怎么办?
  • (5) ......

现在让我们考虑使用POST(我将在下面讨论为什么,现在就接受我的话):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}
Run Code Online (Sandbox Code Playgroud)

可能没问题 ......但仅限于:

  • {"action":"bark"}是一份文件; 和
  • /v1/dogs/1/是一个"文档处理器"(类似工厂)的URI."文档处理器"是一个URI,你只是"扔东西"并"忘记"它们 - 处理器可能会在"抛出"之后将你重定向到新创建的资源.例如,在消息代理服务上发布消息的URI,在发布之后会将您重定向到显示消息处理状态的URI.

我对你的系统了解不多,但我已经打赌两者都不是真的:

  • {"action":"bark"} 它不是一个文件,它实际上你试图忍者偷偷进入服务的方法; 和
  • /v1/dogs/1/URI表示"狗"资源(可能是狗id==1),而不是一个文档处理器.

所以我们现在所知道的是上面的设计并不那么RESTful,但究竟是什么呢?这有什么不好的?基本上,它很糟糕,因为这是具有复杂含义的复杂URI.你无法从中推断出任何东西.一个程序员如何知道一个狗有一个bark可以秘密注入其中的动作POST

设计问题的API调用

因此,让我们切入追逐,并尝试通过思考资源来设计那些树皮.请允许我引用Restful Web Services一书:

一个POST请求是试图从现有的一个新的资源.现有资源可以是数据结构意义上的新资源的父级,树的根是其所有叶节点的父级的方式.或者现有资源可以是特殊的"工厂" 资源,其唯一目的是生成其他资源.与POST请求一起发送的表示描述了新资源的初始状态.与PUT一样,POST请求根本不需要包含表示.

继上面的描述我们可以看到,bark可以模拟为一个子资源dog(因为bark包含在狗中,也就是说,树皮"咆哮" 狗).

从这个推理我们已经得到:

  • 方法是 POST
  • 资源是/barksdog的子资源:/v1/dogs/1/barks代表bark"工厂".该URI对于每只狗都是唯一的(因为它在下面/v1/dogs/{id}).

现在,列表中的每个案例都有特定的行为.

1. bark只是发送电子邮件dog.email并且没有记录任何内容.

首先,咆哮(发送电子邮件)同步或异步任务?其次,bark请求是否需要任何文件(可能是电子邮件)还是空的?


1.1 bark发送电子邮件dog.email并记录任何内容(作为同步任务)

这种情况很简单.对barks工厂资源的调用立即产生树皮(发送电子邮件),并立即给出响应(如果可以或不存在):

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK
Run Code Online (Sandbox Code Playgroud)

因为它记录(变化)没有,200 OK就足够了.它表明一切都按预期进行.


1.2 bark发送电子邮件dog.email并记录任何内容(作为异步任务)

在这种情况下,客户端必须有一种方法来跟踪bark任务.在bark随后的任务应该是与它自己的URI资源:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Run Code Online (Sandbox Code Playgroud)

这样,每个bark都是可追溯的.然后,客户端可以GETbarkURI 发出一个知道它的当前状态.甚至可以用a DELETE来取消它.


2. bark发送电子邮件dog.email然后递增dog.barkCount1

如果你想让客户知道dog资源被改变了,那么这个可能会更棘手:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1
Run Code Online (Sandbox Code Playgroud)

在这种情况下,location标题的意图是让客户知道他应该看一看dog.来自HTTP RFC关于303:

此方法主要用于允许输出 POST激活的脚本以将用户代理重定向到选定的资源.

如果任务是异步的,bark则需要一个子资源,就像1.2情况一样,303应该GET .../barks/Y在任务完成时返回.


3.树皮创建一个新的" bark"记录与bark.timestamp录音时树皮发生.它也增加dog.barkCount1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Run Code Online (Sandbox Code Playgroud)

在此处,bark是由于请求而创建的,因此将201 Created应用状态.

如果创建是异步的,202 Accepted则需要a(如HTTP RFC所示).

保存的时间戳是bark资源的一部分,可以使用a GET来检索.更新的狗也可以"记录"在那里GET dogs/X/barks/Y.


4. bark运行系统命令从Github下载最新版本的狗代码.然后它发送一条短信dog.owner告诉他们新的狗代码正在生产中.

这个的措辞很复杂,但它几乎是一个简单的异步任务:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Run Code Online (Sandbox Code Playgroud)

然后,客户端将发出GETs以/v1/dogs/1/barks/a65h44了解当前状态(如果代码被拉出,则电子邮件被发送给所有者等).每当狗改变时,a 303都是适用的.


包起来

引用罗伊菲尔丁:

REST对方法的唯一要求是为所有资源统一定义它们(即,为了理解请求的含义,中间人不必知道资源类型).

在上面的例子中,POST是统一设计的.它会使狗" bark".这不安全(意味着树皮对资源有影响),也不是幂等(每个请求产生一个新的bark),这很适合POST动词.

程序员会知道:一POST,以barks产生一个bark.响应状态代码(必要时还包含实体主体和标题)可以解释更改的内容以及客户端可以和应该如何进行.

注意:使用的主要来源是:" Restful Web Services "一书,HTTP RFCRoy Fielding的博客.




编辑:

自问题首次创建以来,问题和答案已经发生了很大的变化.在原来的问题询问了URI喜欢的设计:

ACTION http://api.animals.com/v1/dogs/1/?action=bark
Run Code Online (Sandbox Code Playgroud)

下面解释为什么它不是一个好的选择:

客户端如何告诉服务器应该做什么的数据是方法的信息.

  • RESTful Web服务在HTTP方法中传递方法信息.
  • 典型的RPC-Style和SOAP服务将它们保存在entity-body和HTTP头中.

客户端希望服务器运行的数据的哪一部分作用域信息.

  • RESTful服务使用URI.SOAP/RPC-Style服务再次使用entity-body和HTTP头.

例如,使用Google的URI http://www.google.com/search?q=DOG.在那里,方法信息是GET和范围信息是/search?q=DOG.

长话短说:

  • RESTful架构中,方法信息进入HTTP方法.
  • 面向资源的体系结构中,范围信息进入URI.

而经验法则:

如果HTTP方法与方法信息不匹配,则该服务不是RESTful.如果作用域信息不在URI中,则该服务不是面向资源的.

您可以将"bark" "action"放在URL(或实体主体)中并使用POST.没问题,它有效,可能是最简单的方法,但这不是RESTful.

为了使您的服务真正保持RESTful,您可能不得不退后一步思考您在这里真正想做的事情(它会对资源产生什么影响).

我不能谈谈您的具体业务需求,但让我举个例子:考虑一个RESTful订购服务,其中订单的URI类似于example.com/order/123.

现在说我们要取消订单,我们将如何做?人们可能会认为这是一个"取消" "行动",并将其设计为POST example.com/order/123?do=cancel.

正如我们上面谈到的那样,这不是RESTful.相反,我们可能会将一个元素PUT的新表示发送到:ordercanceledtrue

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>
Run Code Online (Sandbox Code Playgroud)

就是这样.如果无法取消订单,则可以返回特定的状态代码.(为简单起见,子资源设计POST /order/123/canceled与实体主体一样true,也可以使用.)

在您的特定场景中,您可以尝试类似的东西.这样,当狗正在吠叫时,例如,GETat /v1/dogs/1/可以包括该信息(例如<barking>true</barking>).或者......如果这太复杂了,放松你的RESTful要求并坚持下去POST.

更新:

我不想让答案太大,但需要一段时间才能将算法(一个动作)暴露为一组资源.不要考虑行动("在地图上搜索地点"),而是需要根据该行动的结果进行思考("匹配搜索条件的地图上的地点列表").

如果您发现您的设计不适合HTTP的统一界面,您可能会发现自己回到这一步.

查询变量 作用域信息,但表示新资源(/post?lang=en显然是相同的资源/post?lang=jp,只是一个不同的表示).相反,它们被用于传达客户端状态(如?page=10,使得状态不保留在服务器; ?lang=en在这里也一个例子)或输入参数算法资源(/search?q=dogs,/dogs?code=1).同样,不是不同的资源.

HTTP动词'(方法)属性:

?action=somethingURI 中显示的另一个明确点是RESTful,是HTTP谓词的属性:

  • GET并且HEAD是安全的(和幂等的);
  • PUT并且DELETE只是幂等的;
  • POST 既不是.

安全性:A GETHEAD请求是读取某些数据的请求,而不是更改任何服务器状态的请求.客户可以提出GETHEAD请求10次,这与制作一次或从不制作它一样.

幂等性:无论是一次还是多次应用它都具有相同效果的幂等运算(在数学中,乘以零是幂等的).如果您DELETE是资源一次,则再次删除将具有相同的效果(资源GONE已经存在).

POST既不安全也不是幂等.POST对"工厂"资源发出两个相同的请求可能会导致两个下级资源包含相同的信息.随着重载(URI或实体主体中的方法)POST,所有赌注都关闭.

这两个属性对于HTTP协议的成功非常重要(在不可靠的网络上!):您有多少次更新(GET)页面而不等到它完全加载?

创建一个动作并将其放在URL中会明显破坏HTTP方法的合同.再一次,技术允许你,你可以做到,但这不是RESTful设计.

  • 答案我们更新了.这有点长,因为似乎需要彻底的解释("请记住,我对REST有一个基本的了解.").让它尽可能清晰完整是一种挣扎.希望它在某些方面有用. (5认同)
  • 很好的解释,我投票但是不应该在202 Accepted响应中使用Location标头.这似乎是许多人从RFC做出的错误解释.请查看http://stackoverflow.com/questions/26199228/is-the-use-of-location-header-in-http-202-response-rfc-compliant (2认同)

Jor*_*dan 6

之前回答过,但是这个答案与我的旧答案相矛盾,并采用了一种不同的策略来寻求解决方案.它显示了如何根据定义REST和HTTP的概念构建HTTP请求.它也用来PATCH代替POSTPUT.

它通过REST约束,然后是HTTP的组件,然后是一个可能的解决方案.

休息

REST是一组旨在应用于分布式超媒体系统的约束,以使其可扩展.即使在远程控制动作的上下文中理解它,您也必须考虑将动作远程控制为分布式超媒体系统的一部分 - 分布式超媒体系统是发现,查看和修改互连信息的系统的一部分.如果这比它的价值更麻烦,那么尝试使它成为RESTful可能并不好.如果您只想在客户端上使用"控制面板"类型的GUI,它可以通过端口80触发服务器上的操作,那么您可能需要一个简单的RPC接口,如通过HTTP请求/响应的JSON-RPC或WebSocket.

但REST是一种迷人的思维方式,问题中的示例恰好可以通过RESTful界面轻松建模,因此让我们接受挑战,以获得乐趣和教育.

REST 由四个接口约束定义:

资源识别; 通过陈述来处理资源; 自我描述性的信息; 并且,超媒体作为应用程序状态的引擎.

你问如何定义一个接口,满足这些约束,一台计算机通过它告诉另一台计算机让狗叫.具体来说,您希望您的接口是HTTP,并且您不希望在按预期使用时丢弃使HTTP RESTful成为的功能.

让我们从第一个约束开始:资源识别.

可以命名的任何信息都可以是资源:文档或图像,临时服务(例如"洛杉矶的今天天气"),其他资源的集合,非虚拟对象(例如人)等等.

所以狗是一种资源.需要确定.

更确切地说,资源R是时间上变化的隶属函数M R(t),其对于时间t映射到等效的一组实体或值.集合中的值可以是资源表示和/或资源标识符.

您通过采用一组标识符和表示形式对狗进行建模,并说它们在给定时间彼此相关联.现在,让我们使用标识符"dog#1".这带来了第二和第三个约束:资源表示自我描述.

REST组件通过使用表示捕获该资源的当前或预期状态并在组件之间传输该表示来对资源执行操作.表示是字节序列,加上用于描述这些字节的表示元数据.

以下是捕获狗的预期状态的字节序列,即我们希望与标识符"dog#1"相关联的表示(注意它仅代表状态的一部分,因为它不考虑狗的名字,健康,甚至过去的吠声):

自该州改变实施以来,它每10分钟一直在吠叫,并将无限期地持续下去.

它应该附加到描述它的元数据.此元数据可能很有用:

这是一份英文声明.它描述了预期状态的一部分.如果多次收到,只允许第一个产生效果.

最后,让我们看看第四个约束:HATEOAS.

REST ...将应用程序视为信息和控制备选方案的一致结构,用户可通过该结构执行所需任务.例如,在在线词典中查找单词是一个应用程序,如在虚拟博物馆中巡回演出,或者查看一组课堂笔记以进行考试....应用程序的下一个控制状态驻留在第一个请求资源的表示中,因此获得第一个表示是优先级....因此,模型应用程序是通过检查并从当前表示集合中的备选状态转换中进行选择而从一个状态移动到下一个状态的引擎.

在RESTful接口中,客户端接收资源表示以便弄清楚它应该如何接收或发送表示.在应用程序的某处必须有一个表示,客户端可以从中找出如何接收或发送它应该能够接收或发送的所有表示,即使它遵循一系列表示来获得该信息.这看起来很简单:

客户端要求表示被识别为主页的资源; 作为响应,它获得一个表示,其中包含客户端可能想要的每只狗的标识符.客户端从中提取标识符并询问服务如何与识别的狗进行交互,并且服务表明客户端可以发送描述狗的预期状态的部分英语声明.然后客户端发送这样的语句并接收成功消息或错误消息.

HTTP

HTTP实现REST约束,如下所示:

资源识别:URI

资源表示:entity-body

自我描述:方法或状态代码,标题,以及实体主体的可能部分(例如XML模式的URI)

HATEOAS:超链接

你决定http://api.animals.com/v1/dogs/1作为URI.让我们假设客户端从网站上的某个页面获取此信息.

让我们使用这个实体 - body(值next是一个时间戳;值0'是'当收到此请求时'):

{"barks": {"next": 0, "frequency": 10}}
Run Code Online (Sandbox Code Playgroud)

现在我们需要一种方法.PATCH符合我们决定的"预期状态的一部分"描述:

PATCH方法请求将请求实体中描述的一组更改应用于Request-URI标识的资源.

还有一些标题:

表示实体主体的语言: Content-Type: application/json

确保它只发生一次: If-Unmodified-Since: <date/time this was first sent>

我们有一个要求:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}
Run Code Online (Sandbox Code Playgroud)

成功时,客户应该收到204响应中的状态代码,或者205如果代表/v1/dogs/1/已经改变以反映新的吠叫时间表.

失败时,它应该收到403一个有用的消息,为什么.

REST服务在表示中反映树皮调度并不是必需的GET /v1/dogs/1/,但是如果JSON表示包括这个,那么它将是最有意义的:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}
Run Code Online (Sandbox Code Playgroud)

将cron作业视为服务器从界面隐藏的实现细节.这就是通用界面的美妙之处.客户端不必知道服务器在幕后做了什么; 它所关心的只是服务理解并响应所请求的状态变化.