BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

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

| 作者 李光磊 关注 0 他的粉丝 发布于 2011年6月10日. 估计阅读时间: 17 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

关于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):
  3. 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例子包含的第三个要素。

  4. 定义领域相关的media-type。HTML的问题域是如何表达各种显示效果,RSS/ATOM则是如何发布信息。media-type以及link relations等都是可扩展的。我们要做的就是为我们的特定领域的应用定义扩展,如果现存的标准化的元素不够用的话。这里有一个问题,就是REST应用范围的问题。如果我们的应用是面向Internet的,面向无数已知的未知的客户端应用的,则意味着我们必须尽可能标准化我们扩展的media-type,或者至少让它广泛接受。而对于企业内部以REST架构的应用,我们同样面临标准化的问题,只不过范围可能小一点,至少是企业内部需要有共同接受的media-type。
  5. 前面我们看到了良好定义的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的潜力。

参考资料

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

好文章 by 哥 冰

最近在琢磨Rest架构,本文解决了两个疑惑, 第一如何选择Rest表述方式(有限状态机超媒体,或者使用atom),第二在Rest架构中如果实现类似amzons3级别的服务会限制服务的扩展性,增加耦合度。Rest架构的强大在于超媒体

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

1 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT