REST API最佳实践:如何接受参数值列表作为输入[关闭]


409

我们正在启动一个新的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实用程序,所以输入类型没什么大不了的。

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

Answers:


341

退后一步

首先,REST将URI描述为一个通用的唯一ID。太多的人被URI的结构所困扰,哪些URI比其他URI更“安静”。 这种说法可笑,就像说给某人称“鲍勃”比称呼他“乔”要好–两个名字都完成了“识别一个人”的工作。 URI只是一个通用的唯一名称。

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

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

意见建议

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

    我们可能要接受两种样式,但是这种灵活性是否会导致更多的混乱和头疼(可维护性,文档等)?

    URI标识资源。每个资源应具有一个规范的URI。这并不意味着您不能有两个URI指向同一个资源,但是有定义明确的方法可以做到这一点。如果您决定同时使用基于JSON和基于列表的格式(或任何其他格式),则需要确定以下哪种格式是主要的规范 URI。指向相同“资源”的其他URI的所有响应都应包含Content-Locationheader

    坚持使用类比名称,拥有多个URI就像为人们昵称一样。这是完全可以接受的,而且通常很方便,但是,如果我使用昵称,我可能仍想知道他们的全名-指代该人的“官方”方式。这样,当某人用其全名“ Nichloas Telsa”提及某人时,我知道他们正在谈论的是我称为“ Nick”的那个人。

  2. 在您的URI中“搜索”

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

    我的一般经验法则是,如果您的URI包含动词,则可能表明存在问题。URI的标识资源,但是他们不应该指出什么我们正在做的这些资源。这就是HTTP的工作,或者说就是我们的“统一接口”。

    为了击败类比名称,在URI中使用动词就好比当您想要与某人交互时更改其名称一样。如果我要与Bob互动,当我想对他说“嗨”时,Bob的名字就不会变成“ BobHi”。同样,当我们要“搜索”产品时,我们的URI结构不应从“ / Product / ...”更改为“ / Search / ...”。

回答您的最初问题

  1. 关于["101404","7267261"]vs 101404,7267261:为了简化起见,我的建议是避免使用JSON语法(即,在不需要时,不需要用户对URL进行编码)。它将使您的API更加实用。更好的是,正如其他人所建议的那样,请使用标准application/x-www-form-urlencoded格式,因为它可能是您的最终用户最熟悉的格式(例如?id[]=101404&id[]=7267261)。它可能不是“ pretty”,但是Pretty URI不一定表示可用的URI。但是,尽管要重申我的初衷,但最终在谈到REST时,这并不重要。不要过于沉迷于此。

  2. 您可以使用与产品示例几乎相同的方式来解决复杂的搜索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 感兴趣。


67
想跟进并注意,[]并非始终支持该语法(尽管它很常见,甚至可能违反URI规范)。某些HTTP服务器和编程语言只希望重复该名称(例如productType=value1&productType=value2)。
nategood

1
此查询的最初问题。“ / Search?term = pumas&filters = {“ productType”:[“服装”,“袋”],“颜色”:[“黑色”,“红色”]}“”转换为。 。(productType ==服装|| productType ==袋子)&&(color ==黑色|| color ==红色)但您的解决方案:/ Product?term = pumas&productType [] =服装&productType [] = Bags&color [] = Black&color [] =红色似乎可以转换为......(要么是productType ==衣物|| productType ==袋||颜色==黑色||颜色==红色),要么是(productType ==衣物&& productType ==袋子&& color ==黑色&& color == red)这似乎与我有些不同。还是我误会了?
Thomas Cheng

2
邮政请求中的输入内容如何?我想知道我们是否正在更新资源,那么以标准格式在正文中发送查询/过滤器和数据是否是一种不好的做法?例如 如果我想使用api /user/并在正文中更改与用户相关的数据,我将通过与用户一起查询发送{ q:{}, d: {} }q将在数据库中查询并d作为修改后的数据。
分子

1
如果列表很大,您会怎么做?URI的长度受浏览器的限制。我通常已切换到发布请求,并将列表发送到正文中。有什么建议吗?
Troy Cosentino

4
它必须非常大(请参阅stackoverflow.com/questions/417142/…),但是是的,在最极端的情况下,您可能需要使用请求的正文。POST查询进行数据检索是RESTafarians喜欢讨论的事情之一。
nategood

233

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

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

大多数服务器代码会将其解释为值列表,尽管许多服务器代码都具有单个值的简化形式,所以您可能不得不查找。

分隔值也可以。

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

我所看到的一些方法可以完全执行RESTful复杂查询,分两个步骤:

  1. POST 您的查询要求,接收回ID(本质上是创建搜索条件资源)
  2. GET 搜索,参考上面的ID
  3. (可选)根据需要删除查询要求,但请注意,这些要求可供重用。

8
谢谢凯西。我想我和您在一起,也不太喜欢在URL中看到JSON。但是,我不喜欢张贴搜索,这是固有的GET操作。你能指出一个例子吗?
whatupwilly

1
如果查询可以作为简单参数工作,请执行此操作。消息来源来自rest-discuss邮件列表:tech.groups.yahoo.com/group/rest-discuss/message/11578
Kathy Van Stone 2010年

2
如果您只想显示两种资源,则詹姆斯·韦斯特盖特(James Westgate)的答案更典型
凯西·范·斯通

这是正确的答案。在不久的将来,我确定我们将看到OData支持的某些filter = id in(a,b,c,...)或类似的东西。
Bart Calixto 2014年

这就是Akka HTTP的工作方式
Joan

20

第一:

我认为你可以做到2种方式

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

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

http://our.api.com/Product/<id1>,<id2> :如James所建议的那样可以作为一种选择,因为Product标记后面是参数

或者我最喜欢的是:

您可以将Hypermedia用作 RestFul WS 的应用程序状态(HATEOAS)属性的引擎,并进行调用http://our.api.com/Product,该调用应返回的等效URL,http://our.api.com/Product/<id>并在此之后进行调用。

第二

当您必须对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/- >接收所有服装的URL- >呼叫您想要的URL(衣物和包袋)- >接收可能的颜色网址->调用所需的网址


1
几天前,我也遇到了类似的情况,必须调整(HATEOAS)rest api以获取过滤的(大)对象列表,而我只是选择了第二个解决方案。难道不是一遍又一遍地回忆起api有点过分吗?
参孙

这实际上取决于您的系统。...如果它是一个简单的,很少有“选项”的系统,则可能会显得过时。但是,如果您有一些非常大的列表,那么在一个大调用中完成所有操作可能会变得非常麻烦,此外,如果您的API是公开的,那么它对用户可能会变得复杂(如果它是私有的,应该会更容易...只是教您认识的用户)。或者,您可以同时实现HATEOAS和高级用户的“非平稳数组”调用两种方式
Diego Dias

我正在Rails中构建一个宁静的api Web服务,我需要遵循上述相同的url结构(our.api.com/term/pumas/productType/clothing/color/black)。但是我不确定如何相应地配置路由。
rubyist 2014年

控制器的术语,产品类型和颜色是什么?如果是这样,则只需要执行以下操作:资源:term做资源:productType做资源:color结束端
Diego Dias

productType和color是参数。因此,productType的参数是服装,而服装的参数是黑色
红宝石专家

12

您可能要签出RFC 6570。此URI模板规范显示了许多有关url如何包含参数的示例。


1
第3.2.8节似乎适用。尽管值得注意的是,这只是一个提议的标准,似乎并没有超出这一点。
Mike Post

3
@MikePost现在,IETF已进入“标准跟踪”文档的两步成熟过程,我希望6570能够在这种情况下保持几年,然后再转向“ Internet标准”。tools.ietf.org/html/rfc6410 该规范非常稳定,具有许多实现方式并且被广泛使用。
Darrel Miller's

啊,我不知道这种变化。(或者,TIL IETF现在更合理了。)谢谢!
Mike Post

8

第一种情况:

正常的产品查找如下所示

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

因此,我认为最佳实践将是您要做的

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

第二种情况

使用querystring参数进行搜索-像这样很好。我很想将术语与AND和OR结合使用,而不是使用[]

PS:这可能是主观的,所以请随便做。

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

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


3
当然,在两个示例中,尾随/都不应存在,因为URI寻址的是资源而不是集合。
劳伦斯·多尔

2
我总是在REST中使用HTTP动词来执行特定的操作,这就是指导方针:GET:检索/读取对象,POST创建对象,PUT更新现有对象和DELETE删除对象。因此,我不会使用POST来检索内容。如果我想要一个特定的对象列表(过滤器),我将使用url参数中的列表进行GET(用逗号分隔似乎很不错)
Alex

1

我会同意nategood的回答,因为它很完整,而且似乎可以满足您的需求。不过,我想添加一种方式来标识多个(1个或更多)资源:

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

为此,您:

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

创建冗余API ,其中一个API用于获取所有产品,而上述一个用于获取1个或多个。由于出于UX的考虑,您不应向用户显示超过1页的详细信息,因此,我相信拥有1个以上的ID将毫无用处,并且仅用于过滤产品。

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

我想从《惊奇》订购一本书。我确切地知道这是哪本书,并且在浏览恐怖书籍时可以在清单中看到它:

  1. 10000条惊人的行,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:
---------------------------------

我的想法

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

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

当您需要多个资源时,建议您从较大的集合中进行过滤。对于同一示例:

/products?ids=

当然,这是我的看法,因为它不是强制性的。

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.