用于搜索的 RESTful URL 设计

Posted

技术标签:

【中文标题】用于搜索的 RESTful URL 设计【英文标题】:RESTful URL design for search 【发布时间】:2010-09-17 11:22:21 【问题描述】:

我正在寻找一种将搜索表示为 RESTful URL 的合理方法。

设置:我有两个模型,汽车和车库,其中汽车可以在车库中。所以我的网址看起来像:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

汽车可以单独存在(因此是 /car),也可以存在于车库中。什么是表示给定车库中所有汽车的正确方法?比如:

/garage/yyy/cars     ?

yyy 和 zzz 车库里的汽车联合起来怎么样?

表示搜索具有特定属性的汽车的正确方法是什么?说:给我看所有有 4 门的蓝色轿车:

/car/search?color=blue&type=sedan&doors=4

还是应该改为 /cars?

在那里使用“搜索”似乎不合适 - 有什么更好的方式/术语?应该是:

/cars/?color=blue&type=sedan&doors=4

搜索参数应该是 PATHINFO 还是 QUERYSTRING 的一部分?

简而言之,我正在寻找有关跨模型 REST url 设计和搜索的指导。

[更新]我喜欢贾斯汀的回答,但他没有涵盖多字段搜索案例:

/cars/color:blue/type:sedan/doors:4

或类似的东西。我们如何从

/cars/color/blue

到多字段案例?

【问题讨论】:

虽然它在英文中看起来更好,但将/cars/car 混合在一起并不符合语义,因此是个坏主意。当该类别下有多个项目时,请始终使用复数。 这些都是错误的答案。搜索应使用查询字符串。如果使用得当(即用于搜索),查询字符串是 100% RESTful。 签出doriantaylor.com/policy/http-url-path-parameter-syntax 【参考方案1】:

RESTful 漂亮的 URL 设计是关于显示基于结构的资源(类似目录的结构,日期:articles/2005/5/13,对象及其属性,..),斜线/表示层次结构,请改用-id

#分层结构# 我个人更喜欢:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

如果用户删除 /car-id 部分,它会带来 cars 预览 - 直观。用户确切地知道他在树中的哪个位置,他在看什么。他从第一眼就知道,车库和汽车是相关的。 /car-id 也表示与/car/id 不同,它属于一起。

#搜索# 搜索查询没问题,只有您的偏好,应该考虑什么。加入搜索时会出现有趣的部分(见下文)。

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

或者基本上任何不是如上所述的斜线的东西。 公式:/cars[?;]color[=-:]blue[,;+&], * 虽然我不会使用& 符号,因为乍一看从文本中无法识别。

** 你知道在 URI 中传递 JSON 对象是 RESTful 的吗? **

选项列表

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

##可能的功能?## 否定搜索字符串 (!) 要搜索任何汽车,但不是 黑色红色?color=!black,!redcolor:(!black,!red)

联合搜索 在车库中搜索 redblueblack3 门的汽车 id 1..20 em> 或 101..103999不是 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3] 然后,您可以构建更复杂的搜索查询。 (查看CSS3 attribute matching 了解匹配子字符串的想法。例如搜索包含“bar”的用户user*=bar。)

#结论# 无论如何,这对你来说可能是最重要的部分,因为你可以随心所欲地做,只要记住 RESTful URI 代表一个易于理解的结构,例如类似目录的/directory/file/collection/node/item、日期/articles/year/month/day.. 当你省略任何最后一段时,你会立即知道你得到了什么。

所以..,所有这些字符都允许未编码

未保留:a-zA-Z0-9_.-~通常允许编码和不编码,然后两种用法是等效的。 特殊字符:$-_.+!*'(), 保留:;/?:@=&可以未编码用于它们所代表的目的,否则必须对其进行编码。 不安全:<>"#%|^~[]`为什么不安全以及为什么应该编码:RFC 1738 see 2.2

有关更多字符类,另请参阅RFC 1738#page-20。

RFC 3986 see 2.2 尽管我之前说过,这里有一个常见的分隔符区别,这意味着有些“是”比其他的更重要。

通用分隔符::/?#[]@ 子分隔符:!$&'()*+,;=

更多阅读: 层次结构:see 2.3、see 1.2.3url path parameter syntaxCSS3 attribute matchingIBM: RESTful Web services - The basics 注意:RFC 1738 由 RFC 3986 更新

【讨论】:

我不相信我没有想过在查询字符串中使用 JSON。这是我面临的一个问题的答案——不使用POST 的复杂搜索结构。此外,您在回答中给出的其他想法也非常值得赞赏。非常感谢! @Qwerty:很棒的帖子!我想知道:使用; 而不是& 的唯一原因是可读性?因为如果是这样,我想我实际上更喜欢&,因为它是更常见的分隔符......对吗? :) 谢谢! @Flo 没错 :),但请记住,& 作为分隔符只有开发人员知道。父母、祖父母和未受过 IT 教育的人群接受常见书面文本中使用的分隔符。 当查询字符串被很好理解和标准时,为什么要制定一个非标准的方案? @Qwerty 没有什么能阻止您使用 /search?cars=red,blue,green&garages=1,2,3 或者如果您使用 表单:/search?cars=red&cars=blue&garages=1&garages =2【参考方案2】:

这里有很多适合您的案例的好选择。您仍然应该考虑使用 POST 正文。

查询字符串非常适合您的示例,但如果您有更复杂的内容,例如任意长的项目列表或布尔条件,您可能希望将帖子定义为客户端通过 POST 发送的文档。

这允许对搜索进行更灵活的描述,并避免服务器 URL 长度限制。

【讨论】:

【参考方案3】:

另外我还建议:

/cars/search/all?color,model,year
/cars/search/by-parameters?color,model,year
/cars/search/by-vendor?vendor

这里,Search 被视为Cars 资源的子资源。

【讨论】:

【参考方案4】:

我使用两种方法来实现搜索。

1) 最简单的情况,用于查询关联元素和导航。

    /cars?q.garage.id.eq=1

这意味着,查询车库 ID 等于 1 的汽车。

还可以创建更复杂的搜索:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

FirstStreet 中所有车库中非红色的汽车(第 3 页,每页 100 个元素)。

2) 复杂查询被视为创建并可以恢复的常规资源。

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

用于创建搜索的 POST 正文如下:

      
       "$class":"test.Car",
       "$q":
          "$eq" :  "color" : "red" ,
          "garage" : 
             "$ne" :  "street" : "FirstStreet" 
          
       
    

它基于 Grails(标准 DSL):http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

【讨论】:

【参考方案5】:

RESTful 不建议在 URL 的 /cars/search 中使用动词,这不是 RESTful。过滤/搜索/分页 API 的正确方法是通过查询参数。但是,在某些情况下,您可能不得不打破常规。例如,如果您要搜索多个资源,则必须使用 /search?q=query

您可以通过http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ 了解设计 RESTful API 的最佳实践

【讨论】:

搜索也是名词?【参考方案6】:

虽然在路径中包含参数有一些优势,但在 IMO 中,还有一些重要因素。

并非搜索查询所需的所有字符都允许在 URL 中使用。大多数标点符号和 Unicode 字符都需要作为查询字符串参数进行 URL 编码。我正在努力解决同样的问题。我想在 URL 中使用 XPath,但并非所有 XPath 语法都与 URI 路径兼容。因此,对于简单路径,/cars/doors/driver/lock/combination 适合在驾驶员车门 XML 文档中定位“combination”元素。但是/car/doors[id='driver' and lock/combination='1234']就不是那么友好了。

根据属性之一过滤资源与指定资源之间存在差异。

例如,因为

/cars/colors返回所有汽车所有颜色的列表(返回的资源是颜色对象的集合)

/cars/colors/red,blue,green 将返回红色、蓝色或绿色的颜色对象列表,而不是汽车集合。

要还车,路径是

/cars?color=red,blue,green/cars/search?color=red,blue,green

路径中的参数更难阅读,因为名称/值对未与路径的其余部分隔离,而不是名称/值对。

最后一条评论。我更喜欢/garages/yyy/cars(总是复数)而不是/garage/yyy/cars(可能是原始答案中的错字),因为它避免了改变单数和复数之间的路径。对于加了's'的词来说,变化还不错,但是把/person/yyy/friends改成/people/yyy好像很麻烦。

【讨论】:

是的,我同意...除了我的东西 urls 路径结构应该反映实体之间的自然关系,我的资源的某种地图,就像车库有很多车,一辆汽车属于一个车库等等......让过滤器参数,因为这就是我们正在谈论的,查询查询字符串......你怎么看?【参考方案7】:

我的建议是这样的:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

编辑:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

希望这能给你这个想法。本质上,您的 Rest API 应该很容易被发现,并且应该使您能够浏览您的数据。使用 URL 而不是查询字符串的另一个优点是,您可以利用 Web 服务器上存在的本地缓存机制来处理 HTTP 流量。

这是一个描述 REST 中查询字符串弊端的页面的链接:http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

我使用了 Google 的缓存,因为普通页面对我不起作用,这也是该链接: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

【讨论】:

感谢您的详细回答。在最后一个上,如果我想同时按门的颜色和数量进行搜索怎么办? /cars/colors/red,blue,green/doors/4 好像不对。 网址中的逗号对我来说感觉不对,但仍然有效。我认为这只是一种范式转变。 我不喜欢这个建议。你怎么知道 /cars/colors/red,blue,green/cars/colors/green,blue,red 之间的区别? URI 的路径元素应该是分层的,我真的不认为这是这种情况。我认为这是查询字符串是最合适的选择的情况。 这是一个糟糕的答案。事实上,实现搜索的正确方法是使用查询字符串。如果使用得当,查询字符串一点也不邪恶。引用的文章不是指搜索。提供的示例显然受到了折磨,并且无法很好地支持更多参数。 查询字符串主要是为了解决查询资源的问题,即使有多个参数。扭曲 URI 以启用“RESTful”API 似乎很危险且目光短浅——尤其是因为您必须编写自己的复杂映射来处理 URI 上参数的各种排列。更好的是,使用现有的在 URI 中使用分号的概念:doriantaylor.com/policy/http-url-path-parameter-syntax【参考方案8】:

对于搜索,使用查询字符串。这完全是 RESTful:

/cars?color=blue&type=sedan&doors=4

常规查询字符串的一个优点是它们是标准的且被广泛理解,并且可以从 form-get 生成。

【讨论】:

这是正确的。查询字符串的全部意义在于执行搜索等操作。 确实这是正确的,因为根据RFC3986,路径查询字符串标识资源。更重要的是,正确的命名只是/cars?color=whatever 如果你需要比较器(>、=)呢? /cars?rating @mjs /cars?param=value 用于对汽车列表进行简单的过滤/cars/search?param=value 用于创建搜索(使用ou 没有持久性)结果可能包含搜索评分、分类等。您还可以创建/删除命名搜索,例如/cars/search/mysearch。看那个:***.com/a/18933902/1480391 @YvesM。我困惑了一会儿,但我之前的评论在编辑之前提到了原始答案:***.com/revisions/1081720/1。我同意你的观点,如果搜索是不同的东西,那么search 出现在资源名称中是有意义的。不过,我认为这不是最初的问题。【参考方案9】:

这不是 REST。您不能在 API 中为资源定义 URI。资源导航必须是超文本驱动的。如果你想要漂亮的 URI 和大量的耦合,那很好,但不要称它为 REST,因为它直接违反了 RESTful 架构的约束。

请参阅 REST 发明者的 article。

【讨论】:

你说得对,它不是 REST,它是 RESTful 系统的 URL 设计。但是,您说它违反了 RESTful 架构也是不正确的。 REST 的超文本约束与 RESTful 系统的良好 URL 设计正交;我记得几年前在 REST 列表上与 Roy T. Fielding 进行了一次讨论,我参与了他明确表示的地方。换一种说法,可以有超文本和 URL 设计。 RESTful 系统的 URL 设计就像编程中的缩进;不是必需的,但一个非常好的主意(忽略 Python 等) 对不起,你是对的。我刚刚从 OP 那里得到了他要让客户知道如何构建 URL 的印象——他会将 URL“布局”作为他的 API 的一部分。 将违反 REST。 @aehlke,您应该更新您的答案以匹配您的评论。 符合 2 级 Richardson maturity model。您指的是第 3 级。只需接受 REST 作为可逐步采用的东西。 @Jules Randolph - 抱歉,我的答案是在 Richardson 成熟度模型首次提出几个月后、Martin Fowler 和其他作者推广它之前写的:) 事实上,这是一个值得遵循的指导模型。随意编辑答案。【参考方案10】:

要扩展 Peter 的答案 - 您可以将 Search 设为一流资源:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/id     # show the results of a previously-run search
DELETE  /searches/id     # delete a search (admin)

搜索资源将具有颜色、制造模型、车库状态等字段,并且可以以 XML、JSON 或任何其他格式指定。与 Car and Garage 资源一样,您可以根据身份验证限制对搜索的访问。经常运行相同搜索的用户可以将它们存储在他们的配置文件中,这样就不需要重新创建它们。 URL 将足够短,在许多情况下可以通过电子邮件轻松交易。这些存储的搜索可以作为自定义 RSS 提要的基础,等等。

当您将搜索视为资源时,使用搜索的可能性有很多。

Railscast 中更详细地解释了这个想法。

【讨论】:

这种方法不是违背了使用不安定协议的想法吗?我的意思是,将搜索持久化到数据库是一种有状态的连接......不是吗? 更像是有状态的服务。每次添加新汽车或车库时,我们都会更改服务状态。搜索只是另一种可用于所有 HTTP 动词的资源。 以上如何定义一个URI约定? REST 与漂亮的 URI 或 URI 嵌套等无关。如果您将 URI 定义为 API 的一部分,则它不是 REST。 我以前争论过这个。这不是有状态的,但这是一件可怕的事情。搜索的“删除”不是很清楚,在这里你说它删除了这个搜索实体,但我想用它删除我通过那个搜索找到的结果。请不要将“搜索”添加为资源。【参考方案11】:

虽然我喜欢贾斯汀的回答,但我觉得它更准确地代表了过滤器而不是搜索。如果我想了解名称以 cam 开头的汽车怎么办? 在我看来,您可以将其构建为您处理特定资源的方式: /汽车/凸轮* 或者,您可以简单地将其添加到过滤器中: /汽车/门/4/名称/凸轮*/颜色/红、蓝、绿 就我个人而言,我更喜欢后者,但我绝不是 REST 方面的专家(大约 2 周前才第一次听说它......)

【讨论】:

像这样:/cars?name=cam*【参考方案12】:

贾斯汀的答案可能是要走的路,尽管在某些应用程序中,将特定搜索本身视为一种资源可能是有意义的,例如,如果您想支持命名的已保存搜索:

/search/searchQuery

/search/savedSearchName

【讨论】:

没有。将动作作为资源是毫无意义的。 @thecoshman 正如上面评论中提到的,搜索也是一个名词。 当你保存搜索时,它就变成了一种资源......据我说,这使得这个提议成为一个有效的提议:)

以上是关于用于搜索的 RESTful URL 设计的主要内容,如果未能解决你的问题,请参考以下文章

用于搜索的 RESTful URL 设计

Restful

RESTful

RESTFUL如何指导WEB API设计?

如何设计 RESTful 高级搜索/过滤器

短短的 RESTful API 设计规范