写点什么

实现超媒体

  • 2015-03-06
  • 本文字数:5727 字

    阅读完需:约 19 分钟

为 Web__ 设计、实现和维护 API__ 不仅仅是一项挑战;对很多公司来说,这是一项势在必行的任务。本系列 将带领读者走过一段旅程,从为API__ 确定业务用例到设计方法论,解决实现难题,并从长远的角度看待在Web__ 上维护公共API _。沿途将会有对有影响力的人物的访谈,甚至还有 API__ 及相关主题的推荐阅读清单。_

这篇 InfoQ__ 文章是 Web API从开始到结束系列文章中的一篇。你可以在这里进行订阅,以便能在有新文章发布时收到通知。

有关于如何在API 中使用超媒体方面的各种理论,能够找到的(电子)文字可谓不计其数,但是在实践方面的资源似乎有些匮乏?在近期举办的 API Craft 2014 大会上,许多演讲者为与会者分享了使用超媒体方面的故事,但他们都有些不好意思地承认,他们之前从来没有在公众面前谈论这些话题。因此,我们中的许多人决定要将我们的经验分享给大家。我也将在这里为读者分享我本人在这一主题上所遇到的四个示例。

在本文中,我们将讨论四种关于超媒体在真实情况中的实现:在图片链接中使用超媒体(你很可能已经使用过这种方式了)、GitHub 是如何使用 Link 头信息实现分页的、在例如 iOS 这样的受限系统中使用超媒体,以及 Balanced 是如何使用超媒体理论开发产品的故事。这些故事分别描述了不同的场景,并且各自展示了在 API 设计中使用超媒体的不同方面。

缩略图

虽然许多有关于超媒体的理论都将超媒体描述为整个 API 的基础结构与底层理论,但我还是要为你分享一个小秘密:这一点并非是必需的。即使不将整个 API 设计为完全使用超媒体,你依然能够从超媒体的使用中受益。我将为你分享两个最常见的案例:缩略图和分页。

你可能已经在无意中使用了某些超媒体技术了,我经常在包含了图片,尤其是缩略图的相关 API 中看到这一情况。请考虑一下以下这个来自于 Twitter 的 API 响应(节选):

复制代码
GET https://api.twitter.com/1.1/users/show.json?screen_name=rsarver
{
"name": "Ryan Sarver",
"profile_image_url": "http://a0.twimg.com/profile_images/1777569006/image1327396628_normal.png",
"created_at": "Mon Feb 26 18:05:55 +0000 2007",
"location": "San Francisco, CA",
"profile_image_url_https": "https://si0.twimg.com/profile_images/1777569006/image1327396628_normal.png",
"utc_offset": -28800,
"id": 795649,
"lang": "en",
"followers_count": 276334,
"protected": false,
"profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/113854313/xa60e82408188860c483d73444d53e21.png",
"verified": false,
"time_zone": "Pacific Time (US & Canada)",
"description": "Director, Platform at Twitter. Detroit and Boston export. Foodie and over-the-hill hockey player. @devon's lesser half",
"profile_background_image_url": "http://a0.twimg.com/profile_background_images/113854313/xa60e82408188860c483d73444d53e21.png",

没有人认为 Twitter 有一套完整的超媒体 API,但以上这个响应中所包含的链接却确确实实地应用了超媒体技术。它本 _ 可以 _ 选择使用数据 URI 的方式内联包含这些图片,但它最终选择了使用链接的方式。

由于在响应中包含了链接地址,因此使用该 API 的客户端就能够自由选择要下载怎样的信息。这些链接告知了客户端有哪些选择,并且它们的地址在哪里。换句话说,这就是“货真价实”的超媒体应用。

为了让这种方式所带来的优点更明显一些,让我们来考虑一下这个略有不同的响应内容:

复制代码
GET https://api.example.com/profile
{
"name": "Steve",
"picture": {
"large": "https://somecdn.com/pictures/1200x1200.png",
"medium": "https://somecdn.com/pictures/100x100.png",
"small": "https://somecdn.com/pictures/10x10.png"
}
}

由于在这里应用了超媒体,因此我们无需返回三个不同版本的用户档案图片。我们所做的只是告诉客户端有三种可用的图片尺寸可以选择,并且告诉客户端能够在哪里找到这些图片。这样一来,客户端就能够根据不同的场景,做出符合自身需要的选择。而且,如果客户端只需要一种格式的图片,那就无需下载全部三种版本的图片了。这样一来可谓一箭三雕:既减少了网络负载,又增进了客户端的灵活性,更增进了 API 的可探索性。

我想说的是,或许你已经部署了一套简单的超媒体 API,只是你之前从来没想到罢了。即使你没有将整套 API 都围绕着超媒体进行设计,你也能够从这个场景中获得好处。

分页

只需简单地应用某些超媒体,就能够极大地简化客户端的代码,分页正是一个典型的场景。让我们看看 GitHub 这一现实世界中的示例。在 GitHub 的文档中,提到了它们对于 API 所采取的某种限制。

不同的 API 调用可能会返回不同的默认值。举例来说,如果调用了某个方法去列出 GitHub 上所有的公共代码库,那么所返回的数据集是每页 30 个项目;而如果调用了 GitHub 的 Search API,那么会返回每页 100 个项目的结果。

让我们对照着响应的示例来看一下,这样能够更容易理解这些内容。来看一下 GitHub 是如何实现这一点的吧。

以下的示例是在使用 GitHub 的搜索请求时,所得到一个经过分页的结果集:

复制代码
GET "https://api.github.com/search/code?q=addClass+user:mozilla"

以上请求会返回带有 Link 头信息的响应:

复制代码
Link: <https: api.github.com="" q="addClass+user%3Amozilla&page=2" search="">; rel="next",
<https: api.github.com="" q="addClass+user%3Amozilla&page=34" search="">; rel="last"
</https:></https:>

RFC 5988 所定义的 Link 头,正如其名称所显示的一样,为客户端提供了链接。这些链接各自包含一个 URL 以及一个链接关系,其中的 rel 就代表了链接关系的内容。因为该响应返回的是结果集中的第一页内容,因此 GitHub 所返回的响应中包含了下一页及最后一页的选项。

如果我们继续访问下一个链接,就会得到一个不同的头信息集:

复制代码
Link: <https: api.github.com="" q="addClass+user%3Amozilla&page=15" search="">; rel="next",
<https: api.github.com="" q="addClass+user%3Amozilla&page=34" search="">; rel="last",
<https: api.github.com="" q="addClass+user%3Amozilla&page=1" search="">; rel="first",
<https: api.github.com="" q="addClass+user%3Amozilla&page=13" search="">; rel="prev"
</https:></https:></https:></https:>

在这个响应中,我们会看到其中包含了一个前一页及第一页的链接。

那么这种方式的优点在哪儿呢?那就是客户端代码简化了。设想一下使用 Ruby 的场景,在传统的情况下,如果我们需要获取搜索结果的下一页,通常会这么做:

复制代码
require 'uri'
url = "https://api.github.com/search/code"
per_page = 15
current_page = 1
next_page = 1 # zero based, of course
page = (current_page + next_page * per_page).to_s
query = "addClass+user%3Amozilla"
uri = URI(url)
uri.query = URI.encode_www_form([["q", query], ["page", page]])
puts Net::HTTP.get(uri);

如果使用了超媒体,我们就可以这么做了:

复制代码
# response contains parsed body from previous request.
puts Net::HTTP.get(response.headers[:link].rels[:next])

不仅代码简化了许多,而且出错的机率也小得多。不仅如此,如果 GitHub 有朝一日决定将默认值改为每页 10 个结果,而不是当前的每页 15 个结果,那么第二个示例中的代码也无需进行任何改动。而第一个示例中的代码则不得不进行修改,并且如果代码不及时修改,你的用户就会在使用时遇到错误。

iOS 超媒体

超媒体在 iOS 上的应用正在逐渐普及。原因在于:如果要在 iOS 应用上进行任何改动,你必须经历 Apple 的批准流程。但如果你使用了超媒体,那么服务端就能够改变客户端的行为。在很久之前,我和朋友们就在某个项目中使用了这一技术,恕我不能透露这个项目的名称,以保护这个可爱的小朋友不要碰到麻烦。;)

我们所创建的应用程序是一个音频播客应用,为用户提供大量的音频及视频文件。当时,Apple 在音频方面有一个严格的限制:所提交的应用不允许在 GSM 连接环境下提供高品质的音频文件。不过,我们设计了一个方案以解决了这个问题,那就是链接。

当应用还处于审查阶段时,我们在服务器上只为该播客提供低品质的音频链接。一旦审查通过,我们就在服务器上提供了高品质的音频文件。我们的客户就能够免费升级,获得了超越技术限制所允许的高品质文件。是不是很无耻?哈哈!

当我们想到这个主意后,就立即想到可以在应用的其它部分也使用这项技术。举例来说,某些播客能够进行实时广播,并且允许你通过一通电话参与这档节目。在用户界面上,我们让应用发送一个对服务器的请求,以查询这档节目是否已经开始了。一旦节目开始,我们就会显示一个按钮,让你可以单击这个按钮以参与节目。只要你没有关闭屏幕,应用就会不断地向服务端的终结点发送请求,一旦节目结束,这个按钮就消失了。这种由服务端驱动的互操作性正是超媒体的实用方式,如果我们必须要部署一个新的客户端以改变按钮的状态,这个场景就不可能实现了。

继续来看下一个例子,某档播客节目的主办者可以提供该节目播出的时间。在屏幕上的信息一栏中就能够看到诸如“每个星期五中午”这样的说明。如果这些信息不是从服务器获取的,那么如果主办者打算改变节目播出的时间,用户就不得不更新应用,还得经历绕不过去的“等待审核”过程,才能够获得正确的信息。由于所有的档案都是由服务端驱动的,因此一旦节目的主办者点击了“保存”按钮,所有的应用都能够获得更新的信息。

以上这些和超媒体有什么关系呢。嗯,有两点关系。首先,服务端控制着所有的可能性,而客户端对这些可能性进行展示,这一种方式正是超媒体处理 API 设计的核心所在。以上所有三种应用场景都是对这一原理进行应用的绝佳例子。

其次,我们通过链接的方式实现了这三个场景。在应用刚刚启动时,会从服务端获取一个 XML 配置文件,其中提供了 RSS 的链接、“查看当前是否有节目在播出”的链接、以及对档案信息的链接。应用会随后在需要的时候使用这些链接,为了实现 RSS 的转换,我们只需将“低品质”的链接改为“高品质”的链接,客户就能够自动获得一套完全不同的 RSS 了。

我们还可以按照这个思路继续探索下去。在某些领域中,如果你无法频繁地对客户端进行更新,那么让客户端在不经过更新的情况下对服务端进行正确的响应就是重点所在。在类似于 iOS 的系统或嵌入式设备的场景下,这种限制是非常明显的,因为你的用户通常不会像你所期望的那样频繁地对客户端进行更新……

案例学习:Balanced

最后,我想谈论一下我之前的公司—— Balanced 对超媒体的应用的某些情况。Balanced 的 API 设计应用了超媒体技术,并且遵循了由我参与共同设计的 JSON API 标准。在之前我所提到的示例中,都是在响应中加入了少量的超媒体相关信息,但 Balanced 却是完全由超媒体所驱动的。这个产品的完成效果更好,但也同样遇到了许多挑战。

从好的方面来说,新特性的发布不会破坏已有的客户端的运行。举例来说,该 API 的 1.1 版本是首个完全遵循 JSON API 规格的版本。当 1.1 版本发布之后,Balanced 又推出了一个 Push to Card 的特性,这是一个全新的功能。由于超媒体的存在,我们无需推出一个新的 API 1.2 版本,因为现有的客户端会自动忽略新的特性,而只有新版本的客户端才能够使用这一特性。这就大大简化了运营工作,因为管理多个版本会使部署及开发复杂化。只要 Balanced 继续将新的特性添加到 API 中,他们就会按照相同的办法进行处理。

超媒体的狂热支持者总是在谈论它的好处,但并非超媒体的每个特征都是正面的。站点 Balanced 的立场上,我也要提一提它的负面影响。通常在客户报告了某个与产品支持相关的问题时,你会问客户的第一个问题都是“您是哪位?”在许多情况下,这一问题具体表现为“你的客户 ID 是多少?”但由于 Balanced 使用了超媒体,因此客户没有 ID 号,他们只有一个客户 URL。在有些时候,当你向客户问及他们的“客户 URL”时,他们可能会感到困惑。随着更多的人理解超媒体 API 的概念,这种情况可能会逐渐发生改变。但由于目前超媒体的应用还属于新鲜事物,那么对于完全使用超媒体进行 API 设计的人来说,他需要做好准备,对使用他的设计的客户进行一些使用方面的指导。在 Balanced 的案例中,这就意味着他们要事先为客户传授一些知识,因为许多人还不知道如何开发一个良好的超媒体客户端。虽然说为客户提供一些预先发布的客户端作为参考是个好主意(在本例中,Balanced 必须这么做),但如果使用更为传统的 API,他们就能够将开发精力专注在其它有意义的事情上了。

结论

如你所见,对超媒体的应用存在着多种形式,它并不一定要成为你的 API 的唯一的组织原则。首先,我们谈到了你或许在不经意间就通过图片链接的方式使用了超媒体。随后,我们谈到了 GitHub 以及他们的分页示例。接下来,我们通过一个示例表示一个由服务端驱动的客户端应用不需要频繁地进行更新,这对于类似于 iOS 的受限环境是非常有用的。最后,我们讲到了某个公司将超媒体的应用作为他们的竞争优势的案例,但这种应用也同时带来了一些负面效应。

希望这些真实世界中的超媒体实现能够让你理解,对于超媒体的应用并非那么绝对——要么完全使用,要么完全不用;并且你或许已经在某些场景中使用到它了。我很高兴地看到超媒体在越来越多的 API 中发挥作用,也乐于听到人们分享他们在应用这项技术时遇到的各种成败得失。

关于作者

Steve Klabnik是一位 Rails 项目的成员,也是 Rust 项目的贡献者,同时也是《Rails 4 in Action》、《Designing Hypermedia APIs》及《Rust for Rubyists》等书籍的作者。

为 Web__ 设计、实现和维护 API__ 不仅仅是一项挑战;对很多公司来说,这是一项势在必行的任务。本系列 将带领读者走过一段旅程,从为API__ 确定业务用例到设计方法论,解决实现难题,并从长远的角度看待在Web__ 上维护公共API _。沿途将会有对有影响力的人物的访谈,甚至还有 API__ 及相关主题的推荐阅读清单。_

这篇 InfoQ__ 文章是 Web API从开始到结束系列文章中的一篇。你可以在这里进行订阅,以便能在有新文章发布时收到通知。

查看英文原文: Article: Implementing Hypermedia

2015-03-06 07:232921
用户头像

发布了 428 篇内容, 共 178.7 次阅读, 收获喜欢 38 次。

关注

评论

发布
暂无评论
发现更多内容

百度赵世奇:文心智能体平台加速跑通商业闭环

Geek_2d6073

与Zilliz、Google等探讨中美AI应用与落地,XTransfer受邀参与分享沙龙

XTransfer技术

算法打败文盲,我用向量数据库与RAG,做了个“鲁迅没说过”

Zilliz

开源 Milvus 向量数据库 语义检索 rag

2024年互联网Java面试题最新整理附答案(1100题)

架构师之道

编程 java面试

数字化,企业为什么要做数字化转型

积木链小链

数字化转型 企业管理 数字化

智源研究院发布千万级多模态指令数据集Infinity-MM:驱动开源模型迈向SOTA性能

智源研究院

Vitalik 新文丨以太坊可能的未来:The Splurge

TechubNews

多租户系统的核心概念模型

EquatorCoco

多租户

三维度深度分析:TDengine 数据订阅 vs InfluxDB 数据订阅

TDengine

数据库 tdengine 时序数据库

牛客网最全1000道Java中高级面试题附答案详解,最全面详细,看完稳了

采菊东篱下

Java 面试

2025北京消费电子技术博览会

AIOTE智博会

消费电子展 消费电子展会 消费电子博览会 消费电子展览会

Amphion 推出开源 TTS 模型 MaskGCT,5 秒克隆声音;神秘文生图模型「小熊猫」登顶竞技场丨 RTE 开发者日报

声网

软件测试学习笔记丨Selenium配置浏览器启动状态options

测试人

软件测试

鸿蒙网络编程系列40-TLS数字证书查看及验签示例

长弓三石

DevEco Studio 开发实例 HarmonyOS NEXT 网络与连接

获取API接口数据的最佳实践详解

Noah

捷途旅行者:79171台销量——方盒子SUV的全能伙伴

科技热闻

开发者视角:探索技术无垠,肩负时代重任

Noah

日志管理系统的系统目标是什么?

ServiceDesk_Plus

日志分析 日志管理

现代化可观测性平台(1)

俞凡

架构 云原生 可观测性

MES系统在制造业智能化中的作用是什么

万界星空科技

智能制造 mes 制造业数字化 万界星空科技mes

ppt计时器软件哪个好?掌握这2个技巧,轻松搞定PPT计时!

职场工具箱

职场 PPT 办公软件 AI生成PPT

京东商品评价API的获取和应用

科普小能手

API 接口 API 测试 淘宝API接口 淘宝评论API 淘宝商品API

店铺商品尽在掌握:阿里巴巴API返回值说明

技术冰糖葫芦

API 接口 API 测试 API 协议 API 优先

一篇读懂 C 指针

mazhen

c c++ Linux

资产代币化的崛起:揭开万亿级市场机遇的探索

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 钱包开发 代币开发

Solana生态亮点、代币经济学、竞争定位全览

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 钱包开发 代币开发

TikTok Shop多账户运营怎么防关联?

Ogcloud

海外云手机 tiktok云手机 tiktok运营 tiktok矩阵 tiktok防关联

TikTok运营干货:快速起号教程

Ogcloud

tiktok运营 tiktok直播专线 TikTok养号 tiktok起号 tiktok运营干货

2024-10-30:或值至少 K 的最短子数组 I。用go语言,给定一个非负整数数组 nums 和一个整数 k,我们需要判断数组中是否存在一个最短的非空子数组,使得该子数组所有元素的按位或(OR)运

福大大架构师每日一题

福大大架构师每日一题

鸿蒙智行再迎OTA升级,车载小艺化身私人用车顾问、百科导师

Geek_2d6073

Mint Blockchain 正式宣布推出 Mint Forest 3.0!

NFT Research

blockchain #Web3

实现超媒体_语言 & 开发_Steve Klabnik_InfoQ精选文章