写点什么

企业级 REST = 自定义、创造和标准化 Media Type

  • 2011-06-09
  • 本文字数:3918 字

    阅读完需:约 13 分钟

关于 REST,经常容易引起困惑的一个问题是:在 Machine-to-machine 的 REST 应用中, 客户端怎么才能在没有任何预先知识的情况下,就能跟随服务器端返回的超文本,从而自适应地将应用程序逻辑进行下去?

答案是不可能。Client 端必须提前了解足够多的 server 端返回的超文本的知识,才能跟随其中的指示将逻辑进行下去。那跟 SOAP、WSDL 之类的又有啥区别? 答案在于 REST 要求的“足够知识”要远远弱于 WSDL 对服务接口定义的约束,其灵活性则高于 WSDL。毕竟,REST 架构风格着眼于生命周期较长的、不断演化的、跨组织边界的应用程序。可以用来满足 HATEOAS 这个 REST 约束的武器就是 Media Type: 扩展已有的 Media Type,发明新的 Media Type,并在合适的范围内标准化

来看两个例子。

Web 的成功与其说是 HTTP 的成功,不如说是 HTML 的成功,或者说 text/html 这种 Media Type 的成功。HTML 定义了一组有限的、标准化的、特定领域的标签。所有的 HTML 客户端都能理解,并按照自己的能力渲染出 Web 服务器返回的任何合法的 HTML。从全功能的 Chrome/FireFox/Safari/IE,到只是显示文本的 lynx,到资源受限环境如手机中的各种浏览器,都能将多姿多彩的 Web 呈现给最终用户,并引导他们进行下一步交互。

最经常被拿来作为 machine-to-machine REST 应用成功案例的则是 RSS/ATOM。这族标准定义了新的 Media Type:application/rss+xml、application/atom+xml。不出意外地,这组 Media Types 定义了一组有限的、标准化的、特定领域的标签。每一个 RSS/ATOM 客户端应用都可以从某个资源的 application/rss(atom)+xml 的表述开始,顺藤摸瓜地遍历每个感兴趣的资源。这些客户端都理解每一个 RSS/ATOM 定义的标签。服务端可以自由地改变新资源的 URI/URL Template 而不必担心破坏现有的 Client,因为这些 Client 并不依赖于预先设定的 URL Template,而仅仅依赖于一个 Root 的表述,及标准的 link relations。

回到我们的问题。那么 REST 要求的“足够知识”要弱到什么程度,才能既使客户端能理解服务器给出的线索,又不致于耦合得太紧以致服务器和客户端无法独立演化? 观察上面两个例子,它们有以下共同特点:

  1. Response 是 Well-formed,可以被客户端解析。这是废话,除了 AI 应用,绝大部分网络应用都得有明确定义的协议。但这意味着 REST 应用中也必须明确定义客户端和服务器数据交换的格式
  2. Response 中包含了当前上下文 (或资源) 相关的信息,但对我们这个问题来说,更重要的是包含了对其它资源进行访问的线索。是的,它是用最最基本最最普通的 link 来实现的,足够弱。那语义是否足够清晰到客户端能理解每一个 link,能够选择正确的 link 并知道如何访问呢? 我们来看看 link 上都能承载啥语义 (from http://www.ietf.org/rfc/rfc5988.txt ):
复制代码
Link           = "Link" ":" #link-value
link-value     = "<" URI-Reference ">" *( ";" link-param )
link-param     = ( ( "rel" "=" relation-types )
                 | ( "anchor" "=" <"> URI-Reference <"> )
                 | ( "rev" "=" relation-types )
                 | ( "hreflang" "=" Language-Tag )
                 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
                 | ( "title" "=" quoted-string )
                 | ( "title*" "=" ext-value )
                 | ( "type" "=" ( media-type | quoted-mt ) )
                 | ( link-extension ) )
  link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
                 | ( ext-name-star "=" ext-value )
  ext-name-star  = parmname "*" ; reserved for RFC2231-profiled
                                ; extensions.  Whitespace NOT
                                ; allowed in between.
  ptoken         = 1*ptokenchar
  ptokenchar     = "!" | "#" | "$" | "%" | "&" | "'" | "("
                 | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
                 | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
                 | "[" | "]" | "^" | "_" | "`" | "{" | "|"
                 | "}" | "~"
  media-type     = type-name "/" subtype-name
  quoted-mt      = <"> media-type <">
  relation-types = relation-type
                 | <"> relation-type *( 1*SP relation-type ) <">
  relation-type  = reg-rel-type | ext-rel-type
  reg-rel-type   = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
  ext-rel-type   = URI

这里我们重点考察一下 relmedia-type 属性。

在 ATOM( http://www.ietf.org/rfc/rfc4287.txt ) 里,缺省定义了 5 种 link relations,分别是 alternate,related,self,enclosure 和 via。标准赋予它们明确的语义,比如 alternate 表示 link 所指向的目标是当前资源的备用版本。客户端因此可以理解每个 link 的含义,自动或引导用户完成后面的操作。如何完成?则牵扯到 media-type。比如如果 media-type 定义的是 audio/mp4,客户端则可以显示播放选项或自动播放或干脆忽略。

这里 link 以及 media-type 等属性帮助形成了一个递归的过程。我们从某个 link 开始,知晓它的 media-type(所谓知晓它的 media-type,就是能理解这种 media-type 定义的每一个元素的语义),我们就能够得到这个 link 所指向的资源以这种 media-type 表现出来的一种表述,其中包含了其它可用的 link 以及 media-type,可以让这个过程一直继续下去,直到客户端觉得可以了或者服务端返回了不包含任何其它 link 的表述。

然而初始的 media-type 和 link relations 总是有限的,我们如何应对现有 media-type 表达不了的语义?这就是 HTML 和 RSS/ATOM 例子包含的第三个要素。

  1. 定义领域相关的 media-type。HTML 的问题域是如何表达各种显示效果,RSS/ATOM 则是如何发布信息。media-type 以及 link relations 等都是可扩展的。我们要做的就是为我们的特定领域的应用定义扩展,如果现存的标准化的元素不够用的话。这里有一个问题,就是 REST 应用范围的问题。如果我们的应用是面向 Internet 的,面向无数已知的未知的客户端应用的,则意味着我们必须尽可能标准化我们扩展的 media-type,或者至少让它广泛接受。而对于企业内部以 REST 架构的应用,我们同样面临标准化的问题,只不过范围可能小一点,至少是企业内部需要有共同接受的 media-type。
    前面我们看到了良好定义的 media-type 是如何帮助客户应用理解 server 端的 response,而又不致紧密耦合服务端的实现的。我们需要一个企业开发的例子来验证一下我们的理解。比如要开发一个企业内部不同应用之间共享用户信息的应用,包括权限信息,当前可以执行的操作,可以访问的应用,以及应用之间共享的 Preference 设置等。我们可以定义一种叫 application/vnd.tw.account+xml 的 media-type 可以有以下的片段
复制代码
<account>
<preference>
<link rel="preference" media-type="application/vnd.tw.account.preference+xml" href="http://xxx/preference" />
<link rel="edit" media-type="text/html" href="http://xxx/preference/editForm">
</preference>
<subscribed-services>
<subscribed-service>
<name>Taxes</name>
<link rel="subscription" media-type="text/html" href="http://taxes.xxx.com" />
</subscribed-service>
<subscribed-service>
<name>Audit</name>
<link rel="subscription" media-type="text/html" href="http://audit.xxx.com" />
</subscribed-service>
</subscribed-services>
<contacts>
<contact>
<name>Tom</name>
<link rel="contact" media-type="application/xfn+xml" href="http://account.xxx.com/tom" />
</contact>
</contacts>
</account>

这样无论是交互式客户端像浏览器还是自动化的后台应用,都可以按照这段 media 的指示进行自己感兴趣的操作。

URL Template Considered Harmful

如果这些都实现的话,就可以有一个推论:URL Template 不是必须的,甚至是有害的。它限制了服务器端的变化。客户应用应总是从 Root Resource 开始,在特定 Media Type 的引导下解析出其它的 URI,给予服务程序演化的灵活性而不是按照 URL Template 这种预先的知识来推算。

用 REST 做过很多项目的 Xu Hao 对 Url template 也有同样的看法:“URL template 在我看来是有害的,它是一种隐含的服务器和客户端约定。此外还有一点,由于大多数 URL template 是用来表达 state transfer 的 URL 的,比如 /xxx/approve 之类的,使得客户端必须了解服务器的状态转移的细节,这在我看来是一个更大的问题。这使得客户端和服务器的耦合变得更加紧密,同时这种风格极度鼓励服务器借由客户端来维持状态的一致性”。

很多 REST 相关的文章都把大量的篇幅给了 HTTP,比如 HTTP Verbs POST/GET/PUT/DELETE,HTTP Status Code 等,而 media type 着墨不多。Xu Hao 在社区中曾很多次的提起自定义 Media Type,对 REST 应用不多可能当时不太明白,现在越来越多的人认识到扩展和标准化更多的 media type 才能更多的发挥 REST 的潜力。

参考资料

2011-06-09 21:533190

评论

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

数据无界、湖仓无界,Apache Doris 湖仓一体典型场景实战指南(下篇)

SelectDB

数据湖 Doris LakeHouse trino 湖仓一体

HarmonyOS @Reusable 装饰器自学指南:高性能组件复用实战指南

李游Leo

@Reusable

慈善组织购买堡垒机需要考虑哪些因素?买哪家好?

行云管家

信息安全 堡垒机 慈善组织

RabbitMQ集群部署(三)——镜像集群模式部署及常见问题

天翼云开发者社区

RabbitMQ

镜舟科技荣膺“北京市用户满意企业”认证,以用户为中心驱动高质量发展

镜舟科技

数据 技术创新 LakeHouse StarRocks 镜舟科技

《深入理解 eBPF 与可观测性》正式上架,龙蜥多位资深专家倾力打造

OpenAnolis小助手

Linux 操作系统 龙蜥社区 eBPF 技术

“清华”天才们联合创立,这家具身智能领域创企完成2亿元天使轮融资!

机器人头条

科技 大模型 人形机器人 具身智能

【新模型速递】PAI一键云上零门槛部署DeepSeek-V3-0324、Qwen2.5-VL-32B

阿里云大数据AI技术

人工智能 模型部署 Qwen PAI DeepSeek

2.5D封装为何成为AI芯片的“宠儿”?

E科讯

全民豪车时代,享界S9增程版靠什么“一鼎定乾坤”?

脑洞汽车

AI

Java 开发高手必备:AI 工具如何帮你快速生成 Spring Boot 配置?

飞算JavaAI开发助手

秒杀系统开发指南:用 AI 工具生成高并发代码的 5 个要点

飞算JavaAI开发助手

Flink + Doris 实时湖仓解决方案

Apache Flink

大数据 flink 实时计算 Doris

关于 K8s 的一些基础概念整理-补充

不在线第一只蜗牛

Docker Kubernetes

2025年企业组网新趋势:SASE与SD-WAN发展解析

Ogcloud

SD-WAN 组网 企业组网 企业网络 SD-WAN服务商

四款远控软件对比:哪一款功能最全?哪一款延迟最低?

科技热闻

企业信创项目建设实践

日志易

#信创 实践经验

快速使用Milvus MCP Server,0代码搭建智能搜索Agent

阿里云大数据AI技术

大数据 搜索 Milvus LLM MCP

SvelteKit 最新中文文档教程(12)—— 高级路由

冴羽

React Svelte SvelteKit

外贸人必看!三步用云手机轻松收集产品反馈

Ogcloud

云手机 海外云手机 舆情监控 舆情监测 海外舆情监控

昆仑万维发布 Mureka TTS API 和音乐推理大模型;通义发布小尺寸端到端多模态模型 Qwen2.5-Omni丨日报

声网

DeepSeek-V3 0324炸场升级:代码能力碾压GPT-4.5,测试开发效率革命开启!

测试人

让 DeepSeek 更懂你的业务,基于向量数据库 VectorDB 搭建问答应用

Baidu AICLOUD

数据库 向量数据库

淘宝商品详情 API 接口全解析:从接入到实战

tbapi

淘宝商品详情接口 淘宝API 淘宝商品数据采集

Spring Boot 集成实战:AI 工具如何自动生成完整微服务模块

飞算JavaAI开发助手

MySQL 优化利器 SHOW PROFILE 的实现原理

不在线第一只蜗牛

MySQL 数据库

远程控制软件套路深?4款对比测评,只有贝锐向日葵最靠谱!

科技热闻

怎么用DeepSeek生成甘特图?DS高阶使用技巧分享!

职场工具箱

甘特图 在线白板 AIGC AI 绘图 DeepSeek

HarmonyOS:ArkTS 显式动画 animateTo 自学指南

李游Leo

HarmonyOS

云学堂更名绚星智慧科技:发布AI新战略 领航企业智能生产力时代

人称T客

智能网络感知,打造极致流畅的鸿蒙版中国移动云盘图文体验

最新动态

企业级REST = 自定义、创造和标准化Media Type_Java_李光磊_InfoQ精选文章