我目前正在用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。


当前回答

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

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

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

将返回一个具有唯一ID的新资源以及201已创建状态代码。然后使用这个ID可以向/api/users/ like发出GET请求: 得到/ api /用户/ ?filterId = 1234 - abcd 通过POST创建新过滤器后,它不会回复201 created,而是立即回复303 SeeOther,并附上指向/api/users/?filterId=1234-abcd的Location头。此重定向将通过底层库自动处理。

在这两种情况下,都需要发出两个请求来获得过滤后的结果——这可能被认为是一个缺点,特别是对于移动应用程序。对于移动应用程序,我将使用单个POST调用/api/users/filter/。

如何保持创建的过滤器?

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

这个想法的优点是什么?

过滤器,过滤的结果是可缓存的,甚至可以收藏。

其他回答

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

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

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

将返回一个具有唯一ID的新资源以及201已创建状态代码。然后使用这个ID可以向/api/users/ like发出GET请求: 得到/ api /用户/ ?filterId = 1234 - abcd 通过POST创建新过滤器后,它不会回复201 created,而是立即回复303 SeeOther,并附上指向/api/users/?filterId=1234-abcd的Location头。此重定向将通过底层库自动处理。

在这两种情况下,都需要发出两个请求来获得过滤后的结果——这可能被认为是一个缺点,特别是对于移动应用程序。对于移动应用程序,我将使用单个POST调用/api/users/filter/。

如何保持创建的过滤器?

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

这个想法的优点是什么?

过滤器,过滤的结果是可缓存的,甚至可以收藏。

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

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

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

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

事实上,HTTP RFC 7231说:

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

更多信息请看这里。

供你参考:我知道这有点晚了,但对任何感兴趣的人来说。 这取决于你想要多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变量结束,该变量包含一个数组/过滤器对象,以及一个页面和我想要加载的任何相关资源。

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

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

例如:

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

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