写点什么

企业级 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:533352

评论

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

redis面试知识点和内存算法了解

收藏!阿里P9耗时28天,总结出来了“618、双十一”活动高并发系统设计手册

Java 程序员 架构 面试 高并发

经典永流传,华为云媒体 AI 让老电影焕发新生

华为云开发者联盟

AI 云原生 音视频 电影修复 华为云媒体

🏆「作者推荐」【JVM原理探索】字节码指令集调用执行流程分析(语法分析篇)

码界西柚

JVM Class字节码 6月日更 字节码指令

四份深入源码层面笔记,学完后让你彻底精通Spring Cloud!

Java架构追梦

Java 架构 面试 微服务 SpringCloud

模型化生存

俞凡

认知

什么是网络流量劫持?

网络安全学海

网络安全 安全 渗透测试 安全漏洞 网络攻防

星环科技边缘计算平台Sophon Edge通过EC Ready边缘服务权威评测!

星环科技

什么是NQI?质量基础设施“一站式”服务平台我来帮你搭建

源中瑞-龙先生

NQI 质量基础设施“一站式”

“动态规划”这词太吓人,其实可以叫“状态缓存”

华为云开发者联盟

Java 动态规划 超时 dp数组 状态缓存

THOR:MindSpore 自研高阶优化器源码分析和实践应用

华为云开发者联盟

网络 mindspore THOR 高阶优化器 THOR算法

Swarm云算力矿机分币系统搭建,chia矿机系统源码

zookeeper客户端zkclient和curator的api

赵镇

zookeeper

公司给的期权有没有价值?

石云升

期权 职场经验 6月日更

NeoFetch - Linux 使用命令行查看系统信息

HoneyMoose

亚马逊云科技宣布Amazon WAF 在北京区域和宁夏区域正式上线

亚马逊云科技 (Amazon Web Services)

[译] R8 优化: Switch 场景下的枚举

Antway

6月日更

带你认识Flink容错机制的两大方面:作业执行和守护进程

华为云开发者联盟

flink 守护进程 容错 作业执行 Flink 容错机制

华为自研PB级分布式时序数据库揭秘第一期:初识GaussDB(for Influx)

华为云数据库小助手

数据库 GaussDB(for Influx) 华为云数据库

一分钟懂5G

俞凡

5G

拥抱数字娱乐家庭新生态,亚马逊云科技赋能智象“蛟龙出海” | 精选案例

亚马逊云科技 (Amazon Web Services)

在线html链接提取工具

入门小站

工具

云小课 | 华为云KYON:网段零修改上云,简单又好用

华为云开发者联盟

KYON企业级云网络 私网NAT网关 弹性负载均衡ELB 虚拟私有云VPC L2CG VPVEP

JAVA原生线程池源码解析及使用建议( 程序员必看!)

Java 面试 BAT

经典Android开发教程!腾讯T3团队整理,附小技巧

欢喜学安卓

android 程序员 面试 移动开发

Linux之touch命令

入门小站

Linux

重磅!北京区域已经推出第三个可用区啦

亚马逊云科技 (Amazon Web Services)

肝到头秃!阿里爆款的顶配版Spring Security笔记

Java 程序员 架构 面试 计算机

华为云官网前端的技术演进与低代码实践

华为云开发者联盟

大前端 低代码 可视化 页面 华为云官网

TDH8.0使用必读2: 10种数据模型全支持 未来属于多模型大数据平台

星环科技

Ubuntu 安装 NTP 服务

HoneyMoose

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