【问题标题】:RESTful URL design for search用于搜索的 RESTful URL 设计
【发布时间】: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。

标签: rest


【解决方案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)

联合搜索
在车库中搜索 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.3see 1.2.3
url path parameter syntax
CSS3 attribute matching
IBM: 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。看那个:stackoverflow.com/a/18933902/1480391
        • @YvesM。我困惑了一会儿,但我之前的评论在编辑之前提到了原始答案:stackoverflow.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 正如上面评论中提到的,搜索也是一个名词。
        • 当你保存搜索时,它就变成了一种资源......据我说,这使得这个提议成为一个有效的提议:)
        猜你喜欢
        • 1970-01-01
        • 2013-11-24
        • 1970-01-01
        • 2018-02-11
        • 2011-06-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多