如何创建不带动词的REST URL?


283

我正在努力确定如何设计静态URL。我全力支持使用带有名词而不是动词的URL的宁静方法,因为它不知道如何做到这一点。

我们正在创建一项服务以实现财务计算器。计算器带有一系列参数,我们将通过CSV文件上传这些参数。用例将涉及:

  1. 上载新参数
  2. 获取最新参数
  3. 获取给定营业日期的参数
  4. 激活一组参数
  5. 验证一组参数

我收集的宁静方法是使用以下类型的URL:

/parameters
/parameters/12-23-2009

您可以使用以下方法实现前三个用例:

  1. POST,您将参数文件包含在发布请求中
  2. GET第一个URL
  3. 获取第二个URL

但是,如何在没有动词的情况下使用第4和第5个用例呢?您不需要以下网址:

/parameters/ID/activate
/parameters/ID/validate

??


3
我更喜欢PATCH而不是POST进行部分更新。
user2016971 2013年

Answers:


71

也许像这样:

PUT /parameters/activation HTTP/1.1
Content-Type: application/json; encoding=UTF-8
Content-Length: 18

{ "active": true }

1
POST如果您需要执行“过程”(例如每次发送请求时都要验证参数),则可以。但是,当您修改资源的(应用程序)状态时,实际上是在更新现有资源,而不是创建一些新资源或发布处理请求。
2009年

19
PUT用于创建新资源,或将新资源(全部而非部分)放置在特定URL上。我看不到PUT如何适合这种情况。
布列塔尼

30
事实上,POSTVS PUT是不是酷似insertVS updatePUT更新与给定路径相对应的资源,或创建与给定路径相对应的新资源。POST在某处创建新资源。例如,PUT /blog/posts/3/comments/5将更新适当的注释,同时POST /blog/posts/3/comments创建一个新comment资源(并应在响应中返回新资源的路径)。
yfeldblum

23
@Justice @Breton更重要的区别是,PUT幂等而POST不是。通常,您应尽可能限制所提供的内容。坚持PUT提供更多信息给服务的客户。
2009年

3
资源也可能是/ parameters / status并且请求的主体可能只是“活动”。这样,您就以某种方式将全新的资源放置到特定的URL。
卡洛斯·阿瓜约

991

良好URI设计的一般原则:

  • 不要使用查询参数来更改状态
  • 如果可以,请不要使用大小写混合的路径;小写是最好的
  • 不要在URI中使用特定于实现的扩展名(.php,.py,.pl等)
  • 不要随便使用URI 进入RPC
  • 不要限制你的URI的空间尽可能地
  • 路径段尽量短
  • 不是选/resource/resource/; 从您不使用的位置创建301重定向
  • 一个资源的子选择使用查询参数; 即分页,搜索查询
  • DO移动的东西出来的URI的,应该是在HTTP报头或身体

(注意:我没有说“ RESTful URI设计”; URI在REST中本质上是不透明的。)

HTTP方法选择的一般原则:

  • 永远不要使用GET更改状态;这是让Googlebot毁了你一天的好方法
  • 除非要更新整个资源,否则不要使用PUT
  • 除非您也可以合法地对同一URI执行GET,否则请勿使用PUT
  • 不要使用POST来检索寿命长或可能合理缓存的信息
  • 执行不操作幂等与PUT
  • 不要使用GET为尽可能
  • 优先投入使用POST有疑问时
  • 不要使用文章时,你要做的东西,感觉RPC样
  • 不要使用PUT对资源类,较大或分层
  • 优先使用删除博文,删除资源
  • 请勿将GET用于计算之类的事情,除非您的输入很大,在这种情况下,请使用POST

使用HTTP进行Web服务设计的一般原则:

  • 不要将元数据放在应放在标题中的响应主体中
  • 请勿将元数据放在单独的资源中,除非包含元数据会造成大量开销
  • 使用适当的状态码
    • 201 Created创建资源后;发送响应时资源必须存在
    • 202 Accepted 成功执行操作或异步创建资源后
    • 400 Bad Request当某人对明显伪造的数据进行操作时;对于您的应用程序,这可能是验证错误;通常为未捕获的异常保留500
    • 401 Unauthorized当有人在没有提供必要Authorization标头的情况下访问您的API 时,或其中的凭据Authorization无效时;如果您不希望通过Authorization标头获得凭据,请不要使用此响应代码。
    • 403 Forbidden 当有人以恶意方式或未经授权的方式访问您的API时
    • 405 Method Not Allowed 当某人使用POST时应该使用PUT等
    • 413 Request Entity Too Large 当某人试图向您发送不可接受的大文件时
    • 418 I'm a teapot 尝试用茶壶冲泡咖啡时
  • 不要使用缓存头时,您可以
    • ETag 当您可以轻松地将资源减少为哈希值时,标头会很好
    • Last-Modified 应该向您表明,保持资源更新的时间戳记是一个好主意
    • Cache-Control并且Expires应该被赋予明智的价值
  • 一切你能兑现在请求缓存头(If-None-ModifiedIf-Modified-Since
  • 在合理的情况下使用重定向,但是对于Web服务来说,重定向应该很少

关于您的特定问题,POST应该用于#4和#5。这些操作属于上面的“类似于RPC”的准则。对于#5,请记住POST不一定必须使用Content-Type: application/x-www-form-urlencoded。这很容易就是JSON或CSV负载。


11
413是为要发送的请求的大小而设计的,以便您可以礼貌地拒绝某人向您发送大量数据(通常与411结合使用),以便您强迫人们告诉您发送了多少数据。对于针对413的示例,我认为400是更合适的响应。
Garry Shutler

5
+1,因为这是一个很好的资源。但是,这是常规资源,不会直接解决这个问题。理想情况下,这应该包括一个带有特定答案的附加段落。
塞缪尔·内夫

@GarryShutler好的,你绝对正确。感谢您的修改。
鲍勃·阿曼

1
是的,仅在覆盖整个对象的情况下才使用PUT。但是,我声称在部分更新资源的情况下,PATCHPOST是合理的。在操作上,PATCH更加明确,但是由于并非所有客户端都能够发出PATCH请求,因此允许POST完全合适,我什至可以主张如果使用PATCH,则应始终允许POST作为后备。
Bob Aman

1
+1(共409个错误)。可以通过充分的客户端验证来解决400错误。409阐明请求本身是可以接受的并且是一致的,但是与服务器状态的某些方面(通常是并发控制,但是理论上是任何非输入约束)相冲突。
claytond

18

每当您需要新的动词时,请考虑将其转换为名词。例如,将“激活”变为“激活”,将“验证”变为“验证”。

但是仅从您编写的内容来看,我会说您的应用程序存在更大的问题。

每当提出一个称为“参数”的资源时,它都应该在每个项目团队成员的脑海中发出危险信号。“参数”实际上可以应用于任何资源;还不够具体。

“参数”到底代表什么?可能有许多不同的事物,每个事物都应该有专用的专用资源。

另一种解决方法-当您与最终用户(可能对编程了解甚少的最终用户)讨论应用程序时,他们自己反复使用哪些词?

这些是您应该在周围设计应用程序的词。

如果您尚未与潜在用户进行这种转换,请立即停止一切操作,除非您这样做,否则不要再编写其他代码!只有这样,您的团队才会知道需要构建什么。

我对金融软件一无所知,但是如果我不得不猜测,我会说某些资源可能会使用“ Report”,“ Payment”,“ Transfer”和“ Currency”之类的名称。

关于软件设计过程的这一部分,有很多不错的书。我可以推荐的两个是域驱动设计分析模式


1
这是一个很好的观点。如果您处于处理形式逻辑和推理的状态,就很容易错过。只要X以有效方式与其他零件配合在一起,X到底是什么都没有关系。人为因素逐渐消失。
布列塔尼

1
有时,我发现将单词转换为“处理资源”(例如“激活器”或“验证器”)很有用。根据RFC 2616的规定,POST可用于“向数据处理过程提供数据块...”
Darrel Miller 2009年

明白了 在这种情况下,用户确实将数据称为“参数”(或“风险参数”或类似内容)。参数列表的确包含许多不同类型的设置,但是参数始终作为一个整体(在CSV文件中)上载。
马库斯·莱昂,

@Marcus-听起来很不寻常。也许,如果您更详细地说明了您的应用程序的功能,我们将能够提供更好的建议来识别资源。
Rich Apodaca,2009年

1
“当您与最终用户讨论您的应用程序时,他们自己反复使用哪些词?” ...如果它们都是动词怎么办?XD
Amalgovinus

11

URL的设计与您的应用程序是否为RESTful无关。因此,短语“ RESTful URL”是胡说八道。

我认为您应该进一步了解REST的实际含义。REST将URL视为不透明,因此不知道其中包含什么,是否有动词或名词或其他内容。您可能仍想设计URL,但这是关于UI而不是REST的。

就是说,让我们开始一个问题:后两种情况不是RESTful的,也不适合任何类型的Restful方案。这些就是您所谓的RPC。如果您认真对待REST,则必须重新考虑应用程序的工作原理。要么放弃REST,要么将您的应用程序作为RPC应用程序进行。

嗯,也许不是。

这里的想法是,您必须将所有内容都视为资源,因此,一旦一组参数具有URL,您可以从中引用它,只需添加:

GET [parametersurl]/validationresults

POST [paramatersurl]
body: {command:"activate"}

但是,再次激活的是RPC,而不是REST。


您在这里陈述了一个有趣的观点。您能否进一步阐述一下类似RESTful的方法是什么?
poezn

我花了一些时间阅读此处的回复,我认为有些事情可能会伸张正义。他将参数对象的各个属性建模为各个资源,并使用PUT动词替换该资源在该资源中的内容。这是将每个对象的状态建模为资源的集合,而将状态修改为放置或删除或修改资源。至于验证-您只需要一个神奇地说明参数是否有效的资源,就像我在上面的回答中所述。只要没有副作用,就可以了。
布列塔尼

当然,提供的“激活”只是将单个属性设置为true。如果它必须做其他事情,那么它仍然不是RESTful的,而且我不确定如何对RESTful建模。
布列塔尼

我认为您不能说后两种情况不是RESTful的。实际上,Activate和Validate只是间接地表示资源正在状态机中变为新状态的方式。REST非常有能力对此建模。
Darrel Miller 2009年

@Darrel,我认为您指出了REST的一部分,这可能对许多REST新手来说都是挑战。您将如何实施“验证资源x”操作?我认为具有挑战性的事情是,这可能会导致状态改变,但是新状态是发出请求的结果。
肖恩

6

激活和验证需求是您尝试更改资源状态的情况。使订单“完成”或“提交”其他请求没有什么不同。有很多方法可以对这种状态变化进行建模,但我发现经常可以使用的一种方法是为相同状态的资源创建集合资源,然后在集合之间移动资源以影响状态。

例如创建一些资源,例如

/ActiveParameters
/ValidatedParameters

如果要使一组参数处于活动状态,则将其添加到ActiveParameters集合中。您可以将参数集作为实体传递,也可以将url作为查询参数传递,如下所示:

POST /ActiveParameters?parameter=/Parameters/{Id}

/ ValidatedParameters可以完成相同的操作。如果参数无效,则服务器可以向请求返回“错误请求”,以将参数添加到已验证参数的集合中。


1

我建议以下元资源和方法。

激活参数和/或验证它们:

> 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': '...'}
< }
<

据我了解,问题在于静态URL的命名,而不是功能,不是吗?
poezn

2
仅限于“ RESTful URL”的问题是一个坏问题,不应回答。相反,应该将该问题扩展为考虑“具有相关方法和URL的RESTful资源”,并以此回答。
yfeldblum

据我了解,问题在于URL命名约定命名资源应响应的HTTP方法。
2009年

1

我觉得有点悲哀地看到,经过10年以上没有答案真的,说明如何作为OP要求这样的事情可能会在REST架构来设计的,因此我觉得有必要现在就这样做。

首先,什么是REST ?!缩写REST或ReST代表“表示状态转移”,并以某种表示格式定义资源状态的交换。表示格式符合协议的媒体类型。如果是application/html表示形式,可能是在应用某些样式表格式将某些元素放置在某些位置之后,在浏览器中呈现的HTML格式的文本内容流。

REST原则上是众所周知的可浏览Web的概括,尽管它针对的是各种应用程序,而不仅针对浏览器。因此,通过设计,适用于Web的相同概念也适用于REST体系结构。诸如如何以“ RESTful”方式实现某些目标之类的问题解决了围绕以下问题的答案:如何在网页上实现某些目标,然后将相同的概念应用于应用程序层。

基于Web的计算器通常可以从一些“页面”开始,该页面允许您输入一些值以将输入的数据发送到服务器之前进行计算。在HTML中,这通常是通过HTML <form>元素实现的,HTML 元素教客户如何设置可用参数,发送请求的目标位置以及在发送输入数据时应用的表示格式。这可以看起来像这样:

<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>

上面的示例即指出,有两个输入字段可以由用户或其他一些自动机填写,并且在调用Submit输入元素时,浏览器会负责将输入数据application/x-www-form-urlencoded格式化为发送的表示形式POST在这种情况下,可以通过指定的HTTP请求方法将其定位到上述目标位置。如果我们1firstNumber输入字段和输入字段2secondNumber输入内容,浏览器将生成的表示形式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页面进行响应,因为请求表明客户端可以理解这种格式。

正如布雷顿已经指出的那样,不存在“ RESTful” URL或URI之类的东西。URI / URL是它自己的东西,不应向客户端/用户传达任何含义。在上面的计算器示例中,用户根本不感兴趣将数据发送到哪里,只是感兴趣的是,一旦触发了提交输入字段,请求就被发送了。服务器应该已经提供了执行任务所需的所有必需信息。

浏览器可能还没有意识到请求实际上是在向计算器提供一些输入参数,它也可能是某种订购单,它仅返回下一个表单表示以继续订购过程,或者某种完全不同的订购单。资源。在这种情况下,它只是执行HTML规范所要求的内容,因此它不会在乎服务器的实际操作。这个概念使浏览器可以使用相同的表示形式来完成所有事情,例如从您首选的网上商店订购商品,与您最好的朋友聊天,登录在线帐户等等。

某些元素的承受能力(例如通常在呈现为按钮的“提交输入”字段情况下)定义了您应该如何使用它。对于按钮或链接,它基本上会告诉您单击它。其他元素可以传达不同的能力。这种可承受性也可以通过链接关系来表示,即使用带preload注释的链接,这些链接基本上告诉客户端它已经可以在后台加载链接资源的内容,因为用户接下来很可能会获取该内容。当然,此类链接关系应标准化或遵循Web链接定义的关系类型扩展机制。

这些是在Web上使用的基本概念,在REST体系结构中也应使用。据“鲍勃叔叔”罗伯特·C·马丁(Robert C. Martin)称,架构与意图有关,REST架构背后的意图是使客户端与服务器脱钩,以使服务器在将来自由发展,而不必担心会破坏客户端。不幸的是,这很容易受到约束,因为引入耦合或添加快速解决方案很容易完成工作并继续进行。正如吉姆·韦伯(Jim Webber)在REST体系结构中指出的那样,作为服务提供者,您应尝试设计类似于客户端将遵循的70年代基于文本的计算机游戏域应用程序协议,直到客户到达过程结束为止。

不幸的是,现实中有很多所谓的“ REST” API只是在做这些。您会看到在特定于API的外部文档中指定的大多数基于JSON的数据交换,这些文档通常很难动态集成。请求的外观格式也被硬编码到外部文档中,这导致大量实现解释URI以返回预定义的类型而不是使用预先协商的一些常见表示格式。这样可以防止服务器更改,因为客户端现在希望接收预定义URI的某种数据格式(注意,不是表示格式!)。这种自定义数据格式交换还可以防止客户端与其他API进行交互,因为“数据格式”通常是特定API的潮流。我们从过去的RPC技术(例如Corba,RMI或SOAP)中知道了这个概念,尽管Peppol最近通过将AS2替换为AS4作为默认传输协议而再次使用它,但我们还是谴责它是邪恶的。

关于实际提出的问题,将数据作为csv文件发送与使用application/x-www-form-urlencoded表示形式或类似内容没什么不同。吉姆•韦伯(Jim Webber)明确指出,毕竟HTTP只是一种传输协议,其应用领域是通过Web进行文档传输。客户端和服务器至少应同时支持RFC 7111中text/csv定义的内容。可以通过处理定义了表单元素的媒体类型,将请求发送到的目标元素或属性以及执行配置上载的HTTP方法来生成此CSV文件。

有几种支持表单的媒体类型,例如HTMLHAL FormshalformionHydra。不过,我目前尚不了解一种可以自动将输入数据text/csv直接编码为媒体的媒体类型,因此可能需要定义一个媒体类型并向IANA的媒体类型注册中心注册

我猜,完整参数集的上传和下载应该不是问题。如前所述,目标URI不相关,因为客户端将仅使用URI来检索要处理的新内容。按营业日期进行过滤也不难。但是,服务器在此应具有客户端可以选择的所有可能性的客户端。近年来,GraphQL和RestQL不断发展,它们引入了类似SQL的语言,可以将其定位于某个端点以获得经过过滤的响应。但是,从真正的REST的角度来看,这违反了REST的思想,因为a)GraphQL,即仅使用单个端点,这在某种程度上阻止了缓存的最佳使用,并且b)需要了解可用字段,这可能导致引入客户端耦合资源的基础数据模型。

激活或取消激活某些配置参数仅是触发提供此功能的超媒体控件。在HTML表单中,这可以是一个简单的复选框,也可以是列表中的多行选择,也可以是此类。根据其形式和定义的方法,它可能会通过发送整个配置,PUT或者对所做的更改很精明,而仅通过进行部分更新PATCH。后者基本上需要对更新表示的变更表示进行计算,并向服务器提供所需的步骤,以将当前表示转换为所需的表示。根据PATH规范,这必须在事务内完成,以便应用所有步骤或不应用任何步骤。

HTTP允许并鼓励服务器在应用更改之前先验证收到的请求。对于PUT,规范指出:

原始服务器应验证PUT表示是否与服务器对目标资源的任何约束一致,该约束不能由PUT更改,也不会由PUT更改。当源服务器使用与URI相关的内部配置信息以便为GET响应设置表示元数据的值时,这尤其重要。当PUT表示与目标资源不一致时,源服务器应该通过转换表示或更改资源配置来使其一致,或者使用包含足够信息的适当错误消息进行响应,以解释为什么该表示不合适。建议使用409(冲突)或415(不受支持的媒体类型)状态码,

例如,如果目标资源配置为始终具有“文本/ html”的内容类型,并且表示为PUT的表示具有“图像/ jpeg”的内容类型,则源服务器应执行以下操作之一:

一个。重新配置目标资源以反映新的媒体类型;

b。将PUT表示形式转换为与资源格式一致的格式,然后再将其保存为新的资源状态;要么,

C。使用415(不支持的媒体类型)响应拒绝该请求,该响应指示目标资源仅限于“ text / html”,可能包括指向其他​​资源的链接,该链接将是新表示形式的合适目标。

HTTP并未明确定义PUT方法如何影响原始服务器的状态,超出了用户代理请求的意图和原始服务器响应的语义所能表达的范围。...

总结一下,您应该使用现有的媒体类型,该媒体类型可以让您了解所需的或受支持的输入参数,向其发送请求的目标位置,要使用的操作以及媒体的媒体类型。必须格式化请求,或者定义自己的请求,并向IANA注册。如果要将输入转换为,则可能需要后者text/csv然后将CSV表示形式上载到服务器。验证应在将更改应用于资源之前进行。实际的URI不应与客户端相关,而应确定将请求发送到何处,因此服务执行者可以自由选择。通过执行以下步骤,您几乎可以随时更改服务器端,并且如果客户端支持所用的媒体类型,客户端也不会因此而中断。


0

编辑:确实,URI将阻止GET请求保持幂等。


但是,为了进行验证,使用HTTP状态代码来通知请求的有效性(创建新的或修改现有的“参数”)将适合Restful模型。

400 Bad Request如果提交的数据无效,则使用状态代码进行报告,并且在重新提交请求之前必须更改请求(HTTP / 1.1状态代码)。

但是,这依赖于在提交时进行验证,而不是像在用例中那样将其推迟。其他答案对此场景有合适的解决方案。


URI是作为​​标识符。使用特定的URL应该没有副作用。想象一下代理将如何处理。
布列塔尼

2
或Google,就此而言。我曾经读过一个关于网上商店的故事,由于这种愚蠢行为,所有商店的产品都被Google删除了。
布列塔尼

0

在REST环境中,每个URL都是唯一的资源。你有什么资源 财务计算器确实没有任何明显的资源。您需要深入研究正在调用的参数并提取资源。例如,贷款的摊销日历可能是一种资源。日历的URL可能包括开始日期,期限(以月或年为单位),期限(在复利时),利率和初始原则。使用所有这些值,您便拥有特定的付款日历:

http://example.com/amort_cal/2009-10-20/30yrsfixed/monthly/5.00/200000

现在,我不知道您在计算什么,但是您对参数列表的概念听起来并不RESTful。正如其他人所说,您的上述要求听起来更像XMLRPC。如果您尝试使用REST,则需要名词。计算不是名词,它们是作用于名词的动词。您需要转过来将名词从计算中拉出。


5
我认为在这里使用正斜杠有点愚蠢,amort_cal?date = 2009-10-20&type = 30yrsfixed&period = monthly&rate = 5.0&initialamount = 200000怎么了?REST不在乎,只要它是资源即可。URI规范确实在乎。您如何想象相对链接可以使用这样的方案?
布列塔尼

尽管如此,您还是提出了一个好观点。这些“参数”是否甚至需要存储在服务器端?如果只是一次性计算,为什么不创建一个虚拟空间,其中的参数在URL中。只要您不更改内部状态,就可以了。
布列塔尼

1
而且“参数”不适用于“资源”。资源是具有唯一标识符的单个实体。我的网址标识单个资源。参数化的URL表示您在使用参数中选择的资源的集合。
jmucchiello

2
REST不基于“ CRUDing资源”。将所有查询参数都放入路径段并不会自动创建RESTful接口,因为现在您认为可以将每个排列称为资源。不幸的是,您没有可应用的魔术过程来识别系统中的资源。它需要精心设计,而不是机械公式。
Darrel Miller

2
再一次,REST体系结构不关心URL中的内容。该URL应该是不透明的。无论您使用正斜杠,分号还是unicode心作为分隔符,都不必担心。阅读并回答这个问题,而不是您想像我在说什么。
布列塔尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.