我正在努力确定如何设计宁静的 URL。我完全赞成使用带有名词而不是动词的 URL 的宁静方法,我不明白如何做到这一点。
我们正在创建一项服务来实现财务计算器。计算器采用一堆参数,我们将通过 CSV 文件上传这些参数。用例将涉及:
上传新参数 获取最新参数 获取给定业务日期的参数 激活一组参数 验证一组参数
我收集安静的方法是使用以下类型的 URL:
/parameters
/parameters/12-23-2009
您可以通过以下方式实现前三个用例:
POST,您在 post 请求中包含参数文件 第一个 URL 的 GET 第二个 URL 的 GET
但是你如何在没有动词的情况下完成第 4 和第 5 用例呢?你不需要像这样的网址:
/parameters/ID/activate
/parameters/ID/validate
??
良好 URI 设计的一般原则:
不要使用查询参数来改变状态
如果可以,请不要使用大小写混合路径;小写最好
不要在 URI 中使用特定于实现的扩展名(.php、.py、.pl 等)
不要用你的 URI 陷入 RPC
尽可能限制你的 URI 空间
保持路径段短
更喜欢 /resource 或 /resource/;从您不使用的重定向创建 301 重定向
使用查询参数对资源进行子选择;即分页,搜索查询
将应该在 HTTP 标头或正文中的内容移出 URI
(注意:我没有说“RESTful URI 设计”;URI 在 REST 中本质上是不透明的。)
HTTP方法选择的一般原则:
永远不要使用 GET 来改变状态;这是让 Googlebot 毁掉你一天的好方法
除非您要更新整个资源,否则不要使用 PUT
不要使用 PUT 除非您也可以合法地在同一个 URI 上执行 GET
不要使用 POST 来检索长期存在的信息或可能合理缓存的信息
不要使用 PUT 执行非幂等操作
尽可能多地使用 GET
如有疑问,请优先使用 POST 而不是 PUT
每当您必须做一些感觉像 RPC 的事情时,请务必使用 POST
对更大或分层的资源类使用 PUT
使用 DELETE 优先于 POST 来删除资源
除非您的输入很大,否则请务必使用 GET 进行计算,在这种情况下使用 POST
使用 HTTP 设计 Web 服务的一般原则:
不要将元数据放在应该在标头中的响应正文中
不要将元数据放在单独的资源中,除非包含它会产生大量开销
请使用适当的状态码
201 创建资源后创建;资源在发送响应时必须存在
202 成功执行操作或异步创建资源后接受
400 Bad Request 当有人对明显伪造的数据进行操作时;对于您的应用程序,这可能是一个验证错误;通常为未捕获的异常保留 500
401 Unauthorized 当有人在没有提供必要的 Authorization 标头或 Authorization 中的凭据无效的情况下访问您的 API 时;如果您不希望通过 Authorization 标头获得凭据,请不要使用此响应代码。
403 Forbidden 当有人以可能是恶意的方式或未经授权访问您的 API 时
405 Method Not Allowed 当有人在应该使用 PUT 时使用 POST 等
413 Request Entity Too Large 当有人试图向您发送一个不可接受的大文件时
418 用茶壶冲泡咖啡时,我是茶壶
尽可能使用缓存标头
当您可以轻松地将资源减少为哈希值时,ETag 标头很好
Last-Modified 应该向您表明,保留资源更新时间的时间戳是个好主意
Cache-Control 和 Expires 应该被赋予合理的值
尽你所能尊重请求中的缓存标头(If-None-Modified、If-Modified-Since)
在有意义的时候使用重定向,但是对于 Web 服务来说这些应该很少见
关于您的具体问题,POST 应该用于#4 和#5。这些操作属于上述“类 RPC”指南。对于 #5,请记住 POST 不一定必须使用 Content-Type: application/x-www-form-urlencoded
。这可以很容易地成为 JSON 或 CSV 有效负载。
也许是这样的:
PUT /parameters/activation HTTP/1.1
Content-Type: application/json; encoding=UTF-8
Content-Length: 18
{ "active": true }
POST
vs PUT
并不完全像 insert
vs update
。 PUT
更新与给定路径对应的资源,或创建与给定路径对应的新资源。 POST
在某处创建新资源。例如,PUT /blog/posts/3/comments/5
将更新相应的评论,而 POST /blog/posts/3/comments
将创建一个新的 comment
资源(并应在响应中返回新资源的路径)。
PUT
是幂等的,而 POST
不是。通常,您应该尽可能多地限制您提供的结果。坚持使用 PUT
可为服务的客户端提供更多信息。
每当您看起来需要一个新动词时,请考虑将那个动词变成名词。例如,将“激活”变为“激活”,将“验证”变为“验证”。
但就您所写的内容而言,我会说您的应用程序存在更大的问题。
每当提出一种称为“参数”的资源时,它都应该在每个项目团队成员的脑海中发出危险信号。 “参数”可以从字面上适用于任何资源;它不够具体。
“参数”究竟代表什么?可能有很多不同的东西,每一个都应该有一个单独的资源专门用于它。
另一种解决方法 - 当您与最终用户(可能对编程知之甚少的人)讨论您的应用程序时,他们自己重复使用的词是什么?
这些是您应该围绕这些词来设计您的应用程序。
如果您尚未与潜在用户进行此转换 - 立即停止一切,在您完成之前不要编写另一行代码!只有这样,您的团队才会知道需要构建什么。
我对财务软件一无所知,但如果我不得不猜测的话,我会说一些资源可能会以“报告”、“付款”、“转账”和“货币”等名称命名。
关于软件设计过程的这一部分,有很多好书。我可以推荐的两个是 Domain Driven Design 和 Analysis Patterns。
URL 的设计与您的应用程序是否为 RESTful 无关。因此,短语“RESTful URLs”是无稽之谈。
我认为您应该更多地阅读 REST 实际上是什么。 REST 将 URL 视为不透明的,因此不知道其中有什么,是否有动词或名词等等。您可能仍想设计您的 URL,但那是关于 UI,而不是 REST。
也就是说,让我们来回答您的问题:最后两种情况不是 RESTful 并且不适合任何类型的 RESTful 方案。这些就是你可能称之为 RPC 的东西。如果您对 REST 很认真,您将不得不重新考虑您的应用程序是如何从头开始工作的。要么放弃 REST,要么将您的应用程序作为 RPC 应用程序。
嗯,也许不是。
这里的想法是您必须将所有内容视为资源,因此一旦一组参数有一个您可以引用它的 URL,您只需添加:
GET [parametersurl]/validationresults
POST [paramatersurl]
body: {command:"activate"}
但同样,激活的东西是 RPC,而不是 REST。
激活和验证要求是您尝试更改资源状态的情况。将订单“完成”或其他请求“提交”没有什么不同。有很多方法可以对这些状态变化进行建模,但我发现一种通常有效的方法是为相同状态的资源创建集合资源,然后在集合之间移动资源以影响状态。
例如创建一些资源,例如,
/ActiveParameters
/ValidatedParameters
如果要使一组参数处于活动状态,则将该组添加到 ActiveParameters 集合中。您可以将参数集作为实体主体传递,也可以将 url 作为查询参数传递,如下所示:
POST /ActiveParameters?parameter=/Parameters/{Id}
使用 /ValidatedParameters 可以完成相同的操作。如果参数无效,则服务器可以向请求返回“错误请求”,以将参数添加到已验证参数的集合中。
我会建议以下 Meta 资源和方法。
激活参数和/或验证它们:
> PUT /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Content-Type: application/json
> Connection: close
>
> {'active': true, 'require-valid': true}
>
< HTTP/1.1 200 OK
< Connection: close
<
检查参数是否有效:
> GET /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Connection: close
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Connection: close
<
< {
< 'active': true,
< 'require-valid': true,
< 'valid': {'status': false, 'reason': '...'}
< }
<
看到 10 多年后没有答案真正说明如何在 REST 架构中设计 OP 中要求的这种东西,我感到有点难过,因此我觉得现在有必要这样做。
首先,什么是 REST?!首字母缩略词 REST 或 ReST 代表“Representational State Transfer”,并以某种表示格式定义资源状态的交换。表示格式趋向于协商的媒体类型。在 application/html
的情况下,表示格式可能是在浏览器中呈现的 HTML 格式文本内容流,可能是在应用一些样式表格式以将某些元素定位在某些位置之后。
REST 原则上是我们都知道的可浏览 Web 的概括,尽管它针对各种应用程序而不仅仅是浏览器。因此,通过设计,适用于 Web 的相同概念也适用于 REST 架构。诸如如何以“RESTful”方式实现某事的问题围绕回答如何在 Web 页面上实现某事然后将相同的概念应用到应用程序层的问题来解决。
基于 Web 的计算器通常从一些“页面”开始,允许您在将输入的数据发送到服务器之前输入一些值进行计算。在 HTML 中,这通常是通过 HTML <form>
元素实现的,该元素向客户端介绍要设置的可用参数、将请求发送到的目标位置以及在发送输入数据时应用的表示格式。这可以看起来像这样:
<html>
<head>
...
</head>
<body>
<form action="/../someResource" method="post" enctype="application/x-www-form-urlencoded">
<label for="firstNumber">First number:</label>
<input type="number" id="firstNumber" name="firstNumber"/>
<label for="secondNumber">Second number:</label>
<input type="number" id="secondNumber" name="secondNumber"/>
<input type="submit" value="Add numbers"/>
</form>
</body>
</html>
上面的示例 ie 声明有两个输入字段可以由用户或其他自动机填写,并且在调用提交输入元素时,浏览器负责将输入数据格式化为 application/x-www-form-urlencoded
表示格式通过指定的 HTTP 请求方法(在本例中为 POST
)发送到提到的目标位置。如果我们在 firstNumber
输入字段中输入 1
并在 secondNumber
输入字段中输入 2
,浏览器将生成 firstNumber=1&secondNumber=2
的表示并将其作为实际请求的正文负载发送到目标资源.
因此,向服务器发出的原始 HTTP 请求可能如下所示:
POST /../someResource
Host: www.acme.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Accept: application/html
firstNumber=1&secondNumber=2
服务器可以执行计算并使用包含计算结果的另一个 HTML 页面进行响应,因为请求表明客户端理解这种格式。
正如 Breton 已经指出的那样,没有“RESTful” URL 或 URI 这样的东西。 URI/URL 是它自己的东西,不应该向客户端/用户传达任何含义。在上面的计算器示例中,用户根本不感兴趣将数据发送到哪里,只对触发提交输入字段时发送请求感兴趣。服务器应该已经提供了执行任务所需的所有必需信息。
浏览器也可能不知道该请求实际上是在为计算器提供一些输入参数,它也可能是某种订单表单,它只返回下一个表单表示以继续订购过程或某种完全不同的类型资源。在这种情况下,它只是执行 HTML 规范要求的内容,它并不关心服务器实际在做什么。这个概念使浏览器能够使用相同的表示格式来执行各种操作,例如从您喜欢的在线商店订购一些东西、与您最好的朋友聊天、登录在线帐户等等。
某些元素的 affordance(例如在通常呈现为按钮的提交输入字段案例中)定义了您应该使用它做什么。对于按钮或链接,它基本上会告诉您单击它。其他元素可能传达不同的启示。这种可供性也可以通过 link-relations 表示,即用 preload
注释链接基本上告诉客户端它已经可以在后台加载链接资源的内容,因为用户很可能接下来会抓取该内容。这样的链接关系当然应该标准化或遵循Web linking定义的关系类型的扩展机制。
这些是在 Web 上使用的基本概念,也应该在 REST 架构中使用。根据“Uncle Bob”Robert C. Martin an architecture is about intent 的说法,REST 架构背后的意图是将客户端与服务器分离,以允许服务器在未来自由发展,而不必担心它们会破坏客户端。不幸的是,这需要大量的纪律,因为很容易引入耦合或添加快速修复解决方案来完成工作并继续前进。正如 Jim Webber 在 REST 架构中指出的那样,作为服务提供者,您应该尝试设计一个 domain application protocol similar to a text based computer game of the 70s,客户端将一直执行到流程结束为止。
不幸的是,许多所谓的“REST”API 在现实中所做的一切都是如此。您会看到在 API 特定的外部文档中指定的主要基于 JSON 的数据的交换,通常很难动态地动态集成。请求所需的格式也被硬编码到外部文档中,这导致大量实现将 URI 解释为 return predefined typs,而不是使用一些预先协商的通用表示格式。这可以防止服务器发生变化,因为客户端现在期望接收预定义 URI 的某种数据格式(注意不是表示格式!)。这种自定义数据格式交换进一步阻止了客户端与其他 API 交互,因为“数据格式”通常是针对特定 API 的。我们从过去的 RPC 技术(例如 Corba、RMI 或 SOAP)中知道这个概念,我们谴责它们在某种程度上是邪恶的,尽管 Peppol 最近再次使用它,将 AS2 替换为 AS4 作为默认传输协议。
关于提出的实际问题,将数据作为 csv 文件发送与使用 application/x-www-form-urlencoded
表示或类似的东西没有什么不同。 Jim Webber 明确表示,毕竟HTTP is just a transport protocol whose application domain is the transfer of documents over the Web。客户端和服务器至少都应该支持 RFC 7111 中定义的 text/csv
。这个 CSV 文件可以作为处理媒体类型的结果生成,该媒体类型定义了表单元素、将请求发送到的目标元素或属性以及执行配置上传的 HTTP 方法。
有几种媒体类型支持 HTML、HAL Forms、halform、ion 或 Hydra 等形式。不过,我目前不知道有一种媒体类型可以自动将输入数据直接编码为 text/csv
,因此可能需要在 IANA's media type registry 中定义和注册。
我猜完整参数集的上传和下载应该不是问题。如前所述,目标 URI 不相关,因为客户端只会使用 URI 来检索要处理的新内容。按营业日期过滤也应该不难。然而,这里服务器应该为客户端提供客户端可以选择的所有可能性。近年来,GraphQL 和 RestQL 不断发展,它们引入了一种类似 SQL 的语言,可以针对某个端点来获得过滤的响应。然而,在真正的 REST 意义上,这违反了 REST 背后的理念,因为 a) GraphQL 即仅使用单个端点,这会以某种方式阻止缓存的最佳使用 b) 需要了解可用字段,这可能会导致引入客户端耦合到资源的基本数据模型。
激活或停用某些配置参数只需触发提供此功能的超媒体控件即可。在 HTML 表单中,这可能是一个简单的复选框或列表中的多行选择或类似的。根据表单和它定义的方法,它可能会通过 PUT
发送整个配置,或者对所做的更改很聪明,只通过 PATCH
执行部分更新。后者基本上需要计算对更新表示的更改表示,并为服务器提供所需的步骤以将当前表示转换为所需的表示。根据 PATH specification,这必须在事务中完成,以便应用所有步骤或不应用任何步骤。
HTTP 允许并鼓励服务器在应用更改之前预先验证收到的请求。对于 PUT,规范规定:
源服务器应该验证 PUT 表示是否与服务器对目标资源的任何约束一致,这些约束不能或不会被 PUT 更改。当源服务器使用与 URI 相关的内部配置信息来设置 GET 响应中表示元数据的值时,这一点尤其重要。当 PUT 表示与目标资源不一致时,源服务器应该通过转换表示或更改资源配置使它们保持一致,或者以包含足够信息的适当错误消息进行响应,以解释表示不合适的原因。建议使用 409(冲突)或 415(不支持的媒体类型)状态代码,后者特定于对 Content-Type 值的约束。例如,如果目标资源配置为始终具有“text/html”的 Content-Type 并且被 PUT 的表示具有“image/jpeg”的 Content-Type,则源服务器应该执行以下操作之一:重新配置目标资源以反映新的媒体类型;湾。将 PUT 表示转换为与资源一致的格式,然后将其保存为新的资源状态;或者,c。使用 415(不支持的媒体类型)响应拒绝请求,指示目标资源仅限于“text/html”,可能包括指向不同资源的链接,该链接将成为新表示的合适目标。 HTTP 没有准确定义 PUT 方法如何影响源服务器的状态,超出了用户代理请求的意图和源服务器响应的语义所能表达的范围。 ...
总结这篇文章,您应该使用现有的媒体类型,它允许您向客户端介绍所需或支持的输入参数、将请求发送到的目标位置、要使用的操作以及媒体类型请求必须格式化,或者定义您自己在 IANA 注册的请求。如果您想将输入转换为 text/csv
,然后将 CSV 表示上传到服务器,则后者可能是必需的。验证应在更改应用于资源之前进行。实际的 URI 不应该与客户端相关,除了确定将请求发送到哪里,因此可以由您(服务实现者)自由选择。通过执行这些步骤,您几乎可以获得随时更改服务器端的自由,并且如果客户端支持使用的媒体类型,则客户端不会因此而中断。
编辑: 实际上,URI 会阻止 GET
请求保持幂等性。
然而,对于验证,使用 HTTP 状态代码来通知请求的有效性(创建新的或修改现有的“参数”)将适合 Restful 模型。
如果提交的数据无效并且必须在重新提交之前更改请求 (HTTP/1.1 Status Codes),请使用 400 Bad Request
状态代码进行报告。
不过,这依赖于在提交时进行验证,而不是像在您的用例中那样推迟它。其他答案对这种情况有合适的解决方案。
在 REST 环境中,每个 URL 都是唯一的资源。你的资源是什么?财务计算器确实没有任何明显的资源。您需要深入了解您正在调用的参数并提取资源。例如,贷款的摊销日历可能是一种资源。日历的 URL 可能包括开始日期、期限(以月或年为单位)、期间(复利时)、利率和初始本金。有了所有这些值,您就有了一个特定的付款日历:
http://example.com/amort_cal/2009-10-20/30yrsfixed/monthly/5.00/200000
现在,我不知道您在计算什么,但是您的参数列表概念听起来不像 RESTful。正如其他人所说,您的上述要求听起来更像是 XMLRPC。如果您尝试使用 REST,则需要名词。计算不是名词,它们是作用于名词的动词。你需要把它转过来把名词从你的计算中拉出来。