首页 文章

来自RESTful API的分页响应有效负载

提问于
浏览
61

我想在我的RESTful API中支持分页 .

我的API方法应该通过 /products/index 返回产品的JSON列表 . 但是,可能存在数千种产品,我想通过它们进行分页,因此我的请求应该如下所示:

/products/index?page_number=5&page_size=20

但是我的JSON响应需要看起来像什么? API消费者通常会在响应中期望分页元数据吗?或者只是一系列必要的产品?为什么?

看起来Twitter的API包含元数据:https://dev.twitter.com/docs/api/1/get/lists/members(参见示例请求) .

使用元数据:

{
  "page_number": 5,
  "page_size": 20,
  "total_record_count": 521,
  "records": [
    {
      "id": 1,
      "name": "Widget #1"
    },
    {
      "id": 2,
      "name": "Widget #2"
    },
    {
      "id": 3,
      "name": "Widget #3"
    }
  ]
}

只是一系列产品(没有元数据):

[
  {
    "id": 1,
    "name": "Widget #1"
  },
  {
    "id": 2,
    "name": "Widget #2"
  },
  {
    "id": 3,
    "name": "Widget #3"
  }
]

3 回答

  • 25

    ReSTful API主要由其他系统使用,这就是我将分页数据放在响应头中的原因 . 但是,某些API使用者可能无法直接访问响应标头,或者可能正在构建基于API的UX,因此提供一种方法来检索(按需)JSON响应中的元数据是一个优点 .

    我相信您的实现应该包括默认的机器可读元数据,以及请求时的人类可读元数据 . 如果您愿意,可以随每个请求返回人类可读的元数据,或者最好通过查询参数(例如 include=metadatainclude_metadata=true )按需返回 .

    在您的特定场景中,我将包含每个产品的URI和记录 . 这使API使用者可以轻松创建指向各个产品的链接 . 我还会根据我的分页请求的限制设置一些合理的期望 . 实现和记录页面大小的默认设置是可以接受的做法 . 例如,GitHub's API将默认页面大小设置为30条记录,最多为100条,另外还设置了查询API的速率限制 . 如果您的API具有默认页面大小,则查询字符串只能指定页面索引 .

    在人类可读的场景中,当导航到 /products?page=5&per_page=20&include=metadata 时,响应可能是:

    {
      "_metadata": 
      {
          "page": 5,
          "per_page": 20,
          "page_count": 20,
          "total_count": 521,
          "Links": [
            {"self": "/products?page=5&per_page=20"},
            {"first": "/products?page=0&per_page=20"},
            {"previous": "/products?page=4&per_page=20"},
            {"next": "/products?page=6&per_page=20"},
            {"last": "/products?page=26&per_page=20"},
          ]
      },
      "records": [
        {
          "id": 1,
          "name": "Widget #1",
          "uri": "/products/1"
        },
        {
          "id": 2,
          "name": "Widget #2",
          "uri": "/products/2"
        },
        {
          "id": 3,
          "name": "Widget #3",
          "uri": "/products/3"
        }
      ]
    }
    

    对于机器可读的元数据,我会在响应中添加Link headers

    Link: </products?page=5&perPage=20>;rel=self,</products?page=0&perPage=20>;rel=first,</products?page=4&perPage=20>;rel=previous,</products?page=6&perPage=20>;rel=next,</products?page=26&perPage=20>;rel=last
    

    (链接头值应该是urlencoded)

    ...如果你这样选择,可能还有一个自定义 total-count 响应头:

    total-count: 521
    

    在以人为中心的元数据中显示的其他分页数据对于以机器为中心的元数据可能是多余的,因为链接头让我知道我在哪个页面和每页的数量,我可以快速检索数组中的记录数 . 因此,我可能只会为总计数创建一个 Headers . 您可以随时改变主意并添加更多元数据 .

    另外,您可能会注意到我从您的URI中删除了 /index . 一个普遍接受的惯例是让您的ReST endpoints 公开集合 . /index 在最后的泥泞中略有上升 .

    在使用/创建API时,这些只是我喜欢的一些内容 . 希望有所帮助!

  • 79

    作为编写了几个用于使用REST服务的库的人,让我向客户提供有关为什么我认为将结果包装在元数据中的方法:

    • 如果没有总计数,客户端如何知道它还没有收到所有内容并且应该继续在结果集中进行分页?在没有_829187实际获取更多数据的UI中 .

    • 在响应中包含元数据允许客户端跟踪较少的状态 . 现在我不必将我的REST请求与响应匹配,因为响应包含重建请求状态所需的元数据(在这种情况下,光标到数据集中) .

    • 如果状态是响应的一部分,我可以同时对同一个数据集执行多个请求,并且我可以按它们到达的任何顺序处理请求,这不一定是我发出请求的顺序 .

    一个建议:像Twitter API一样,你应该用一个直的索引/光标替换page_number . 原因是,API允许客户端设置每个请求的页面大小 . 返回的page_number是客户端到目前为止请求的页数,还是给出最后一次使用的page_size的页面数(几乎可以肯定是后者,但为什么不完全避免这种歧义)?

  • 10

    我建议为它添加 Headers . 将元数据移动到 Headers 有助于摆脱 resultdatarecords 之类的包络,并且响应正文仅包含我们需要的数据 . 如果您也生成分页链接,则可以使用Link标头 .

    HTTP/1.1 200
        Pagination-Count: 100
        Pagination-Page: 5
        Pagination-Limit: 20
        Content-Type: application/json
    
        [
          {
            "id": 10,
            "name": "shirt",
            "color": "red",
            "price": "$23"
          },
          {
            "id": 11,
            "name": "shirt",
            "color": "blue",
            "price": "$25"
          }
        ]
    

    有关详情,请参阅:

    https://github.com/adnan-kamili/rest-api-response-format

    对于招摇文件:

    https://github.com/adnan-kamili/swagger-response-template

相关问题