我目前正在设计和实现PHP中的RESTful API。但是,我没有成功实施我的初始设计。
1 2 3 4 5
| 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的情况下指定复杂的过滤器,例如:
1
| GET /users?parameter1=value1¶meter2=value2¶meter3=value3¶meter4=value4 |
相反,我希望有类似的东西:
1 2 3 4 5 6 7 8
| 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 URL设计的可能重复
-
小心!!! GET方法必须是IDEMPOTENT,并且必须是"可缓存的"。 如果您在正文中发送信息系统如何缓存您的请求? HTTP允许仅使用URL而不是请求主体来缓存GET请求。 例如,这两个请求:example.com {test:"some"} example.com {anotherTest:"some2"}被缓存系统认为是相同的:它们都具有完全相同的URL
-
只是要添加,你应该POST到/ users(集合)而不是/ user(单用户)。
-
另一个要考虑的问题是,大多数应用服务器都有访问日志来记录网址,因此可能介于两者之间。 因此GET上可能会出现一些意外的信息泄漏。
-
搜索的RESTful URL设计可能重复
实现RESTful搜索的最佳方法是将搜索本身视为资源。然后您可以使用POST动词,因为您正在创建搜索。您不必在数据库中逐字创建以使用POST。
例如:
1 2 3 4 5 6 7 8 9 10
| Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
"terms": {
"ssn":"123456789"
},
"order": { ... },
...
} |
您正在从用户的角度创建搜索。这个实现细节是无关紧要的。一些RESTful API甚至可能不需要持久性。这是一个实现细节。
-
这应该被OP接受为答案。这是对这个问题最正确的回答。 @ jason-harrelson表示持久性并不需要支持POST的预期用途。对于搜索资源,使用POST的好处是可以将其用作未来的功能,例如已保存的搜索,或建议搜索API的用户。
-
对搜索端点使用POST请求的一个重要限制是它无法加入书签。为搜索结果(特别是复杂查询)添加书签非常有用。
-
使用POST进行搜索可能会破坏REST缓存约束。 whatisrest.com/rest_constraints/cache_excerps
-
我想知道是否可以将PRG模式用于重定向后获取。
-
根据其性质,搜索是瞬态的:数据在具有相同参数的两次搜索之间演变,因此我认为GET请求不会完全映射到搜索模式。相反,搜索请求应该是POST(/资源/搜索),然后您可以保存该搜索并重定向到搜索结果,例如/资源/搜索/ iyn3zrt。这样,GET请求成功并且有意义。
-
我不认为post是合适的搜索方法,普通GET请求的数据也可能随时间而变化。
-
怎么样GitHub做到这一点。我喜欢他们如何将它吐出资源但我想如果你正在进行像Google一样的自由文本搜索,你就不能把它真正地分解成网址中的具体资源。 GitHub是这样的:/ search / [resourceName]?q = John ...其中q是关键字或者在搜索框中键入的文本,但是在这种情况下你要搜索特定的资源类型
-
也许只是转储所有动词,并使用POST来处理所有事情......但是说真的,如果我们要推断这个逻辑,那么应该使用POST而不是GET来检索所有动态页面,因为我们正在创建一个新的(并且可能唯一的)HTML / JSON /每个请求的任何文档。不敢相信这个答案有这么多的赞成。
-
@OlegsJeremejevs:据说GET应该是幂等的(总是返回相同的结果) - 搜索显然会在不同的时间返回不同的结果。现在,/搜索根本不是资源,我会说。我会把它放在过滤实际/用户(或其他)端点,你怎么看?
-
@Zlatko:想想GET /users - 你需要分页,对吧?分页也是一种搜索/过滤器,因为您传递了一些参数并获得了资源的子集。这保证POST吗?
-
正是我在想什么。这就是为什么我认为POST在这里是错误的。搜索应该只是GET /resource调用的过滤器。
-
@OlegsJeremejevs Idempotent并不意味着"总是返回相同的结果"。否则,我永远无法从任何也接受P??OST或PUT的资源或集合中获取
-
@OlegsJeremejevs"可能只是转储所有动词,并使用POST来处理所有事情"我不确定你的意思。在这里,我们将发布到一个集合(搜索)以在该集合中创建一个新资源(搜索)。这不是它通常用于什么?
-
这绝对是最糟糕的答案。我无法相信它有这么多的赞成。这个答案解释了原因:programmers.stackexchange.com/questions/233164/…
-
在浏览了richard发布的链接后,我认为POST不是正确的RESTful方式。
-
我不认为GET参数总是正确的选择。您可能正在搜索敏感数据,例如卡号或SSN,并且在POST正文中隐藏该信息更安全。隐藏发布数据中的所有内容是防止恶意攻击或阻止聪明用户执行不应该做的事情的好方法。
-
@jkerak你不应该有像ssn一样的敏感数据,特别是搜索!使用代理键或不敏感的东西。
-
使用GET进行搜索并不违反GET的幂等性质。关于REST的"幂等"点是调用该操作本身不会影响资源。用params调用GET 10次不会改变结果。
-
非常有创意的答案:只需将搜索视为资源和-poof-您有理由使用POST而不是GET。在我看来,方便但错误。
-
@DiegoDeberdt如果你足够勇敢,一切都是资源。 (抱歉)
-
Facebook和Netflix用REST表达了他们的地狱,并提出了他们自己的查询规范。 GraphQl和Falcor以及tney都是OSS。
-
永远不应该使用POST动词进行搜索或过滤。这完全错了!
-
所以很多人都建议POST是错误的。我通常使用GET和查询字符串来指定我的过滤器(为更复杂的搜索编码我的过滤器)但是如果我想检查资源是否存在,或者用户资源是否更具体,请通过用户名和密码来模拟登录认证?这显然是我不想暴露的敏感数据(密码)。我应该将两个数据发布到一个enpoint并返回匹配的资源吗?我应该创建一个RPC类型调用并返回true / false或其他什么?
-
我认为理想情况下,您将客户端站点作为GET接收搜索,因此可以将其添加为书签,但如果后面有专用的API服务器,则最好POST查询。知道不是每个人都有这种架构 - 只需要决定你是否需要搜索网址是否可以缓存/加入书签。
-
这里有很多意见。也许决定因素应该是您要搜索的数据类型。例如,它可以是用于比较的文件或图片。或者搜索文本可能难以进行url编码。这似乎是发布数据的理由。
-
这可能是我听过的最糟糕的POST场景定义。
-
它不仅打破了书签。它还完全打破导航 - 尝试返回搜索结果。糟糕的经历。我的经验法则是:在浏览器体验中永远不使用POST而不重定向到GET。
-
我想有了SPA,我们可以使用POST使用GET和BackEnd查询构建FrontEnd导航。这样书签仍然有效但你必须将GET url映射到FrontEnd中的POST查询。只是一个想法似乎在互联网上没有达成共识。
如果您在GET请求中使用请求正文,那么您将违反REST原则,因为您的GET请求将无法缓存,因为缓存系统仅使用URL。
更糟糕的是,您的URL无法加入书签,因为该URL不包含将用户重定向到此页面所需的所有信息
使用URL或Query参数代替请求正文参数。
例如。:
1 2
| /myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx |
事实上,HTTP RFC 7231说:
GET请求消息中的有效负载没有定义的语义;在GET请求上发送有效负载主体可能会导致某些现有实现拒绝该请求。
有关更多信息,请查看此处
-
从我的错误中学习 - 我使用接受的答案的建议(POST json)设计了一个api,但我正在转向url参数。书签能力可能比您想象的更重要。在我的情况下,需要将流量引导到某些搜索查询(广告系列)。此外,使用历史API使用URL参数更有意义。
-
这取决于它的使用方式。如果你链接到一个基于这些参数加载页面的URL,这是有意义的,但如果主页面只是为了获取基于过滤器参数的数据而进行AJAX调用,那么无论如何都不能将其加入书签,因为它是ajax电话并没有影响。当然,您也可以为URL添加书签,当您去那里时,建立一个过滤器并将其发送到ajax调用,它可以正常工作。
-
@DanielLorenz为获得最佳用户体验,在这种情况下,仍应通过History API更改URL。当网站不允许使用浏览器后退功能导航到以前的页面时,我无法忍受。如果它是标准的服务器端生成页面,使其成为可收藏的唯一方法是使用GET请求。似乎好的查询参数是最好的解决方案。
-
@Nathan我想我误解了这个答案。我在谈论在get中使用查询字符串参数。你永远不应该在GET调用中使用body参数,因为那将是完全无用的。我正在谈论更多关于GET的查询字符串可以使用/书签,然后在页面启动时,您可以使用这些参数构建过滤器POST,使用这些参数来获取数据。在那种情况下,历史仍然可以正常工作。
-
@DanielLorenz啊,没关系,这是有道理的。我想我误解了你在说什么。
似乎资源过滤/搜索可以以RESTful方式实现。我们的想法是引入一个名为/filters/或/api/filters/的新端点。
使用此端点过滤器可以视为资源,因此通过POST方法创建。这种方式 - 当然 - 身体可用于携带所有参数以及可以创建复杂的搜索/过滤器结构。
创建此类过滤器后,有两种方法可以获得搜索/过滤结果。
将返回具有唯一ID的新资源以及201 Created状态代码。然后使用此ID,可以将GET请求设为/api/users/,如:
1
| GET /api/users/?filterId=1234-abcd |
通过POST创建新过滤器后,它将不会以201 Created回复,而是使用303 SeeOther同时回复Location标题指向/api/users/?filterId=1234-abcd。此重定向将通过底层库自动处理。
在这两种情况下,需要进行两次请求以获取过滤结果 - 这可能被视为一个缺点,尤其是对于移动应用程序。对于移动应用程序,我会使用单个POST调用/api/users/filter/。
如何保持创建的过滤器?
它们可以存储在DB中,以后再使用。它们也可以存储在一些临时存储器中,例如redis并有一些TTL,之后它们将过期并将被删除。
这个想法有什么好处?
过滤器,过滤结果可以缓存,甚至可以加入书签。
-
这应该是公认的答案。您没有违反REST原则,您可以对资源进行长时间的复杂查询。它很好,干净,书签兼容。唯一的另一个缺点是需要为创建的过滤器存储键/值对,以及已经提到的两个请求步骤。
我认为您应该使用请求参数,但只要没有合适的HTTP标头来完成您想要的操作。 HTTP规范没有明确说明,GET不能有一个正文。然而,本文指出:
By convention, when GET method is
used, all information required to
identify the resource is encoded in
the URI. There is no convention in
HTTP/1.1 for a safe interaction (e.g.,
retrieval) where the client supplies
data to the server in an HTTP entity
body rather than in the query part of
a URI. This means that for safe
operations, URIs may be long.
-
ElasticSearch也可以与身体一起使用并且效果很好!
-
是的,但他们控制服务器实现可能不是互联网上的xase。
因为我正在使用laravel / php后端,所以我倾向于使用这样的东西:
1
| /resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource |
PHP自动将[]参数转换为数组,因此在本例中我将得到一个$filter变量,该变量包含过滤器的数组/对象,以及我想要加载的页面和任何相关资源。
如果您使用其他语言,这可能仍然是一个很好的约定,您可以创建一个解析器将[]转换为数组。
-
这种方法看起来很不错,但在URL中使用方括号可能存在??问题,请参阅什么字符可以在网址中使用
-
@Sky这可以通过URI编码[和]来避免。使用这些字符的编码表示来对查询参数进行分组是众所周知的实践。它甚至用在JSON:API规范中。
如果您的初始API完全是RESTful(特别是当您处于alpha阶段时),请不要担心太多。让后端管道首先工作。您可以随时进行某种URL转换/重写以映射事物,迭代完善,直到您获得足够稳定的广泛测试("beta")。
您可以定义URI,其参数按URI上的位置和约定进行编码,前缀为您知道始终映射到某个内容的路径。我不知道PHP,但我认为这样的工具存在(因为它存在于其他语言的Web框架中):
.IE。对商店#1的i = 1..4使用param [i] = value [i]执行"用户"类型的搜索(使用value1,value2,value3,...作为URI查询参数的简写):
1
| 1) GET /store1/search/user/value1,value2,value3,value4 |
要么
1
| 2) GET /store1/search/user,value1,value2,value3,value4 |
或者如下(尽管我不推荐它,稍后会详细介绍)
1
| 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时更新)。它也是一种资源,只有在被调用时才会存在。
也可以通过将结果缓存一段时间或使用DELETE导致缓存被删除来为其添加更多语义。但是,这可能与人们通常使用DELETE的方式背道而驰(因为人们通常使用缓存头来控制缓存。)
你如何去做这将是一个设计决定,但这将是我的方式。它并不完美,我相信会有这样的情况,这样做不是最好的事情(特别是对于非常复杂的搜索标准)。
-
哟,如果你(某人,无论是谁/什么)的东西都适合我的回答,那么至少会发表评论来表明你究竟不同意这对你有什么影响吗?我知道这是interweebz,但是......;)
-
我没有投票,但事实上问题始于:"我目前正在设计和实现一个RESTful API",你的答案始于"如果你的初始API完全是REST,那就不要担心太多"对我不对如果您正在设计API,那么您正在设计API。问题是询问如何最好地设计API,而不是关于是否应该设计API。
-
API是系统,首先处理API,而不是后端管道,第一个实现可能/应该只是一个模拟。 HTTP有一个传递参数的机制,你建议它被重新发明,但更糟糕的是(有序参数而不是名称值对)。因此投票率下降。
-
@gardarh - 是的,这感觉不对,但有时它是务实的。主要目标是设计适用于手头业务环境的API。如果完全RESTFULL方法适合手头的业务,那么就去做吧。如果不是,那就不要去了。也就是说,设计一个满足您特定业务需求的API。尝试将RESTfull作为其主要要求,与询问"如何在X / Y问题中使用适配器模式"没有什么不同。除非他们解决实际的,有价值的问题,否则不要使用鞋拔范式。
-
@StevenHerod - 我不是在重塑任何东西。资源的URI不包含URL参数,因此需要表示可能"可表示"为HTTP参数集合和基本URI的某类资源。当你真的需要这样做时,没有干净和好的方法来做到这一点。您希望用URI和HTTP参数表示资源多少或多少,这些特定于手头的问题。有时,您真的不应该使用HTTP参数来表示资源。因此,我提出了处理此类案件的方法(众多方法之一)
-
考虑以下两个(人为的)案例:作者X的书籍集合的URI与主题Y的书籍集合。人们可以很容易地说"好吧,HTTP已经有一个传递参数的机制",我们选择/书籍?作者= X和/书籍?话题= Y.另一方面,如果我们需要RESTfull URI,那么我们可能需要/ books / author / X和/ books / topic / Y之类的东西。那么当我们需要表示这两者的交集时会发生什么?使用HTTP参数,非常简单。对于复杂案例的RESTful要求,它不是那么简单(/ books / X / topic / Y?/ books / Y / author / X?)
-
所以,没有干净,通用的答案。假装有一个,或假装问题不存在,因为"HTTP已经有一个传递参数的机制",这根本就没有意义。
-
我将资源视为一些状态集合,并将参数视为以参数方式操纵该状态表示的方法。可以这样想,如果你可以使用旋钮和开关来调整资源的显示方式(显示/隐藏它的某些部分,以不同的顺序排序等等),那些控件就是参数。如果它实际上是一个不同的资源(例如'/ albums'vs'/ artists'),那就应该在路径中表示它。无论如何,这对我来说是直观的。
-
"让后端管道先工作"。这是一种由内到外的方法。对我来说,我做BDD和TDD,所以我走到外面,从PM的故事开始。这样做可以指导您为业务需求编写代码,这是一种更好的方法。这样做可以确保您的编码精益,而且只针对故事中提到的行为。不要在后端捣乱一堆垃圾。 TDD迫使你走这条路走向精益......相信我。从外部角度考虑REST api,因为它们与需求相关联,这一点更为重要
-
我喜欢这个答案的成熟实用主义,我认为OP可能会发现它传达了一种有用的方法来评估他/她自己的最终解决方案。另外,我喜欢这个答案显示了一种将搜索/过滤作为一种GET的方法,这种方法感觉不那么迂腐,不得不将其视为创建一种(通用的,瞬态的)资源。我希望我的服务专注于应用程序域中的持久资源,而不是架构框架强加的资源。
-
有趣的是人们如何就一项原则争论死亡。 Facebook和Netflix用它来制造并创造了GraphQL和Falcor。仅仅因为它的http和api并不意味着它必须是REST。
-
^^宾果游戏。最后得到它的人。
-
标准化是出于某种目的。它使事情变得直观。否则每个查看API的人都需要详细解释如何解码它。标准化只应在例外情况下打破,如果遵循它会增加比专业人士更多的缺点。不确定URI方案如何建议比自己问题中提到的更好。我可以看到许多标准被破坏而没有增加任何价值。
-
@kishorborate - 我看到人们坚持像宗教这样的标准,而没有考虑其他要求,失去了重要的价值(而不是增加。)一段时间后会变得很累。我们做开发/工程,当一个人不能(或不应该)坚持犹太标准时,找到中间立场的行为。标准是一般案例场景的指导方针,仅此而已。
仅供参考:我知道这有点晚了但是对于任何有兴趣的人来说。
取决于你想要的RESTful,你必须实现自己的过滤策略,因为HTTP规范在这方面不是很清楚。我想建议对所有过滤器参数进行url编码,例如:
1
| GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2 |
我知道它很难看,但我认为这是最RESTful的方式,应该很容易在服务器端解析:)