【问题标题】:REST API Best practice: How to accept list of parameter values as input [closed]REST API 最佳实践:如何接受参数值列表作为输入 [关闭]
【发布时间】:2011-02-05 19:40:27
【问题描述】:

我们正在推出一个新的 REST API,我希望社区能就我们应该如何格式化输入参数的最佳实践提供一些意见:

目前,我们的 API 非常以 JSON 为中心(仅返回 JSON)。关于我们是否想要/需要返回 XML 的争论是一个单独的问题。

由于我们的 API 输出以 JSON 为中心,我们的输入有点以 JSON 为中心,我一直认为这对某些人来说可能很方便,但总的来说很奇怪。

例如,要获取一些产品详细信息,我们目前可以一次提取多个产品:

http://our.api.com/Product?id=["101404","7267261"]

我们是否应该将其简化为:

http://our.api.com/Product?id=101404,7267261

或者是否有方便的 JSON 输入?更痛苦?

我们可能希望同时接受这两种风格,但这种灵活性真的会导致更多的混乱和头痛(可维护性、文档等)吗?

更复杂的情况是我们想要提供更复杂的输入。例如,如果我们想在搜索中允许多个过滤器:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

我们不一定要将过滤器类型(例如 productType 和 color)作为请求名称,如下所示:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

因为我们想将所有过滤器输入组合在一起。

最后,这真的重要吗?可能有太多 JSON 实用程序,以至于输入类型并不重要。

我知道我们对 API 进行 AJAX 调用的 JavaScript 客户端可能会喜欢 JSON 输入以使他们的生活更轻松。

【问题讨论】:

    标签: rest


    【解决方案1】:

    后退一步

    首先,REST 将 URI 描述为通用唯一 ID。太多人被 URI 的结构所困扰,哪些 URI 比其他 URI 更“安静”。 这个论点就像把某人命名为“Bob”比命名他为“Joe”更好——这两个名字都完成了“识别一个人”的工作。URI 只不过是一个 普遍唯一的名称。

    所以在 REST 的眼中,争论 ?id=["101404","7267261"] 是否比 ?id=101404,7267261\Product\101404,7267261 更安静有些徒劳。

    话虽如此,很多时候 URI 的构造方式通常可以很好地指示 RESTful 服务中的其他问题。一般而言,您的 URI 和问题中有几个危险信号。

    建议

    1. 同一资源的多个 URI 和 Content-Location

      我们可能希望同时接受这两种风格,但这种灵活性真的会导致更多的混乱和头痛(可维护性、文档等)吗?

      URI 标识资源。每个资源都应该有一个规范的 URI。这并不意味着您不能让两个 URI 指向同一个资源有明确定义的方法来做这件事。如果您决定同时使用 JSON 和基于列表的格式(或任何其他格式),您需要确定这些格式中的哪一种是主要的 canonical URI。对指向同一“资源”的其他 URI 的所有响应都应包含 Content-Location header

      坚持名称类比,拥有多个 URI 就像给人们起了昵称。这是完全可以接受的,而且通常很方便,但是如果我使用昵称,我可能仍然想知道他们的全名——指代那个人的“官方”方式。这样,当有人提到某人的全名“Nichloas Telsa”时,我知道他们指的是我所说的“Nick”。

    2. 在您的 URI 中“搜索”

      更复杂的情况是我们想要提供更复杂的输入。例如,如果我们想在搜索中允许多个过滤器......

      我的一般经验法则是,如果您的 URI 包含动词,则可能表明某些事情已关闭。 URI 标识资源,但它们不应指示我们正在对该资源执行的操作。这就是 HTTP 或我们的“统一接口”的工作。

      为了避免名称类比,在 URI 中使用动词就像在您想与某人交互时更改某人的名字。如果我与 Bob 互动,当我想跟 Bob 打招呼时,他的名字不会变成“BobHi”。同样,当我们要“搜索”产品时,我们的 URI 结构不应从“/Product/...”更改为“/Search/...”。

    回答您最初的问题

      1234563它会让你的 API 更有用一点。更好的是,正如其他人所建议的那样,使用标准的application/x-www-form-urlencoded 格式,因为您的最终用户可能最熟悉它(例如?id[]=101404&id[]=7267261)。它可能不是“漂亮的”,但漂亮的 URI 并不一定意味着可用的 URI。但是,重申一下我最初的观点,最终在谈到 REST 时,这并不重要。不要过多关注它。
    1. 您的复杂搜索 URI 示例可以通过与您的产品示例非常相似的方式来解决。我建议再次使用application/x-www-form-urlencoded 格式,因为它已经是许多人熟悉的标准。另外,我建议将两者合并。

    你的 URI...

    /Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    
    

    经过 URI 编码后的 URI...

    /Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D
    

    可以转化为...

    /Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red
    

    除了避免 URL 编码的要求并使事情看起来更标准之外,它现在使 API 同质化了一点。用户知道,如果他们想要检索产品或产品列表(在 RESTful 术语中两者都被视为单个“资源”),他们会对 /Product/... URI 感兴趣。

    【讨论】:

    • 想要跟进并注意到 [] 语法并不总是受支持(尽管它很常见,但甚至可能违反 URI 规范)。一些 HTTP 服务器和编程语言更喜欢只重复名称(例如 productType=value1&productType=value2)。
    • 这个查询的初始问题.. "/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red "]}" 转换为... (productType==clothing || productType==bags) && (color==black || color==red) 但您的解决方案:/Product?term=pumas&productType[]=Clothing&productType[] =Bags&color[]=Black&color[]=Red 似乎转换为... (productType==clothing || productType==bags || color==black || color==red)或 Either (productType==clothing && productType==bags && color==black && color==red) 这对我来说似乎有点不同。还是我误会了?
    • post 请求中的输入呢?我想知道我们是否正在更新资源,那么以标准格式在正文中发送查询/过滤器和数据是一种不好的做法。例如。如果我想使用 api /user/ 在正文中更改与用户相关的数据,我将发送 { q:{}, d: {} }q 作为查询,用户将在数据库中查询,d 已修改数据。
    • 当列表可能非常大时,您会怎么做? URI 的长度受浏览器的限制。我通常切换到发布请求并在正文中发送列表。有什么建议吗?
    • 它必须非常大(请参阅stackoverflow.com/questions/417142/…),但是是的,在最极端的情况下,您可能需要使用请求的正文。发布数据检索查询是 RESTafarians 喜欢争论的事情之一。
    【解决方案2】:

    将值列表作为 URL 参数传递的标准方法是重复它们:

    http://our.api.com/Product?id=101404&id=7267261

    大多数服务器代码会将其解释为值列表,尽管许多具有单值简化,因此您可能不得不去寻找。

    分隔值也可以。

    如果您需要将 JSON 发送到服务器,我不喜欢在 URL 中看到它(这是一种不同的格式)。特别是,URL 有大小限制(如果理论上没有的话,在实践中)。

    我见过一些人用 RESTfully 进行复杂查询的方式分两步:

    1. POST 您的查询要求,接收回一个 ID(本质上是创建搜索条件资源)
    2. GET搜索,引用上面的ID
    3. 如果需要,可以选择删除查询要求,但请注意,这些要求可以重复使用。

    【讨论】:

    • 谢谢凯西。我想我和你在一起,也不太喜欢在 URL 中看到 JSON。但是,我不喜欢为固有的 GET 操作的搜索发帖。你能举个例子吗?
    • 如果查询可以作为简单参数工作,那么就这样做。来源来自其余讨论邮件列表:tech.groups.yahoo.com/group/rest-discuss/message/11578
    • 如果只想展示两个资源,James Westgate 的回答比较典型
    • 这是正确答案。在不久的将来,我相信我们会看到 OData 或类似支持的一些 filter=id in(a,b,c,...)。
    • 这就是 Akka HTTP 的工作方式
    【解决方案3】:

    第一:

    我认为你可以通过两种方式做到这一点

    http://our.api.com/Product/<id> : 如果你只想要一条记录

    http://our.api.com/Product : 如果你想要所有记录

    http://our.api.com/Product/<id1>,<id2> :正如詹姆斯建议的那样,可以选择,因为产品标签后面的内容是一个参数

    或者说我最喜欢的是:

    您可以使用 RestFul WS 的 Hypermedia as the engine of application state (HATEOAS) 属性并调用 http://our.api.com/Product 应该返回 http://our.api.com/Product/<id> 的等效 URL 并调用它们之后。

    第二

    当您必须对 url 调用进行查询时。我建议再次使用 HATEOAS。

    1) 拨打http://our.api.com/term/pumas/productType/clothing/color/black

    2) 拨打http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

    3) (使用 HATEOAS)拨打 `http://our.api.com/term/pumas/productType/ 的电话 -> 接收所有服装可能的网址 -> 拨打您想要的网址(服装和包) -> 接收可能的颜色网址 -> 拨打你想要的

    【讨论】:

    • 几天前我遇到了类似的情况,不得不调整(HATEOAS)rest api 以获得过滤的(大)对象列表,我只是选择了你的第二个解决方案。一遍又一遍地回忆每个 API 是不是有点矫枉过正?
    • 这真的取决于你的系统....如果它是一个简单的“选项”很少它可能应该是矫枉过正。但是,如果您有一些非常大的列表,那么在一次大调用中完成所有操作可能会变得非常麻烦,此外,如果您的 API 是公共的,它对用户来说可能会变得复杂(如果它是私有的,它应该更容易......只教你认识的用户)。作为替代方案,您可以为高级用户实现两种样式、HATEOAS 和“非静止数组”调用
    • 我正在 Rails 中构建一个 restful api web 服务,我需要遵循与上面相同的 url 结构(our.api.com/term/pumas/productType/clothing/color/black)。但我不确定如何相应地配置路由。
    • 是您的控制器的术语、产品类型和颜色吗?如果是这样,你只需要这样做: resources :term do resources :productType do resources :color end end
    • productType 和 color 是参数。所以 productType 的参数是服装,服装的参数是黑色
    【解决方案4】:

    您可能想查看RFC 6570。此 URI 模板规范展示了许多 url 如何包含参数的示例。

    【讨论】:

    • 第 3.2.8 节似乎是适用的。尽管值得注意的是,这只是一个提议的标准,似乎并没有超出这一点。
    • @MikePost 现在 IETF 已经转向“标准跟踪”文档的两步成熟过程,我希望 6570 在转向“互联网标准”之前再保持这种状态几年。 tools.ietf.org/html/rfc6410 规范非常稳定,有很多实现,被广泛使用。
    • 啊,我不知道这种变化。 (或者,TIL IETF 现在更合理。)谢谢!
    【解决方案5】:

    第一种情况:

    正常的产品查找应该是这样的

    http://our.api.com/product/1

    所以我认为最好的做法是让你这样做

    http://our.api.com/Product/101404,7267261

    第二种情况

    使用查询字符串参数搜索 - 像这样很好。我很想将术语与 AND 和 OR 结合起来,而不是使用 []

    PS 这可能是主观的,所以做你觉得舒服的事。

    将数据放在 url 中的原因是链接可以粘贴到站点上/在用户之间共享。如果这不是问题,请务必使用 JSON/POST。

    编辑:经过反思,我认为这种方法适合具有复合键的实体,但不适用于多个实体的查询。

    【讨论】:

    • 当然,在这两个例子中,结尾的/ 不应该在那里,因为 URI 指向的是资源,而不是集合。
    • 我一直认为 HTTP 动词,在 REST 用法中是为了执行特定操作,这是指导方针:GET:检索/读取对象,POST 创建对象,PUT 更新现有对象和 DELETE 删除一个东西。所以我不会使用 POST 来检索某些东西。如果我特别想要一个对象列表(过滤器),我会在 url 参数中使用一个列表进行 GET(用逗号分隔看起来不错)
    【解决方案6】:

    我会支持 nategood 的回答,因为它是完整的,并且似乎满足了您的需求。不过,我想就以这种方式识别多个(1 个或多个)资源添加评论:

    http://our.api.com/Product/101404,7267261

    这样做,您:

    复杂化客户端 通过强迫他们将您的响应解释为一个数组,如果我提出以下请求,这对我来说是反直觉的:http://our.api.com/Product/101404

    创建冗余 API 一个 API 用于获取所有产品,一个 API 用于获取 1 个或多个。由于您不应该为了用户体验而向用户显示超过 1 页的详细信息,我相信拥有超过 1 个 ID 是没有用的,纯粹用于过滤产品。

    这可能不是那么成问题,但您要么必须通过返回单个实体(通过验证您的响应是否包含一个或多个)来自己处理服务器端,要么让客户端管理它。

    示例

    我想从 Amazing 订购一本书。我确切地知道它是哪本书,并且在浏览恐怖书籍时会在列表中看到它:

    1. 10 000 条惊人的线路,0 条惊人的测试
    2. 神奇怪物的回归
    3. 让我们复制惊人的代码
    4. 结束的惊人开始

    选择第二本书后,我被重定向到详细说明列表中的书部分的页面:

    --------------------------------------------
    Book #1
    --------------------------------------------
        Title: The return of the amazing monster
        Summary:
        Pages:
        Publisher:
    --------------------------------------------
    

    或者在一个页面上只给我那本书的全部细节?

    ---------------------------------
    The return of the amazing monster
    ---------------------------------
    Summary:
    Pages:
    Publisher:
    ---------------------------------
    

    我的意见

    在获取此资源的详细信息时,我建议在保证唯一性的情况下使用路径变量中的 ID。例如,以下 API 建议使用多种方法来获取特定资源的详细信息(假设产品具有唯一 ID,并且该产品的规范具有唯一名称,并且您可以自上而下导航):

    /products/{id}
    /products/{id}/specs/{name}
    

    当您需要超过 1 个资源时,我建议您从更大的集合中过滤。同样的例子:

    /products?ids=
    

    当然,这是我的意见,因为它不是强加的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-23
      • 2014-06-22
      • 2021-01-30
      相关资源
      最近更新 更多