用于搜索的 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,!red
color:(!black,!red)
联合搜索
在车库中搜索 red 或 blue 或 black 门 3 门的汽车 id 1..20 em> 或 101..103 或 999 但不是 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 或者如果您使用 这里有很多适合您的案例的好选择。您仍然应该考虑使用 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 设计的主要内容,如果未能解决你的问题,请参考以下文章