如何设计RESTful搜索/过滤?

我目前正在用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。我正在考虑在请求体中发送参数以过滤列表。这是因为我想要能够指定复杂的过滤器而不得到一个超长的url,如:

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。

507594 次浏览

我认为你应该使用请求参数,但前提是没有适当的HTTP头来完成你想做的事情。HTTP规范没有明确表示GET不能有主体。然而这篇论文指出:

按照惯例,当GET方法为 使用时,所有信息都需要 识别在其中编码的资源 URI。这里没有惯例 HTTP/1.1用于安全交互(例如, 检索),客户端提供 将数据发送到HTTP实体中的服务器 而不是在查询部分 一个URI。这意味着为了安全

.操作,uri可能很长

如果你的初始API是完全RESTful的,也不要太担心(特别是当你只是在alpha阶段)。首先让后端管道工作。你总是可以做一些URL转换/重写来映射,迭代地改进,直到你得到足够稳定的东西来进行广泛的测试(“测试”)。

您可以定义uri,其参数是根据uri本身的位置和约定编码的,前缀是您知道将始终映射到某个对象的路径。我不懂PHP,但我认为这样的工具是存在的(因为它存在于其他语言的web框架中):

.ie。使用param[i]=value[i]对i=1进行“user”类型的搜索。4在商店#1(与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,将所有前缀为/store1/search/user的uri映射到搜索处理程序(或任何PHP名称),默认搜索store1下的资源(相当于/search?location=store1&type=user)。

根据API记录和执行的约定,参数值1到4用逗号分隔,并按此顺序显示。

选项2添加搜索类型(在本例中为user)作为位置参数#1。任何一种选择都只是表面上的选择。

选择3也可以,但我不喜欢。我认为在某些资源中进行搜索的能力应该在URI本身中先于搜索本身显示出来(就好像在URI中清楚地表明在资源中进行搜索一样)。

与在URI上传递参数相比,这样做的优点是搜索是URI的一部分(因此将搜索视为资源,其内容可以(也将会)随时间而改变的资源)。缺点是参数顺序是强制的。

一旦你做了这样的事情,你可以使用GET,它将是一个只读资源(因为你不能POST或PUT它-当它被GET'ed时它会被更新)。它也是一种只有在调用时才存在的资源。

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

如何去做是一个设计决策,但这是我要做的。它并不完美,而且我相信在某些情况下,这样做并不是最好的选择(特别是对于非常复杂的搜索条件)。

实现RESTful搜索的最佳方法是将搜索本身视为一种资源。然后您可以使用POST动词,因为您正在创建一个搜索。为了使用POST,您不必在数据库中创建一些东西。

例如:

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

您正在从用户的角度创建搜索。它的实现细节是不相关的。有些RESTful api甚至不需要持久性。这是一个实现细节。

如果你在GET请求中使用请求体,你就破坏了REST原则,因为你的GET请求将不能被缓存,因为缓存系统只使用URL。

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

使用URL或查询参数而不是请求体参数,例如:

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

事实上,HTTP RFC 7231说:

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

要了解更多信息,请查看在这里

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

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

在创建这样的过滤器之后,有两种可能获得搜索/过滤器结果。

  1. 一个具有唯一ID的新资源将连同201 Created状态码一起返回。然后使用这个ID向/api/users/发出GET请求,如下所示:

    GET /api/users/?filterId=1234-abcd
    
  2. After new filter is created via POST it won't reply with 201 Created but at once with 303 SeeOther along with Location header pointing to /api/users/?filterId=1234-abcd. This redirect will be automatically handled via underlying library.

In both scenarios two requests need to be made to get the filtered results - this may be considered as a drawback, especially for mobile applications. For mobile applications I'd use single POST call to /api/users/filter/.

How to keep created filters?

They can be stored in DB and used later on. They can also be stored in some temporary storage e.g. redis and have some TTL after which they will expire and will be removed.

What are the advantages of this idea?

Filters, filtered results are cacheable and can be even bookmarked.

供你参考:我知道这有点晚了,但对任何感兴趣的人来说。 这取决于你想要多RESTful,你必须实现你自己的过滤策略,因为HTTP规范在这方面不是很清楚。我建议对所有过滤器参数进行url编码,例如

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

我知道它很丑,但我认为这是最RESTful的方式来做它,应该很容易在服务器端解析:)

当我使用laravel / php后端时,我倾向于这样做:

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

PHP自动将[]参数转换为一个数组,因此在本例中,我将以一个$filter变量结束,它包含一个数组/过滤器对象,以及一个页面和任何我想要加载的相关资源。

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