【问题标题】:REST URL design for greater than, less than operationsREST URL 设计用于大于、小于操作
【发布时间】:2011-01-06 11:10:09
【问题描述】:

我在为休息服务设计一个 URL 时遇到了一些困难,该服务可以根据分页作为一种操作来处理客户请求,或者将大于或小于运算符的请求作为另一种操作来处理。例如:

分页:

GET /customers/0/100

这将为第 0 页获得 100 个客户。

大于/小于:

我还需要一个 URL 设计来吸引 id 大于 n 的客户(例如,假设为 716)。您将如何在网址中包含“大于”或“小于”。我必须记住,字符“>”和“

GET /customers/greaterthan/716
GET /customers/lessthan/716

我不能使用范围,因为这会与上面指定的分页模式冲突,并且在任何情况下都不是一个好的解决方案,例如:

GET /customers/716/999999999999
GET /customers/0/716

我确定我遗漏了一些明显的东西 - 有没有人有更好的解决方案?

【问题讨论】:

  • 你打算如何在实时场景中实现这个——只是为了好奇
  • 这是使用 Java 中的 Restlet 框架完成的。本质上,我正在查询数据库中的记录。

标签: rest url


【解决方案1】:

分页大于小于对我来说听起来像是查询参数,因为您正在使用这些参数查询您的资源。所以你应该这样做:

/customers?page=1,或
/customers?page=1&gt=716,或
/customers?page=1&gt =716

您甚至可以限制页面大小:

/customers?page=1&gt=716&lt=819&maxpagesize=100

其中 gt 代表大于(与 xml 转义相同),lt 代表小于。

【讨论】:

  • 对不起,我想我在这里混淆了每个人 - 我并不是说同时进行分页和多于,少于操作。分页是一个单独的函数,小于/大于另一种类型的函数。
  • 没关系。分页和更少/更大是不相关的问题。您应该确定您的资源。在您的情况下,资源是客户。分页和更少/更大都是对资源的查询,但它仍然是同一个资源。因此,您不应尝试在 url (path-param) 中插入您的功能,而应使用查询参数。最后,您的调用将到达相同的资源类(在 java 中),然后您可以将查询参数添加到 SQL 查询或类似的东西中。
  • 即使您将参数放在查询字符串中,您仍然指的是不同的资源。不过没关系。两个不同的资源仍然可以渲染同一底层数据集的部分。简单规则:如果它是一个不同的 URL 并且它没有重定向,那么它就是一个不同的资源。
  • 另外,您应该确保您的查询字符串参数始终处于相同的顺序(假设它们是独立的),否则 /resource?a=1&b=2 是与 /resource?b= 不同的资源2&a=1 并且不能为另一个返回缓存条目
  • Tarlog 似乎在这里提供了一个很好的解决方案,但是其他人可能会发现这是 API 设计和实践的有用资源...apigee.com/about/api-best-practices/all/webcast
【解决方案2】:

如果您有多个参数,并且需要为每个参数应用一些条件,我建议您将 JSON 对象传递给参数。

考虑你想为idpage 做一个条件:

/customers?id={"lt": 100, "gt": 30}&page={"start": 1, "size": 10}

它表示我希望在 页面 1 和 小于 100 且大于 30 的客户>页码,共 10 页。

所以现在如果你想为其他参数应用另一个条件,你可以这样做:

/customers?id={"lt": 100, "gt": 30}&children={"lt": 5, "gt": 2}&page={"start": 1, "size": 10}

并且此查询表示 ID小于 100 且大于 30 的客户,小于 5 和 的儿童页码中大于 2 1,页面大小为 10。

我强烈建议您阅读有关设计 RESTful API 的文档:http://blog.luisrei.com/articles/rest.html

【讨论】:

    【解决方案3】:

    @Julio Faerman:

    好吧,当您获得多个参数时,问题就开始了。想象一下“18 岁以上、60 岁以下且有 2 个以上孩子的客户”的查询字符串。

    您可以定义任何您喜欢的查询参数,例如:

    /customers?min-age=19&max-age=59&min-children=3
    

    在我的示例中 min 和 max 是整数并且包含在内。如果您愿意,可以更改它。请记住,URI 中的任何内容都表示资源标识符的一部分。我个人的看法是,? 之后的内容等同于 SQL 查询的WHERE 部分中的子句(加上ORDER BYLIMIT,此处未显示):

    SELECT * FROM customers WHERE age>=19 AND age<=59 AND children>=3
    

    编辑:
    代替min-max- 前缀,您可以允许&gt;&lt;(也可能是!)作为参数名称的最后一个字符,因此您有一个名为min-age 的参数,而不是min-age @,当与查询字符串中的值结合时,最终看起来像 age&gt;=19 :-)
    显然,只有当比较中有等号时,您才能使用此技巧。

    【讨论】:

    • 似乎是坚持休息的最可靠方法!如果他们在查询中使用 json - 那么最好使用 mongo 查询样式并使用 x-method-override = get 进行 POST。否则一切都在“?”之后到达。 - 称为过滤选项。
    • @ValentinRusk 我不明白。您可以用您的母语重新发布它,以便我自己翻译吗? Тырусский?
    • Лучший выбор для REST !!! Если использовать JSON в GET, тогда лучше идти по пути стиля Mongo Query + header: x-method-override。 В первом случае, все что приходит после “?” - считается фильтрами.
    • @ValentinRusk 好的,不是问题,只是恭维:-) Спасибо!
    【解决方案4】:

    REST 是一种架构风格,不应被视为特定于 HTTP。 URI 的模式并不是使架构成为 RESTful 的原因。

    话虽如此,您可能希望创建 URI,以便这些查询作为查询参数出现在字符串末尾,例如

    /customers?min=0&max=76
    

    【讨论】:

    • 超过 76 的你会怎么做?
    • 我认为这些参数可以是可选的,你可以放一个'min'参数。
    【解决方案5】:

    这就是我的做法。

    假设您有字段 age,它是一个数字。

    这就是网址的样子
    等于:/filter/age=5
    大于:/filter/age[gt]=5
    大于等于:/filter/age[gte]=5
    小于:/filter/age[lt]=5
    小于等于:/filter/age[lte]=5
    不等于:/filter/age[ne]=5

    然后,当我将这些参数传递给后端时,我有一个脚本,它只解析密钥并根据年龄[INSERT_OPERATOR_HERE] 将其转换为正确的过滤器

    【讨论】:

      【解决方案6】:

      假设您要获取服务器日志并假设您的数据如下所示:

      {
          "protocol": "http",
          "host": "example.domain.com",
          "path": "/apis/classified/server/logs",
          "method": "GET",
          "ip": "::ffff:127.0.0.1",
          "time": 1483066346338,
          "usingTime": 12,
          "status": 200,
          "headers": {
              "user-agent": "Mozilla/5.0 Chrome"
          }
      }
      

      而你想这样查询,在哪里

      • protocol 等于 'http'
      • host 等于 'example.domain.com',或不等于 'example.domain.me'
      • path 等于'/apis/classified/server/logs',或者喜欢/*classified\/server*/
      • method 等于 'DELETE' 或不等于 'GET'['POST', 'PUT', 'DELETE']
      • ip 等于 '127.0.0.1',或不等于 '127.0.0.1'
      • usingTime 大于3500,或者大于等于1500 小于等于3500
      • status 等于 404,或不等于 200,或大于或等于 400 且小于 500。
      • headers.user-agent 喜欢/*chrome*/i

      这里的路线是这样的:

      • /apis/classified/server/logs?path=/apis/classfied
      • /apis/classified/server/logs?path.regex=*classfied*
      • /apis/classified/server/logs?method.ne=GET
      • /apis/classified/server/logs?method=POST&amp;method=PATCH&amp;method=PUT
      • /apis/classified/server/logs?usingTime.gte=1500&amp;usingTime.lte=2500
      • /apis/classified/server/logs?headers.user-agent.regex=*chrome*

      如果您使用 express.js 作为服务器,您的 req.query 的结构如下:

      • {"path": "/apis/classfied"}
      • {"path": {"regex": "*classfied*"}}
      • {"method": "DELETE"}
      • {"method": ["GET","POST","PUT"]}
      • {"usingTime": {"gte": "1500","lte": "2500"}} (lte: 小于或等于)
      • {"status": {"ne": "200"}}}(ne: 不等于)
      • {"path": {"regex": "*classfied*"}}
      • {"headers": {"user-agent": {"regex": "*chrome*", "flag": "i"}}}

      你将使用大量的 if-else 来组成你的 mongoose 查询方法,或者 SQL 字符串。

      【讨论】:

        【解决方案7】:

        一些基于 REST 的标准为这个问题提供了足够的方法。例如https://www.hl7.org/fhir/stu3/search.html

        您的问题可以这样解决: GET /customers?id=ge500&amp;id=lt1000

        此外,OData 的互操作性远胜于任何行业级标准。它提出了这种风格:GET /customers?$filter=id ge 500 and id lt 1000

        【讨论】:

          【解决方案8】:

          我会将它实现为一个范围,如果任一侧是开放的,就不要填充它。

          GET /customers/16-
          GET /customers/-716
          

          请求所有客户时,不要全部添加,留空即可

          GET /customers
          

          或者,当您需要 - 登录您的号码/代码时,请使用:

          GET /customers/16/
          GET /customers//716
          GET /customers/16/716
          

          如果正斜杠是数字的一部分,您可以转义它们。

          【讨论】:

          • 我今天偶然发现了这个。多么优雅的解决方案。不幸的是,它只适用于 ID,不适用于查询,所以 /customers/16-25 很好,因为我们知道 '-' 表示一个范围,但如果它是查询字段,它就不起作用:/customers?name=123-127是name值为123到127之间的客户,还是name等于字符串“123-127”的客户?没办法说。
          • 我喜欢这个答案。还要检查 nodejs 的小范围模块(npmjs.com/package/tiny-range),你也可以使用这个符号:~1,2,3,5~10,20~
          【解决方案9】:

          我建议使用查询字符串。

          我建议使用以下模板之一:

          ?amount=~gt~100&age=~gt~16
          
          ?amount=gt100&age=gt16
          
          ?amount=gt 100&age=gt 16
          
          ?amount=greater_than(1)&age=less_than(69)
          

          【讨论】:

            【解决方案10】:

            警告:阅读下面的免责声明

            我不知道这个问题是否与c#有关。但是,如果您在服务器端使用 linq 进行查询,我还有另一个建议:

            GET /customers?filter=CustomerNo>=16 AND CustomerNo<=716
            

            然后,在服务器端:

            using System.Linq.Dynamic;
            
            var filteredCustomers = myCustomerTable.Where(filter);
            

            我认为这应该是最灵活的解决方案。


            警告于 2017 年 1 月 19 日添加 我刚刚注意到 Dynamic Linq 存在代码注入漏洞。上面的示例开启了客户端可以在服务器上启动代码执行的可能性。

            【讨论】:

            • 它看起来像一个GET /database?query=SELECT * FROM customers WHERE CustomerNo&gt;=16 AND CustomerNo&lt;=716 端点。
            【解决方案11】:

            我愿意

             GET /customers/greaterthan=16;lessthan=716/
            

            因为顺序并不重要。您甚至可以在顶部进行分页:

             GET /customers/greaterthan=16;lessthan=716/page/10
            

            或者(使用适当的请求路由器):

             GET /customers/16-716/page/10
            

            并且没有“过滤器”:

             GET /customers/all/page/10
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-08-13
              • 2016-10-18
              相关资源
              最近更新 更多