RESTful URL设计进行搜索


427

我正在寻找一种将搜索表示为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

多领域的情况?


16
尽管英语看起来更好,但是混合使用/cars/car没有语义,因此不是一个好主意。如果该类别下有多个项目,请始终使用复数形式。
Zaz

4
这些是错误的答案。搜索应使用查询字符串。正确使用(即用于搜索)时,查询字符串是100%RESTful的。
pbreitenbach 2010年

Answers:


435

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

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

常规查询字符串的一个优点是它们是标准的并且被广泛理解,并且可以从表单获取中生成。


42
这是对的。查询字符串的全部目的是用于执行搜索之类的工作。
aehlke

22
确实,这是正确的,因为按照RFC3986,路径查询字符串标识资源。更重要的是,适当的命名就可以了/cars?color=whatever
Lloeki 2012年

35
在需要比较器(>,<,<=,> =)的情况下呢?/ cars?rating <= 3?
杰西

3
如果要访问嵌套在查询字符串下的资源怎么办?例如,/cars?color=blue&type=sedan&doors=4/engines将无法正常工作
安倍沃尔克

9
@mjs /cars?param=value用于在汽车列表上进行简单过滤,并/cars/search?param=value用于创建搜索搜索结果不包含持久性),搜索结果可能包含搜索评分,分类等。您还可以创建/删除诸如的命名搜索/cars/search/mysearch。看一下:stackoverflow.com/a/18933902/1480391
Yves M.

121

REST风格的漂亮的URL设计是用来显示基于结构的资源(类似目录的结构,日期:文章/ 2005/5/13,对象和它的属性,..),斜线/表示分层结构,使用-id来代替。

层次结构

我个人更喜欢:

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

如果用户删除/car-id零件,它将带来cars预览-直观。用户完全知道他在树上的什么位置,在看什么。他从第一眼就知道车库和汽车是有联系的。/car-id也表示它属于不同/car/id

正在搜寻

searchquery是可以的,只有您的偏好,应该考虑什么。有趣的部分是加入搜索时出现的(见下文)。

/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)

加入搜索
搜索红色蓝色黑色汽车行驶3门在车库编号1..20101..103999,但不是 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
然后,您可以构建更复杂的搜索查询。(有关匹配子字符串的想法,请参见CSS3属性匹配。例如,搜索包含“ bar”的用户user*=bar。)

结论

无论如何,这可能是你最重要的部分,因为你可以做你喜欢的毕竟只是记住,REST风格的 URI代表的结构是很容易理解如类目录/directory/file/collection/node/item,日期/articles/{year}/{month}/{day}..当你忽略任何最后一个细分,您都会立即知道得到什么。

所以..,所有这些字符都可以不编码

  • unreserved:a-zA-Z0-9_.-~
    通常允许同时编码和不允许编码,这两种用法是等效的。
  • 特殊字符: $-_.+!*'(),
  • 保留:;/?:@=&
    可出于其表示的目的而未经编码使用,否则必须对其进行编码。
  • 不安全:<>"#%{}|\^~[]`
    为什么不安全,为什么应该编码:RFC 1738见2.2

    另请参阅RFC 1738#page-20了解更多字符类。

RFC 3986参见2.2
尽管我之前说过,但这是分隔符的常见区别,这意味着某些比其他更重要。

  • 通用分度计: :/?#[]@
  • 亚微米 !$&'()*+,;=

更多阅读:
层次结构:请参见2.3请参见1.2.3
url路径参数语法
CSS3属性匹配
IBM:RESTful Web服务-基础知识
注:RFC 1738由RFC 3986更新


3
我不相信我还没有考虑过在查询字符串中使用JSON。这是我面临的一个问题的答案-无需使用的复杂搜索结构POST。另外,您在回答中给出的其他想法也非常重要。非常感谢!
gustavohenke

4
@Qwerty:很棒的帖子!我想知道:使用;而不是的唯一原因&是可读性?因为如果是这样,我想我实际上更喜欢,&因为它是更常见的分隔符...对吗?:) 谢谢!
Flo 2015年

3
@Flo确实是:),但是请记住,&只有开发人员才能知道作为分隔符。父母,祖父母和未受过教育的人口接受普通书面文字中使用的分隔符。
2015年

17
当查询字符串易于理解且标准时,为什么要组成一个非标准方案?
pbreitenbach

1
@Qwerty没有什么阻止您进入/ search?cars = red,blue,green&garages = 1,2,3或如果您使用<multiselect>形式:/ search?cars = red&cars = blue&
garages

36

尽管在路径中具有参数具有某些优势,但IMO有一些优势。

  • URL并非允许搜索查询所需的所有字符。大多数标点符号和Unicode字符都需要URL编码为查询字符串参数。我正在努力解决同样的问题。我想在URL中使用XPath,但并非所有XPath语法都与URI路径兼容。因此,对于简单路径,/cars/doors/driver/lock/combination将适当的位置combination放在驾驶员车门XML文档中的' '元素。但是/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似乎很麻烦。


2
是的,我同意...除了我的东西,URL路径结构还应该反映实体之间的自然关系,某种形式的我的资源地图,例如车库里有很多汽车,汽车属于车库等等...过滤器参数,这就是我们要讨论的,查询字符串...您如何看待?
09年

31

要扩展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或任何其他格式指定。与“汽车和车库”资源一样,您可以基于身份验证来限制对“搜索”的访问。经常执行相同搜索的用户可以将其存储在自己的个人资料中,从而无需重新创建它们。这些URL足够短,以致在许多情况下都可以通过电子邮件轻松地进行交易。这些存储的搜索可以作为自定义RSS提要的基础,等等。

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

这个想法在Railscast中有更详细的解释。


6
这种方法与使用不安定协议的想法背道而驰吗?我的意思是,持续搜索数据库有点像是有状态的连接……不是吗?
09年

5
这更像是有状态服务。每当我们添加新的汽车或车库时,我们也在更改服务状态。搜索只是可以与所有HTTP动词一起使用的另一种资源。
Rich Apodaca,2009年

2
以上内容如何定义URI约定?
Rich Apodaca,2009年

3
REST与漂亮的URI或URI嵌套等无关。如果将URI定义为API的一部分,则它不是REST。
aehlke

2
我之前已经争论过这一点。这绝不是有状态的,但这是一件可怕的事情。搜索的“删除”不是很清楚,在这里您是说它删除了该搜索实体,但是我想使用它删除通过该搜索找到的结果。请不要将“搜索”添加为资源。
thecoshman 2014年

12

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

/search/{searchQuery}

要么

/search/{savedSearchName}

11
没有。将动作作为资源从来没有道理。
thecoshman 2014年

3
如上面评论中所述,@ thecoshman,搜索也是一个名词。
andho

6

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

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

这不是REST。您无法为API内部的资源定义URI。资源导航必须是超文本驱动的。如果您想要漂亮的URI和大量的耦合,但是不要将其称为REST,那是很好的,因为它直接违反了RESTful体系结构的约束。

请参阅REST的发明者的这篇文章


28
您说的不是REST,而是RESTful系统的URL设计。但是,您在说它违反RESTful体系结构时也是错误的。REST的超文本约束与RESTful系统的良好URL设计正交。我记得几年前与Roy T. Fielding在REST列表上进行了一次讨论,我参加了他如此明确地陈述。换句话说,可以有超文本和URL设计。RESTful系统的URL设计就像编程中的缩进一样。不是必需的,而是一个非常好的主意(忽略Python等)
MikeSchinkel 2010年

2
抱歉,您是对的。我刚从OP中得到的印象是,他将使客户知道如何构造URL-他会将URL“布局”作为其API的一部分。将违反REST。
aehlke

@aehlke,您应该更新答案以匹配您的评论。
詹姆斯·麦克马洪

1
它符合2级Richardson成熟度模型。您指的是第3级。只需将REST视为可以逐步采用的东西即可。
Jules Randolph'2

1
@Jules Randolph-道歉,我的答案是在Richardson成熟度模型首次被提出之后几个月才写的,而在Martin Fowler和其他作者普及它的几个月前:)的确,这是一个有启发性的模型。随时编辑答案。
aehlke

1

尽管我喜欢贾斯汀的回答,但我觉得它更准确地代表了过滤器而不是搜索。如果我想了解名称以cam开头的汽车怎么办?

以我的方式看,您可以将其构建为处理特定资源的方式:
/ cars / cam *

或者,您可以将其简单地添加到过滤器中:
/ cars / doors / 4 / name / cam * / colors / red,蓝色,绿色就

我个人而言,我更喜欢后者,但是我绝不是REST的专家(仅在大约2周前才听说过REST ...)


像这样:/cars?name=cam*
DanMan


1

另外我还建议:

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

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


1

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

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

这样可以更灵活地描述搜索,并避免服务器URL长度限制。


-4

我的建议是这样的:

/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


1
感谢您的详细回答。在最后一个上,如果我想同时按颜色和门数进行搜索怎么办?/ cars / colors / red,blue,green / doors / 4似乎不太正确。
帕兰德

2
URL中的逗号不适用于我,但仍然是有效的休息。我认为这只是范式转变。
Justin Bozonier

21
我不喜欢这个建议。你怎么知道的区别/cars/colors/red,blue,green/cars/colors/green,blue,red?URI的path元素应该是分层的,在这里我并没有真正看到这种情况。我认为在这种情况下,查询字符串是最合适的选择。
troelskn,2009年

62
这个答案很差。实际上,实现搜索的正确方法是使用查询字符串。正确使用查询字符串丝毫不邪恶。引用的文章不是指搜索。所提供的示例显然受到了折磨,无法在更多参数的情况下保持良好状态。
pbreitenbach

4
使用querystrings主要是为了解决查询资源的问题,即使具有多个参数也是如此。转换URI以启用“ RESTful” API似乎是危险且短视的-尤其是因为您必须编写自己的复杂映射才能处理URI上参数的各种排列。更好的是,在URI中使用分号的已有概念:doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G
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.