在设计RESTful接口时,请求类型的语义被认为对设计至关重要。



GET-列表集合或检索元素

PUT-替换集合或元素

POST-创建集合或元素

删除-好了,erm,删除集合或元素

但是,这没有似乎没有涵盖“搜索”的概念。

例如在设计支持求职网站的Web服务套件时,您可能有以下要求:


获取单独的求职广告




获取到domain/Job/{id}/



创建工作广告



发布到domain/Job/

>

更新职位广告



将其放入domain/Job/



删除职位广告



删除到domain/Job/




“获取所有工作”也很简单:



获取到domain/Jobs/


但是,“搜索”工作如何落入此结构? >您可以说这是“列表集合”的一种变体,并实现为:可能很复杂,并且完全有可能产生一个生成长GET字符串的搜索。也就是说,在这里引用一个SO问题,存在使用长度超过大约2000个字符的GET字符串的问题。

在多面搜索中可能有一个示例-继续“ job”示例。

我可以搜索以下方面-“技术”,“职位名称”,“学科”以及自由文本关键字,工作年龄,位置和薪水。

用户界面以及大量技术和职务,搜索可以包含大量方面的选择是可行的。

通过将此示例调整为简历而不是职位,可以带来更多的方面,您可以很容易地想象出搜索时选择了100个方面,甚至只是40个方面(每个方面50个字符)(例如,职务,大学名称,雇主名称)。在这种情况下,可能需要移动PUT或POST来确保正确发送搜索数据。例如:



发布到domain/Jobs/


但是从语义上讲,这是创建集合的指令。

您也可以说将其表达为搜索的创建:




发布到domain/Jobs/


如下面的burninggramma所建议)



发布到domain/Jobs/Search/


从表面上看似乎很有意义,但您没有实际上创建任何东西,都是在请求数据。

因此,从语义上讲,这是一个GET,但不能保证GET支持您所需要的。

因此,问题是-尝试尽可能地遵循RESTful设计,同时确保我保持在HTTP的限制内,什么是最适合搜索的设计?

评论

我经常打算使用GET domain / Jobs?keyword = {keyword}。这对我来说很好:)我的希望是,SEARCH动词将成为一个标准。 developers.stackexchange.com/questions/233158 / ...

是的,我可以看到对于一个简单的示例来说,这没有问题。但是,在我们正在构建的工具中,实际上并不是令人难以置信的是,我们最终进行的复杂搜索导致GET字符串超过2000个字符。那怎么办?

其实是非常好的一点。指定压缩技术该怎么办?

HTTP规范允许带有主体的GET,中间件可能会或可能不支持(有时不支持;),因此不建议这样做。这在Stackexchange上定期出现。 stackoverflow.com/questions/978061/http-get-with-request-body

我最终让POST JobSearch创建了一个实际的搜索实体并返回一个jobSearchId。然后GET jobs?jobSearch = jobSearchId返回实际的作业集合。

#1 楼

您不要忘记GET请求比其他解决方案具有一些优越性:

1)GET请求可以从URL栏中复制,它们被搜索引擎摘要,它们是“友好的”。 “友好”表示正常情况下,GET请求不应修改应用程序内部的任何内容(幂等)。

2)所有这些概念不仅对用户和搜索引擎非常重要,而且从架构,API设计的角度来看也非常重要。

3)如果您使用POST / PUT创建解决方法,则会遇到一些您现在不会想到的问题。例如,在浏览器的情况下,后退导航按钮/刷新页面/历史记录。这些当然可以解决,但这将是另一个解决方法,然后是另一个......

考虑所有这些,我的建议是:

a)您应该能够使用巧妙的参数结构放入GET中。在极端情况下,您甚至可以采用类似Google搜索这样的策略,其中我设置了很多参数,但仍然是一个超短网址。假设您有很多选择,那么很可能您也将需要存储这些搜索并进行管理,因此只需清除您的应用程序即可。您可以将JobSearch对象作为一个整体来使用,这意味着您可以对其进行测试/更轻松地使用它。 ),当所有的希望都消失了时,我会流着泪回到选项b)。

评论


为了澄清起见,此问题旨在解决有关Web服务设计的问题,而不是有关网站设计的问题。因此,尽管浏览器的行为在问题解释的更广泛范围内受到关注,但在所描述的特定情况下,这无关紧要。 (不过有趣的一点)。

–Rob Baillie
2014年3月21日在13:02

@RobBaillie是的,浏览器只是一个用例。我想表达一个事实,即您的整个搜索都由URL字符串表示。答案后面的其他要点在可用性方面非常舒适。

– p1100i
2014年3月21日13:23



为什么我会觉得REST通常是问题的一部分,而不是解决方案的一部分?

– JensG
2014年3月27日在16:47



当GET为幂等时,“ GET请求不应修改应用程序内部的任何内容(幂等)”,此处的相关单词为“安全”。幂等意味着对资源执行两次GET与对资源执行一次GET相同。例如,PUT也是幂等的,但并不安全。

–茉莉花
17年2月15日在3:07

@NicholasShanks:是的,我没有。尝试将RPC语义应用于REST的所有工作人员都不会失败,然后失败,然后去这个网站,问仅是由于他们对REST是什么,更重要的是对REST不是什么的误解而引起的问题。菲尔丁本身已经对该主题发表了一些评论。是的,可能是我...警告:此评论可能包含讽刺意味。

– JensG
18年5月3日在8:52



#2 楼

TL; DR:使用GET进行过滤,使用POST进行搜索

我区分过滤列出集合的结果与进行复杂的搜索。我使用的石蕊试纸基本上是如果我需要的不仅仅是过滤(正,负或范围)测试,我认为它是一个更复杂的搜索,需要POST。

考虑返回什么时,这种趋势往往会得到加强。我通常仅在资源具有几乎完整的生命周期(PUT,DELETE,GET,集合GET)的情况下使用GET。通常,在集合GET中,我将返回URI列表,这些列表是构成该集合的REST资源。在一个复杂的查询中,我可能会从多个资源中提取资源以构建响应(认为是SQL连接),因此我将不会发送回URI,而是实际的数据。问题是数据将不会在资源中表示,因此我将始终必须返回数据。在我看来,这是一个需要POST的明确案例。 br />
GET是返回大多数数据,REST资源的集合,资源的结构化数据甚至单个有效载荷(图像,文档等)的直观选择。

POST对于似乎不适合GET,PUT,DELETE等的所有内容,它是一种万能的方法。

在这一点上,我认为简单的搜索,过滤确实可以通过GET进行。复杂的搜索取决于个人喜好,尤其是当您使用聚合函数,互相关(联接),重新格式化程序等时。我认为GET参数不应过长,而且它是一个比较大的查询(但是它是结构化的) )通常更适合作为POST请求正文。

我还考虑了API使用的经验方面。我通常希望使大多数方法尽可能易于使用和直观。我将更灵活(因此更复杂)的调用推送到POST中,并推送到不同的资源URI上,尤其是如果它与同一API中其他REST资源的行为不一致时。 ,一致性可能比您在GET或POST中进行搜索更为重要。

希望这会有所帮助。

评论


由于REST旨在抽象化基础实现(例如,资源不一定是数据库中的行或硬盘上的文件,而是任何东西),我不知道使用POST执行SQL连接时为GET。假设您有一个学校表和一个孩子表,并且想要一堂课(一个学校,多个孩子)。您可以轻松定义虚拟资源和GET / class?queryParams。从用户的角度来看,“类”始终是一件事情,您不必进行任何奇怪的SQL连接。

–stevendesu
15年8月5日在17:17

“过滤”和“搜索”之间没有区别。

–尼古拉斯·申克斯(Nicholas Shanks)
18年5月1日晚上10:10

是的,有一个过滤器是基于现有字段的。搜索可能包含更复杂的模式,组合字段,计算相邻值等。

–user13796
19年1月21日在19:07

完全是@stevendesu,这就是为什么我都使用POST(创建搜索)的原因:-)

– ymajoros
19年1月28日在6:50

@ymajoros除非您将搜索词和搜索结果保存在某个地方,否则我不知道POST在语义上是有意义的。当您执行搜索时,您是在索取信息,而您并没有提供要保留在任何地方的新信息。

–stevendesu
19年1月29日在13:52

#3 楼

在REST中,资源定义非常广泛。但是实际上,您确实希望捆绑一些数据。


将搜索资源视为收集资源很有用。查询参数(有时称为URI的可搜索部分)将资源缩小到客户端感兴趣的项目。例如,主Google URI指向“链接到互联网上的每个站点”。查询参数将其范围缩小到您要查看的站点。

(URI =通用资源标识符,其中URL =通用资源定位符,其中常见的“ http://”是URI。因此URL是一个定位器,但是在REST中将其概括为资源标识符是很好的。尽管人们可以互换使用它们。您的示例是Jobs集合,可以使用


GET site / jobs?type = blah&location = here&etc = etc

(返回){jobs :[{job:...}]}


,然后使用POST,它是附加动词或过程动词,可向该集合添加新项目:


POST站点/作业

{job:...}



请注意,它与job对象的结构相同在每种情况下。客户端可以使用查询参数来缩小搜索范围,然后使用相同格式的项之一来发布新作业,从而获得作业的集合。或者,可以将其中一项添加到其URI中以更新该项。
对于非常长或复杂的查询字符串,约定可以将其作为POST请求发送。将查询参数捆绑为名称/值对,或者将嵌套的对象捆绑为JSON或XML结构,然后将其发送到请求的正文中。例如,如果您的查询具有嵌套数据,而不是一堆名称/值对。 POST的HTTP规范将其描述为附加或流程动词。 (如果要在REST漏洞中驾驶战舰,请使用POST。)

不过,我会将其用作备用计划。

尽管这样做,您会失去的是a)GET是无效的-也就是说,它不会改变任何内容-POST不是。因此,如果调用失败,则中间件将不会自动重试或缓存结果,并且2)正文中带有搜索参数,您将无法再剪切和粘贴URI。也就是说,URI并不是您想要的搜索的特定标识符。

区分“创建”与“搜索”。有两个与REST惯例一致的选项:


您可以在URI中通过在集合名称中添加一些内容来实现此目的,例如用工作搜索代替工作。这只是意味着您将搜索集合视为一个单独的资源。
由于POST的语义都是添加或过程,因此可以使用有效负载来标识搜索主体。就像{job:...} vs. {search:...}。适当地发布或处理它取决于POST逻辑。

这在很大程度上是设计/实现的偏好。我认为没有明确的约定。

所以,就像您已经布局的那样,想法是为jobs定义一个集合资源。 site / jobs


使用GET +查询参数进行搜索以缩小搜索范围。较长或结构化的数据查询进入POST的正文中(可能到单独的搜索集合中)。使用POST创建以追加到集合。并使用PUT更新到特定的URI。

(FWIW URI的样式约定是将所有小写字母与连字符分隔的单词一起使用。但这并不意味着您必须这样做。)

(另外,我要说的是,从您的问题来看,很显然,您还有很长的路要走。我明确地将内容拼写出来只是为了排队,但您的问题已经解决了此答案中的大多数语义问题。我只是将其与一些约定和惯例结合在一起。)

评论


这是一个有趣的想法-我不会考虑使用有效载荷来区分。似乎有点不足!但是我想URI方案实际上不包含任何动词-定义动词的是请求类型。也许有效负载在语义上比URI更接近请求类型。唯一需要关注的是-对API用户透明吗?

–Rob Baillie
2014年3月21日15:04

在实现方面(我们使用的是Node和Express),这可能意味着路由无法真正处理选择的处理。我得看一看...

–Rob Baillie
2014年3月21日15:06

我有同样的直觉,用URI分隔似乎更干净。我有点来回走;这是一个判断电话。但是,HTTP的语义允许将其放入正文中。我想说的是,REST是根据万维网建模的,而WWW是使用GET和POST构建的。

– sea-rob
2014年3月21日15:11



#4 楼

我通常使用OData查询,它们作为GET调用运行,但允许您限制返回的属性并对其进行过滤。看起来像这样:

/users?$select=Id,Name$filter=endswith(Name, 'Smith')


还可以使用$select=$filter=进行分页并订购。

有关更多信息,请查看OData。 .org。您尚未指定使用哪种语言,但是如果使用的是ASP.NET,则WebApi平台支持OData查询-对于其他语言(PHP等),您可能可以使用库将其转换为数据库查询。

评论


一个有趣的链接,值得一看,但是它能否解决所描述的基本问题,即GET请求在查询字符串中不支持超过2000个字符,并且完全有可能查询长度比此更长?

–Rob Baillie
2014年3月21日在12:28

@RobBaillie我不这样认为,因为它仍然是带有查询字符串的GET调用。我建议您尽可能使用OData,因为它是查询Web数据源的标准,并且在少数(如果有的话)查询需要非常复杂以至于无法容纳2000个字符的查询中,请创建一个特定的进行GET调用的端点

–特雷弗·皮利(Trevor Pilley)
2014年3月21日在13:19

您能解释一下“向GET调用的特定端点”的方法吗?您如何想象该端点将看起来如何?

–Rob Baillie
2014年3月21日在13:33

@RobBaillie确定-再次,我不确定您使用的是哪种技术,但是在ASP.NET中,我将创建一个名为JobsNearMeAddedInTheLast7Days的特定控制器或任何封装对于OData来说太长/复杂的查询,然后仅通过GET公开它电话。

–特雷弗·皮利(Trevor Pilley)
2014年3月21日在13:36

我懂了。另一个有趣的想法可能有一些方面,尽管我不确定这在我的特定情况下是否会有所帮助-使用很多方面类型和很多可能的方面值进行分面搜索

–Rob Baillie
2014年3月21日在14:03

#5 楼

这是一个旧的答案,但是我仍然可以为讨论做些贡献。我经常观察到对REST,RESTful和体系结构的误解。 RESTful从来没有提到过关于不构建搜索的内容,RESTful中没有关于体系结构的东西,它是一组设计原则或标准。

为了更好地描述搜索,我们不得不谈论尤其是一种架构,最适合的架构是面向资源的架构(ROA)。

在RESTful中有一些设计原则,幂等并不意味着结果无法随着我在某些答案中的阅读而改变,这意味着独立请求的结果并不取决于执行多少次。它可以改变,让我们想象一下我正在不断更新一个数据库,该数据库向其提供由RESTful Api提供的一些数据,执行相同的GET可能会改变结果,但是它并不取决于执行了多少次。如果我能够冻结世界,这意味着当我请求导致不同结果的资源时,服务中没有状态,转换或任何内容。


根据定义,资源本身就是要被引用为重要的任何事物。


在面向资源的体系结构中(为了简洁起见,我们从现在起将其称为ROA),我们专注于资源可能是很多东西:


文档的版本
文档的最新更新版本
来自搜索的结果
对象列表
我从电子商务中购买的第一篇文章

它在资源方面的独特之处在于可扩展性,这意味着它只有一个URI

这样,考虑到ROA,该搜索就非常适合RESTful。我们必须使用GET,因为我假设您的搜索是常规搜索,并且不会改变任何内容,因此它是幂等的(即使它根据添加的新元素返回不同的内容)。这样会造成混乱,因为我可以坚持使用RESTful而不是ROA,这意味着我可以遵循一种创建搜索并使用相同参数返回不同内容的模式,因为我没有使用ROA的可寻址性原则。那个怎么样?好吧,如果您在正文或标题中发送搜索过滤器,则该资源不可寻址。

您可以在W3原始文档中找到有关确切内容和URI的原理:

https://www.w3.org/DesignIssues/Axioms

这种架构中的任何URL都必须具有自我描述性。如果您遵循原则来解决URI中的所有内容,则很有必要,这意味着您可以使用/(斜杠)分隔所需的内容或查询参数。我们知道这是有局限性的,但这是体系结构模式。

在RESTful中遵循ROA模式,搜索并不比任何其他资源都多,唯一的区别是资源来自计算而不是计算与对象本身的直接关系。基于该原理,我可以基于以下模式解决并获得一种简单的算术计算服务:

http://myapi.com/sum/1/2

其中总和,1和2可以修改,但是计算的结果是唯一的并且是可修改的,每次我使用相同的参数进行调用时,我获得相同的值,并且服务中没有任何变化。 / sum / 1/2和/ substract / 5/4的资源完全遵循原则。

#6 楼

考虑的一种方法是将可能的查询集视为集合资源,例如/jobs/filters

POST使用主体中的查询参数对此资源的请求将创建新资源或标识现有的等效过滤器,并返回包含其ID的URL:/jobs/filters/12345

然后可以在GET作业请求中使用该ID:/jobs?filter=12345。筛选器资源上的后续GET请求将返回筛选器的定义。

此方法的优点是,它使您摆脱了用于筛选器定义的查询参数格式,从而可能为您提供更多的功能来定义复杂的对象过滤器。 OR条件是我想到的一个很难用查询字符串完成的示例。通过GET请求过滤资源)。因此,您可能还希望在/jobs资源上支持与过滤器资源相同或部分的查询参数。这可以用于较短的查询。如果提供了此功能,则为了保持两种过滤之间的可缓存性,当在/jobs资源上使用查询参数时,实现应在内部创建/重用过滤资源,并以表格形式返回表示URL的302303状态代表/jobs?filter=12345

评论


我对此的第一反应是,尽管它提供了很好的信息,但实际上只是@burninggramma提供的答案的一种变体。本质上,它是“创建一个名为filter / search的新实体,先调用以创建它,然后再调用以检索它”。不同之处在于,检索它的调用更像是将其应用于集合的调用。有趣。但是,您和Burninggramma的答案都遇到相同的问题-我不希望创建过滤器。它们将有很多,并且除了为了保持RESTful实现之外,不需要存储它们。

–Rob Baillie
2014年3月28日在9:40



显然,查询参数是最佳解决方案,但是您的问题专门询问如何处理过滤器定义的时间长于某些服务器施加的URL限制。为了解决长度限制,您要么需要以某种方式压缩查询字符串,要么需要使用支持指定任意长度主体的请求方法。如果您不想将过滤器视为资源,则只需支持一个非静态接口即可在其中发布过滤器定义。您将失去可缓存性,但是如果数据不稳定,则无论如何都无法从缓存中受益。

– pgraham
2014年3月29日14:34

您可以通过简单地...不存储过滤器来克服存储过滤器的需要。 REST不能保证它是持久性的。您可能会请求GET / jobs / 37并收到一个结果,然后有人删除了该资源,两秒钟后,同一请求返回了404。类似地,如果您POST / searches并且将您重定向到搜索结果(搜索被创建,并且您收到带有资源的Location标头的201),两秒钟后,结果可能会从内存中清除,必须重新生成。无需长期存放。

–stevendesu
2015年8月5日在17:20

#7 楼

如果您有一个始终为一个URI返回相同结果(表示形式)的静态集合,则GET可以。这也意味着生成这些表示的数据永远不会改变。源是一个只读数据库。

使GET为一个返回不同的结果,并且相同的URI违反了幂等性/安全性和CoolURI原则,因此不是RESTful的。可以将幂等动词写入数据库,但它们绝不能影响表示形式。

常见搜索以POST请求开始,该请求返回对结果的引用。它生成结果(它是新的,可以通过后续的GET获取)。当然,此结果可以是分层的(可以获取URI的其他引用),并且如果对应用程序有意义,则可以重用早期搜索的元素。

我知道做不同的事情。您无需向我解释违反REST有多方便。

评论


Aaaaaaaah-这就是它应该如何工作的!谢谢!

–Rob Baillie
16-09-21在11:27

幂等并不意味着它必须始终返回完全相同的值,如果NOTHING发生更改,则它必须返回相同的值。搜索可以被认为是计算的结果,它本身就是一种资源。

– Maximiliano Rios
17年8月8日在4:35

幂等实际上确实意味着结果保持不变。您可以并且在实际中使用缓存控制。当然,您可以使用DELETE来干扰以后的GET。但是,如果代理需要保留有关应用程序内部工作原理的知识,则它不再是RESTful的。上面,我在谈论REST的最极端想法。在实践中,人们可能会违反它的许多方面。当缓存不再有效地缓存时,他们将为此付出代价。

– Martin Sugioarto
17 Mar 8 '17 at 7:02

“在请求之后,“幂等实际上确实意味着结果保持不变。”我认为,要点是请求不会更改数据。

– AndreiMotinga
19年8月13日在14:27