搜索如何适合RESTful接口?


137

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

  • GET-列表收集或检索元素
  • PUT-替换集合或元素
  • POST-创建集合或元素
  • DELETE-好吧,erm,删除集合或元素

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

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

  • 获取个人招聘广告
    • GETdomain/Job/{id}/
  • 创建招聘广告
    • 发布domain/Job/
  • 更新职位广告
    • PUTdomain/Job/
  • 删除招聘广告
    • 删除domain/Job/

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

  • GETdomain/Jobs/

但是,工作“搜索”如何落入这种结构?

可以声称这是“列表集合”的变体,并实现为:

  • GETdomain/Jobs/

但是,搜索可能很复杂,完全有可能产生一个生成长GET字符串的搜索。也就是说,在这里引用SO问题,使用GET字符串的长度超过2000个字符存在一些问题。

一个示例可能是多面搜索-继续“工作”示例。

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

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

通过将此示例调整为简历而不是职位,可以带来更多的方面,您可以很容易地想象出搜索时选择了100个方面,甚至只是40个方面(每个方面50个字符)(例如,职务,大学名称,雇主名称)。

在那种情况下,可能需要移动PUT或POST,以确保将正确发送搜索数据。例如:

  • 发布domain/Jobs/

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

可以说这将表示为搜索的创建:

  • 发布domain/Jobs/Search/

或(如下面的燃烧语法所建议)

  • 发布domain/JobSearch/

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

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

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


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

是的,我可以看到对于一个简单的示例来说,这没有问题。但是,在我们正在构建的工具中,实际上要进行复杂的搜索会导致GET字符串的长度超过2000个字符,这并非令人难以置信。然后怎样呢?
Rob Baillie 2014年

其实是非常好的一点。指定压缩技术呢?
Knerd 2014年

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

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

Answers:


93

您不应忘记GET请求比其他解决方案具有一些优越的优势

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

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

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

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

a)您应该能够使用聪明的参数结构将其放入GET中。在极端情况下,您甚至可以采用这种Google搜索之类的策略,在该方法中,我设置了很多参数,但仍然是超短网址。

b)在您的应用程序中创建另一个实体,例如JobSearch。假设您有很多选择,那么很可能您也将需要存储这些搜索并进行管理,因此只需清除您的应用程序即可。您可以将JobSearch对象作为一个整体来使用,这意味着您可以对其进行测试 / 轻松使用。


就我个人而言,我会尽力与所有的爪子搏斗,以完成a)的工作,而当所有的希望都消失了时,我会带着眼泪向后爬回选项b)


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

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

在B点,这是我自己参考了一个简单的变化POSTdomain/Jobs/Search/,可能与domain/JobsSearch/替代,还是你的意思是不同的东西?你能澄清一下吗?
Rob Baillie 2014年

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

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

12

TL; DR:GET用于过滤,POST用于搜索

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

在考虑将要返回的内容时,这往往会得到加强。我通常仅在资源具有几乎完整的生命周期(PUT,DELETE,GET,集合GET)的情况下使用GET。通常,在集合GET中,我将返回URI列表,这些URI是组成该集合的REST资源。在一个复杂的查询中,我可能会从多个资源中提取资源以构建响应(认为​​是SQL连接),所以我将不会发送回URI,而是实际的数据。问题是数据将不会在资源中表示,因此我将始终必须返回数据。在我看来,这似乎是需要POST的明确案例。

-

已经有一段时间了,我的原始帖子有点草率,所以我想我会更新。

GET是返回大多数数据,REST资源的集合,资源的结构化数据甚至单个有效载荷(图像,文档等)的直观选择。

POST是适用于GET,PUT,DELETE等似乎不适合的所有内容的综合方法。

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

我还考虑了API使用的经验方面。我通常希望使大多数方法尽可能易于使用和直观。我将更灵活(因而更复杂)的调用推送到POST中,并推送到不同的资源URI上,尤其是在与同一API中其他REST资源的行为不一致的情况下。

无论哪种方式,一致性可能都比在GET或POST中进行搜索更为重要。

希望这可以帮助。


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

“过滤”和“搜索”之间没有区别。
尼古拉斯·香克斯

1
是的,有一个过滤器是基于现有字段的。一个搜索可能包含更为复杂的模式,结合领域,计算adjecent值等
user13796

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

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

10

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

  • 将搜索资源视为收集资源很有用。查询参数(有时称为URI的可搜索部分)将资源缩小到客户端感兴趣的项目。

例如,主要的Google URI指向“到Internet上每个站点的链接”的收集资源。查询参数将其限制为您要查看的站点。

(URI =通用资源标识符,其中URL =通用资源定位符,其中熟悉的“ http://”是URI的默认格式。因此URL是定位符,但是在REST中将其概括为资源标识符是很好的但是,人们可以互换使用它们。)

  • 由于您在示例中搜索的资源是Jobs集合,因此使用

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

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

然后使用POST(即添加或处理动词)将新项目添加到该集合中:

POST网站/职位

{工作:...}

  • 请注意,job在每种情况下,对象的结构都是相同的。客户端可以使用查询参数来缩小搜索范围,然后使用相同的格式对其中一项进行发布新作业,以获取作业的集合。或者,可以将其中一项添加到其URI中以更新该项。

  • 对于非常长或复杂的查询字符串,约定可以将其作为POST请求发送。将查询参数捆绑为名称/值对,或将嵌套对象捆绑为JSON或XML结构,然后将其发送到请求的正文中。例如,如果您的查询具有嵌套数据,而不是一堆名称/值对。POST的HTTP规范将其描述为附加或流程动词。(如果您想让一艘战舰驶过REST中的漏洞,请使用POST。)

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

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

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

  • 您可以通过在集合的名称中添加一些内容来在URI中进行操作,例如用job-search代替jobs。这只是意味着您将搜索集合视为单独的资源。

  • 由于POST的语义都是追加或过程,因此您可以使用有效载荷来标识搜索主体。例如{job:...}与{search:...}。由POST逻辑适当地发布或处理它。

这几乎是一个设计/实现偏好。我认为没有明确的约定。

因此,就像您已经布置了一样,想法是为 jobs

网站/职位

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

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

(另外,我应该说,从您的问题来看,很显然您还有很长的路要走。我明确地将内容拼写出来只是为了将它们排列起来,但是您的问题已经解决了其中的大部分语义问题。回答。我只是将其与一些惯例和惯例结合在一起。)


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

在实现方面(我们使用Node和Express),这可能意味着route无法真正处理选择。我不得不看一下...
Rob Baillie 2014年

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

8

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

您使用诸如$select=和的标记,$filter=因此最终会得到一个类似于以下内容的URI:

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

您还可以使用$skip$top和排序进行分页。

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


6
一个有趣的链接,值得看的,但它解决所描述的根本问题,即GET请求不支持查询字符串超过2000个字符,这是完全有可能的查询可能比这更长的时间?
Rob Baillie 2014年

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

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

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

1
我懂了。另一个有趣的想法可能有些用处,尽管我不确定这对我的情况是否有帮助-使用很多构面类型和很多可能的构
面值进行

5

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

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

ID可以再就业GET请求来使用:/jobs?filter=12345GET过滤器资源上的后续请求将返回过滤器的定义。

这种方法的优势在于,它使您摆脱了用于过滤器定义的查询参数格式的束缚,从而有可能为您提供更多定义复杂过滤器的能力。我可以想到OR条件是使用查询字符串很难完成的一个示例。

这种方法的一个缺点是您失去了URL的可读性(尽管通过GET请求过滤器资源来检索定义可以缓解这种情况)。因此,您可能还希望支持与/jobs过滤器资源相同或部分的查询参数。这可以用于较短的查询。如果提供了此功能,为了维持两种过滤类型之间的可缓存性,当在/jobs资源上使用查询参数时,实现应在内部创建/重用过滤器资源,并以302或返回303状态URL 或/jobs?filter=12345


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

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

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

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

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


3

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

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

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

顺便说一句,我知道人们的做法有所不同。您无需向我解释违反REST有多方便。


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

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

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

“要求平等实际上意味着结果保持不变。” 我认为,要点是请求不会更改数据。
AndreiMotinga
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.