如何设计RESTful搜索/过滤?[关闭]


456

我目前正在用PHP设计和实现RESTful API。但是,我一直无法实现我的初始设计。

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

到目前为止,还算标准,对吗?

我的问题是第一个GET /users。我正在考虑在请求正文中发送参数以过滤列表。这是因为我希望能够指定复杂的过滤器而无需获取超长网址,例如:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

相反,我想拥有类似的东西:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

它更具可读性,并为您设置复杂的过滤器提供了极大的可能性。

无论如何,file_get_contents('php://input')没有返回请求正文的GET请求。我也尝试了http_get_request_body(),但是我使用的共享主机没有pecl_http。不确定它是否会有所帮助。

我发现了这个问题,并意识到GET可能不应该具有请求正文。这还没有定论,但他们建议不要这样做。

所以现在我不确定该怎么办。您如何设计RESTful搜索/过滤功能?

我想我可以使用POST,但这似乎并不十分RESTful。



60
小心!!!GET方法必须是IDEMPOTENT,并且必须是“可缓存的”。如果您在正文中发送信息,系统如何缓存您的请求?HTTP允许仅使用URL而不是请求正文来缓存GET请求。例如,这两个请求: example.com {test:“ some”} example.com {anotherTest:“ some2”}被缓存系统视为相同:它们都具有完全相同的URL
jfcorugedo

15
只需添加,您应该POST到/ users(集合)而不是/ user(单个用户)。
Mladen B.

1
要考虑的另一点是,大多数应用服务器都有访问日志,用于记录url,因此介于两者之间。因此,GET上可能会出现一些意外的信息泄漏。
user3206144

Answers:


397

实现RESTful搜索的最佳方法是将搜索本身视为资源。然后,因为您正在创建搜索,所以可以使用POST动词。您不必从字面上在数据库中创建内容即可使用POST。

例如:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

您正在从用户的角度创建搜索。这的实现细节无关紧要。一些RESTful API甚至可能不需要持久性。这是一个实现细节。


209
将POST请求用于搜索端点的一个重要限制是不能将其添加为书签。为搜索结果添加书签(特别是复杂的查询)可能会非常有用。
沙发和

73
使用POST进行搜索可能会破坏REST缓存约束。whatisrest.com/rest_constraints/cache_excerps
Filipe

55
搜索从本质上来说是瞬态的:数据在两个具有相同参数的搜索之间演化,因此我认为GET请求不会清晰地映射到搜索模式。相反,搜索请求应该是POST(/ Resource / search),然后您可以保存该搜索并重定向到搜索结果,例如/ Resource / search / iyn3zrt。这样,GET请求成功并且有意义。
sleblanc 2014年

32
我认为发布不是适合搜索的方法,正常GET请求的数据也会随时间变化。
想知道

82
这绝对是最糟糕的答案。我不敢相信它有这么多的支持。这个答案解释了原因:程序员
。stackexchange.com/ questions / 233164 /…

141

如果在GET请求中使用请求正文,则会违反REST原则,因为您的GET请求将无法被缓存,因为缓存系统仅使用URL。

更糟糕的是,您的URL无法添加书签,因为该URL不包含将用户重定向到此页面所需的所有信息。

使用URL或查询参数代替请求正文参数。

例如:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

实际上,HTTP RFC 7231表示:

GET请求消息中的有效负载没有定义的语义。在GET请求上发送有效内容正文可能会导致某些现有实现拒绝该请求。

欲了解更多信息,请看这里


29
从我的错误中学习-我使用接受的答案的建议(POSTING JSON)设计了一个api,但现在转向url参数。书签功能可能比您想象的重要。就我而言,需要将流量引至某些搜索查询(广告系列)。另外,使用历史记录API对URL参数更有意义。
杰克

2
这取决于其使用方式。如果要链接到基于这些参数加载页面的URL,这是有道理的,但是如果主页仅通过AJAX调用来获取基于过滤器参数的数据,则无论如何都无法为其添加书签,因为ajax调用并且没有方位。当然,您还可以为URL添加书签,当您转到该URL时会建立一个过滤器,并将其发布到ajax调用中,这样就可以正常工作。
Daniel Lorenz

@DanielLorenz为获得最佳用户体验,在这种情况下,仍应通过History API更改URL。当网站不允许使用浏览器后退功能导航到上一页时,我无法忍受。而且,如果它是标准的服务器端生成的页面,则使其成为书签的唯一方法是使用GET请求。好的查询条件似乎是最好的解决方案。
弥敦道

@Nathan我想我读错了这个答案。我正在谈论在获取中使用查询字符串参数。您永远不要在GET调用中使用主体参数,因为那将完全没有用。我在谈论的更多内容是可以使用/带有查询字符串的GET,然后在页面启动时,您可以使用这些参数来建立POST过滤器,并使用这些参数来获取数据。在这种情况下,历史记录仍然可以正常工作。
丹尼尔·洛伦兹

@DanielLorenz嗯,这很有道理。我想我误会了你的意思。
内森

70

似乎可以以RESTful方式实现资源过滤/搜索。这个想法是要引入一个称为/filters/或的新端点/api/filters/

使用此端点过滤器可以视为一种资源,因此可以通过POST方法创建。当然,这种方法可以使用body来携带所有参数,并且可以创建复杂的搜索/过滤器结构。

创建此类过滤器后,有两种方法可以获取搜索/过滤器结果。

  1. 具有唯一ID的新资源将与201 Created状态码一起返回。然后,使用该ID GET可以发出/api/users/类似的请求:

    GET /api/users/?filterId=1234-abcd
    
  2. 通过创建新过滤器后,POST它不会201 Created303 SeeOther一起回复,但会立即与Location指向的标头一起回复/api/users/?filterId=1234-abcd。此重定向将通过基础库自动处理。

在这两种情况下,都需要提出两个请求以获取过滤后的结果-这可能被认为是一个缺点,尤其是对于移动应用程序而言。对于移动应用我会使用单一POST调用/api/users/filter/

如何保留创建的过滤器?

它们可以存储在DB中,以后再使用。它们也可以存储在某些临时存储中,例如redis,并具有一些TTL,之后它们将过期并被删除。

这个想法有什么好处?

过滤器,过滤结果是可缓存的,甚至可以加为书签。


2
好吧,这应该是公认的答案。您没有违反REST原则,并且可以对资源进行长时间的复杂查询。很好,干净而且与书签兼容。唯一的附加缺点是需要存储用于创建的过滤器的键/值对以及已经提到的两个请求步骤。
dantebarba '18

2
这种方法唯一需要考虑的是,如果查询中有日期时间过滤器(或不断变化的值)。这样,存储在db(或高速缓存)中的过滤器数量将是无数的。
Rvy Pandey

17

我认为您应该使用请求参数,但前提是没有适当的HTTP标头来完成您想做的事情。在HTTP规范没有明确说,这GET不能有身体。但是,本文指出:

按照约定,当使用GET方法时,标识资源所需的所有信息都编码在URI中。HTTP / 1.1中没有用于安全交互(例如,检索)的约定,在这种约定中,客户端在HTTP实体主体中而不是在URI的查询部分中向服务器提供数据。这意味着为了安全操作,URI可能很长。


5
ElasticSearch也可以通过主体进行GET并运行良好!
塔伦·萨普拉

是的,但是它们控制服务器的实现可能不是互连网上的。
user432024

7

当我使用laravel / php后端时,我倾向于使用以下内容:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP自动将[]参数转换为数组,因此在此示例中,我将得到一个$filter变量,该变量保存过滤器的数组/对象,以及页面和我希望加载的任何相关资源。

如果您使用其他语言,这可能仍然是一个很好的约定,您可以创建一个解析器以转换[]为数组。


这种方法看起来不错,但是在URL中使用方括号可能会出现问题,请参见“ 什么字符可以在一个URL中使用一个字符”
Sky,

2
@Sky可以通过URI编码[and 来避免]。使用这些字符的编码表示来对查询参数进行分组是一种众所周知的做法。它甚至在JSON:API规范中使用
jelhan

6

如果您的初始API是否完全是RESTful(尤其是处于alpha阶段),请不要担心太多。首先让后端管道工作。您始终可以进行某种形式的URL转换/重写以将其映射出来,反复进行完善,直到获得足够稳定的东西以进行广泛的测试(“测试版”)为止。

您可以定义URI,这些URI的参数由URI本身上的位置和约定编码,并以一个您将始终映射到某物的路径作为前缀。我不了解PHP,但我会假设存在这样的设施(因为其他语言和Web框架中也存在):

.ie。使用“ param [i] = value [i]”对商店#1上的i = 1..4进行“用户”类型的搜索(使用value1,value2,value3,...作为URI查询参数的简写):

1) GET /store1/search/user/value1,value2,value3,value4

要么

2) GET /store1/search/user,value1,value2,value3,value4

或如下所示(尽管我不建议这样做,以后再介绍)

3) GET /search/store1,user,value1,value2,value3,value4

使用选项1,您可以将所有带有URI前缀的URI映射/store1/search/user到搜索处理程序(或任何PHP名称),默认情况下将在store1下搜索资源(等效于/search?location=store1&type=user

根据API记录和实施的约定,参数值1到4以逗号分隔并按该顺序显示。

选项2将搜索类型(在本例中为user)添加为位置参数#1。任一种选择只是一种外观选择。

选项3也是可行的,但我认为我不喜欢它。我认为在某些资源内进行搜索的能力应在搜索本身之前的URI本身中呈现(好像在URI中清楚地表明搜索是在资源内特定的一样)。

相对于在URI上传递参数而言,此方法的优点是搜索是URI的一部分(因此将搜索视为一种资源,一种资源的内容会随着时间的推移而会改变。)缺点是参数顺序是强制性的。

一旦执行了这样的操作,就可以使用GET,它将成为只读资源(因为您无法对其进行POST或PUT操作-GET时将对其进行更新)。它也将是仅在调用时才存在的资源。

通过将结果缓存一段时间或使用DELETE导致缓存被删除,还可以为其添加更多语义。但是,这可能与人们通常使用DELETE的目的背道而驰(并且因为人们通常使用缓存头来控制缓存)。

您如何去做将是一个设计决定,但这就是我要去做的方式。这并不完美,我敢肯定在某些情况下这样做并不是最好的选择(特别是对于非常复杂的搜索条件)。


7
哟,如果您(某人,某人/某物)使我的回答不合时宜,至少发表评论表明您完全不同意的内容会对您的自我造成伤害?我知道这是interweebz,但是...;)
luis.espinal 2012年

107
我并没有投票赞成,但问题始于:“我目前正在设计和实现RESTful API”,而您的回答开始于“如果您的初始API是否完全为RESTful,请不要太担心”。对我不好 如果您正在设计API,那么您正在设计API。问题是问如何最好地设计API,而不是关于是否应该设计API。
gardarh 2012年

14
API 系统,首先在API上工作,而不是在后端管道上工作,第一个实现可以/应该只是一个模拟。HTTP有一种传递参数的机制,建议您对其进行重新设计,但要更糟一些(有序参数而不是名称/值对)。因此,投了反对票。
史蒂文·希律

14
@gardarh-是的,感觉不对,但有时很实用。主要目标是设计一种适用于当前业务环境的API。如果完全的RESTFULL方法适合手头的业务,那就去做吧。如果不是,那就不要去。也就是说,设计一个满足您特定业务需求的API。尝试使它成为RESTfull,因为它的主要要求与询问“如何在X / Y问题中使用适配器模式”没有什么不同。除非他们解决了实际的,有价值的问题,否则不要穿牛角范式。
luis.espinal 2013年

1
我将资源视为状态的某种集合,并将参数视为一种用于以参数方式操纵该状态表示形式的方法。如果您可以使用旋钮和开关来调整资源的显示方式(显示/隐藏资源的某些位,以不同方式排序等),则这些方式就是参数。如果实际上是不同的资源(例如,“ /专辑”与“ /艺术家”),则应在路径中表示它。无论如何,这对我来说很直观。
埃里克·埃利奥特

2

仅供参考:我知道这有点晚了,但是对任何有兴趣的人来说。取决于您想要的RESTful程度,您将必须实现自己的过滤策略,因为HTTP规范对此并不十分清楚。我想建议对所有过滤器参数进行url编码

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

我知道这很丑陋,但是我认为这是最RESTful的方式,应该可以在服务器端轻松解析:)

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.