利用 WebSphere sMash 为您的 REST 资源实现和测试服务器驱动的内容协商

内容协商是基于 REST 的设计的关键方面。下面介绍您可以用于内容协商的一些技术以及如何使用 IBM® WebSphere® sMash 实现它们。

Roland Barcia, 认证 IT 专家, EMC

Roland Barcia 是一位高级技术人员,并且是 IBM Software Services for WebSphere 的 Web 2.0 首席架构师。他是 IBM WebSphere:Deployment and Advanced ConfigurationPersistence within the Enterprise:A Guide to Persistence Technologies 的合著者。


developerWorks 2 星大师作者

2009 年 8 月 06 日

引言

内容协商的理念是单个资源可具有多个数据表示形式。在内容协商中,所返回的资源表示形式由客户端通过使用若干“Accept”标头来选择,如 HTTP 规范中所定义的那样。最常用于内容协商的标头包括:

  • Accept:Accept 请求标头字段可用来指定客户端可以接受的特定媒体类型响应。Accept 标头可用来指示该请求专门限于一小组期望类型,如请求内联映像时。示例包括:application/jsonapplication/atom+xmltext/html。(请参见此媒体类型列表。)
  • Accept-Charset:Accept-Charset 请求标头字段可用来指示应使用哪些字符集来表示客户端可以接受的响应。此字段使客户端能够了解更加完整或更专用的字符集,以通知服务器它能够以这些字符集表示文档。
  • Accept-Encoding:Accept-Encoding 请求标头字段类似于 Accept,但是仅限于内容编码。例如,您可以使用此字段指示压缩。示例值: compress;q=0.5gzip;q=1.0
  • Accept-Language:Accept-Language 请求标头字段类似于 Accept,但是仅限于作为响应该请求首选的自然语言集。示例包括:en 表示英文,es 表示西班牙语。

包含“Accept”一词的另一个标头是 Accept-Ranges;不过,它用于响应,并且不是请求标头。该服务器使用 Accept-Ranges 来告诉客户端哪些范围可以接受。

另外,下列标头通常用来增强内容协商:

  • User-Agent 标头也可用于内容协商。User-Agent 请求标头字段包含有关发出该请求的用户代理的信息。此信息用于统计目的,用来跟踪协议违规和自动执行的用户代理识别,因此,您可以自定义响应以避免特定的用户代理限制。
  • 还使用了质量值 (qvalue)。HTTP 内容协商使用短浮点数来表示各种可协商参数的相对重要性(或权重)。权重将被规范化为 0 到 1 之间的一个实数,其中 0 为最小值,1 为最大值。如果某个参数的质量值为 0,对于该客户端来说,带有此参数的内容为“不可接受”。

HTTP 规范定义了内容协商的若干技术。本文将重点讲述什么是服务器驱动的内容协商。该规范还将讨论另外两个类型的内容协商:

  • 利用代理驱动的协商,用户代理可以在从原始服务器中接收最初响应后为该响应选择最佳表示形式。选择基于初始响应标头字段或实体正文内所包含的响应的可用表示形式列表,每个表示形式都由其自己的 URI 标识。从表示形式中选择可以自动执行(如果用户代理能够这样做),也可以由用户手动从一个生成的(可能是超文本)菜单中选择。HTTP/1.1 定义了 300(多个选择)和 406(不可接受)状态代码,以便在服务器无法使用服务器驱动的协商提供各种响应时启用代理驱动的协商。
  • 透明协商是服务器驱动的协商与代理驱动的协商的组合。当向缓存提供一个可用响应表示形式列表形式(与在代理驱动的协商中一样)且该缓存完全理解变化的维度时,则该缓存将能够代表初始服务器对该资源发出的后续请求执行服务器驱动的协商。

本文重点介绍使用 Accept 标头执行媒体类型的服务器驱动的协商,但此处涉及的问题和技术实际上还可用于其他 Accept 标头。另外,媒体类型还是一种用户经常处理的应用程序到应用程序类型。

请求基于 REST 的资源的客户端使用 Accept 标头表达意图。还有另一种标头称为 Content-Type 标头,用来表达实际实体正文的媒体类型。例如,HTTP GET 请求可以在 Accept 标头中包含一个受该客户端支持的媒体类型列表。然后该服务器返回资源表示形式作为指定的类型(如果可以),并用提供给客户端的实际媒体类型填充响应中的 Content-Type 标头。


Accept 标头与 URI 参数比较

值得一提的是,有时使用 URI 对内容协商执行非规范处理。现在没有提供通过改变资源 URI 来协商资源表示形式的规范或标准。URI 之所以很方便,是因为您可以使用它在浏览器中进行测试。例如,/document?format=atom 将返回一个 Atom 源。新兴的几种技术已成了惯例,事实证明它们非常有用:

  • 使用 <dot Notation>,如:/document.html/document.json
  • 使用查询参数,如: /myResource?format=json

对于静态资源,<dot notation> 效果不错。例如,如果在您的 Web 服务器中存储了 document.html 和 document.pdf,则它们是两种不同的资源。

但是,如果资源是动态的,则使用 <dot notation> 作为请求同一种资源的不同格式的方法会导致冲突。这是因为,在静态情况下,<dot notation> 表示两种不同的资源,而在动态情况下,则是同一种资源。此外,媒体类型只是一种内容协商类型。如果您将其与前面讨论的其他类型合并,则最终 URL 的外观将非常有趣。例如,如果您需要某个资源的 isso-8859 JSON 英文版本,最终的 URI 将如下所示: /document.json.en.iso-8859-5

查询参数常常用于内容协商。出现此问题的原因是,开发人员经常使用浏览器快速测试和查看结果。添加查询参数通常会提供一个快速执行此操作的方法。查询参数不会造成您在处理另一种资源的假象。

但是,构建的动态基于 REST 的服务通常由客户端应用程序使用,这些客户端应用程序通过 Ajax 的浏览器或其他服务器应用程序使用这些服务。改变 URI 以实现内容协商便于共享指向特定资源表示形式的链接或者在浏览器中进行测试。不过,应用程序应先依靠标准 HTTP 标头,其次才是选定的 URI 惯例。查询参数通常用来为服务提供输入,如筛选标准、排序和其他业务级详细信息。将标头用于内容协商可将 IT 问题与业务问题分开。另外,请求通常能够通过防火墙、代理和其他服务器。这些 HTTP 代理通常理解标准 HTTP 标头,并且可能会提供缓存和其他非功能性要求。

作为折衷办法,您可以在开发时提供查询参数,然后在部署该应用程序时再禁用它。

通常,应用程序客户端应使用最简单的技术。许多 HTTP 标头在设计时都考虑到了网络,并帮助浏览器和中介代理自动执行信息的更改。业务应用程序一般不需要此级别的完善性。


利用 WebSphere sMash 实现内容协商

在了解内容协商的一些背景知识之后,下面我们看一个利用 IBM WebSphere sMash 实现内容协商的示例。可以通过几种方法执行此操作,稍后还要介绍两种技术。

此处提供的示例只是一个称为“document”的简单示例。它是一个包含一些字段的硬编码数据结构。当然,其重点是演示内容协商。此示例是一个使用 Accept 标头的演示。客户端将通过 Accept 标头以所需的格式传递。然后,基于 REST 的服务将基于最佳匹配呈现硬编码文档。如果未发现任何内容,该应用程序将返回适当的 HTTP 响应代码:406 不可接受。对于业务应用程序来说,这是一种很常见的方法。

下载 WebSphere sMash 并运行 App Builder

本文包括您可以下载并测试的 WebSphere sMash 应用程序。要查看和运行该应用程序,您需要下载:

WebSphere sMash Developer Edition V1.1 包括 App Builder(一个基于 Web 的集成工具,用来开发 WebSphere sMash 应用程序)和一个稳定的运行时,用于支持开发、测试应用程序,并对其进行有限部署。命令行接口 (CLI) 包含对开发和运行应用程序的基本支持。其他运行时库可根据需要从 ProjectZero.org 上的模块存储库中检索到。

要设置命令行接口,请按下列步骤操作:

  1. 下载 zero.zip 文件并将其解压缩到任一目录下。这将产生一个称为“zero”的子目录,其中包含运行 CLI 的命令。
  2. 将 zero 目录添加到用户的 PATH 环境变量中。
  3. 将 JDK 安装目录下的 bin 目录添加到用户的 PATH 环境变量中。

您可以使用 WebSphere sMash App Builder 来检查该应用程序。要启动 App Builder,请导航到 zero 目录(安装 CLI 的目录)并运行 appbuilder open。

图 1. 运行 App Builder
图 1. 运行 App Builder

加载该示例

该示例应用程序作为 .zip 文件包含在本文中。下载此文件,然后将其解压缩到任何一个空目录中。然后可以使用 App Builder 打开该应用程序。单击 Open existing application(图 2),导航到您解压缩下载文件的 DocumentsSMashApp 应用程序,并单击 Open(图 3)。该应用程序应显示在应用程序列表上(图 4)。

图 2. 打开示例应用程序
图 2. 打开示例应用程序
图 3. 查找示例应用程序
图 3. 查找示例应用程序
图 4. 应用程序列表
图 4. 应用程序列表

检查并使用 zero.test 运行 JUnit 测试

现在已加载该应用程序,您可以检查测试用例了。测试驱动的开发是一种 Agile 开发技术,其中开发人员在实现服务之前先编码测试用例。这个方法有许多优势。我最看重的是它可以让您了解问题,促使您首先考虑如何使用该服务。

WebSphere sMash V1.1 引入了一个称为 zero.test 的模块,它可以让您在 WebSphere sMash 应用程序内方便地运行单元测试,或者在您自己的测试工具中运行 WebSphere sMash。它还提供了一些额外的测试实用工具。(请参阅开发人员指南中的单元测试部分,以了解关于 zero.test 模块的详细信息。)

下面是一个供您检查的测试用例。首先,在应用程序列表中单击 DocumentsSMashApp 链接(图 4),然后单击 Explorer 链接(图 5)。

图 5. 应用程序选项
图 5. 应用程序选项

Explorer 视图(图 6)使您能够利用项目结构并检查构件。导航到 /app/scripts(如图 6 中所示)。打开名为 ContentNegotiationTest.groovy 的脚本。

图 6. Explorer 视图
图 6. Explorer 视图

下面将介绍测试用例的第一部分。这是一个使用 Groovy 编写的简单 JUnit 测试:

  • 清单 1 中显示的类定义了几个类变量。Abdera 和 Parser 成员是使用 WebSphere sMash 分发的 Apache Abdera API 的一部分。该代码将使用这些类来验证是否返回了正确的 Atom。
  • 接下来的变量是 URI 列表。这两个 URI 分别代表此处演示的两种不同技术。两个 URL 将使用同一 UnitTest。
  • 然后,有一个标题为“callResource”的特殊实用方法,该方法使用 WebSphere sMash API 将 HTTP GET 发送到传入的 URL。如果有 HTTP Accept 标头,还将填充该标头。
清单 1. 填充 HTTP Accept 标头
public class ContentNegotiationTest 
{ 
    Abdera abdera = new Abdera(); 
    Parser parser = abdera.getParser(); 

    def URIS = ["http://localhost:8080/resources/document",
                "http://localhost:8080/custom/document"];      

    private Connection.Response callResource(uri,acceptHeader)throws  Exception 
    { 
Connection conn = new    Connection(uri,Connection.Operation.GET); 
      if (acceptHeader) conn.addRequestHeader("Accept", acceptHeader); 
      return conn.getResponse(); 
      }

共有三种测试方法。前两种方法是测试两种所需的媒体类型 Atom 和 JSON。第三种方法是测试 406 错误情况。以下是 Atom 的测试用例:

  • 有一个应返回 Atom 的 Accept 标头的媒体类型值列表。由于该 Accept 标头可以包含客户端所需类型的逗号分隔的列表,因此测试各种类型非常重要。下面是一些应在何时返回 Atom 的示例:

  • 该测试用例(清单 2)首先遍历实现该服务的每个 URI。

    清单 2. 遍历服务的每个 URI 的测试用例
    @Test 
    void testAtomXml() 
     { 
         try { 
             def acceptHeaders = ["application/xml", 
                                  "application/atom+xml", 
                                  "application/xml,application/json", 
                                  "application/atom+xml,application/json", 
                                   null, //Default 
                                  "application/atom+xml;type=feed", 
                                  "text/html,application/atom+xml"];             
             for(uri in URIS) 
             { 
                     
                 for(acceptHeader in acceptHeaders) 
                 { 
                     System.out.println(); 
                     System.out.println(uri + " with Accept set to " + acceptHeader); 
                     Connection.Response resp = callResource(uri,acceptHeader); 
                     Document<Feed> xmlDoc = parser.parse(resp.getResponseBodyReader()); 
                     Feed feed = xmlDoc.getRoot(); 
                     assertNotNull(feed); 
                     assertEquals(resp.getResponseHeader
    				("Content-Type")[0],"application/atom+xml"); 
                     Writer xmlAtomWriter = 
    				abdera.getWriterFactory().getWriter("prettyxml"); 
                     StringWriter stringWriter = new StringWriter(); 
                     feed.writeTo(xmlAtomWriter, stringWriter); 
                     System.out.println(stringWriter.toString()); 
                     System.out.println(); 
                 } 
             } 
         } catch (Exception e) { 
             System.out.println(e.getMessage()); 
             System.out.println(); 
             fail(e.getMessage()); 
         } 
     }
  • 内部循环将遍历每个媒体类型,然后调用 callResource 实用方法。

类似地,JSON 测试用例(清单 3)列出一组应返回 JSON(即 application/json)的媒体类型。此处还提供了同时支持 JSON 和 Atom 的列表,但是现在先列出了 JSON。该测试用例类似于遍历实现该服务和受支持媒体类型的 URI。然后验证是否有适当类型的 JSON,并验证内容类型响应标头是否为 JSON。

清单 3. JSON 测试用例
@Test 
void testJSON() 
    { 
        try { 
            def acceptHeaders = ["application/json", 
                                   "application/json,application/xml", 
                                   "application/json,application/atom+xml", 
                                   "text/html,application/json,text/xhtml"]; 
            for(uri in URIS) 
            { 
                for(acceptHeader in acceptHeaders) 
                { 
                    System.out.println(); 
                    System.out.println(uri + " with Accept set to " + acceptHeader); 
                    Connection.Response resp = callResource(uri,acceptHeader); 
                    def json = Json.decode(resp.getResponseBodyInputStream()); 
                    assertNotNull(json); 
                    assertEquals(resp.getResponseHeader
				("Content-Type")[0],"application/json"); 
                    System.out.println(json); 
                    System.out.println(); 
                } 
            } 
        } catch (Exception e) { 
            System.out.println(e.getMessage()); 
           System.out.println(); 
            fail(e.getMessage()); 
        } 
        }

测试您的代码中的错误情况非常重要。最后一个测试用例(清单 4)创建一个无效的媒体类型列表。无效媒体类型应返回一个 HTTP 响应代码 406。而且,该测试用例类似于遍历 URI 和媒体类型以测试服务。

清单 4. 创建一个无效的媒体类型列表的测试用例
@Test 
void test406() 
    {    
        try { 
            def acceptHeaders = ["application/ftp", 
                                   "text/html", 
                                   "application/atom+xml;type=entry", 
                                   "junk", 
                                   "text/json"]; 
            for(uri in URIS) 
            { 
                for(acceptHeader in acceptHeaders) 
                { 
                    System.out.println(); 
                    System.out.println(uri + " with Accept set to " + acceptHeader); 
                    Connection.Response resp = callResource(uri,acceptHeader); 
                    assertEquals("406",resp.getResponseStatus()); 
                    System.out.println(resp.getResponseStatus()); 
                    System.out.println(); 
                } 
            } 
        } catch (Exception e) { 
           System.out.println(e.getMessage()); 
           System.out.println(); 
           fail(e.getMessage()); 
        } 
    } 
     }

在这些测试用例自检服务之前,您可以运行这些测试用例。要在 WebSphere sMash 应用程序内运行单元测试,需要将 zero.test 模块添加到该应用程序中。另外,由于您需要用于 Atom 的 Abdera API,因此还需要添加 zero.atom 模块。本文中附带的示例应用程序已经声明了这些依赖项。单击 Dependencies 选项卡(图 7),您将注意到已经添加了 core、atom 和 test 模块。

图 7. 应用程序依赖项
图 7. 应用程序依赖项

单击面板右上角的 Start 按钮启动该应用程序(图 8)。

图 8. 启动应用程序
图 8. 启动应用程序

WebSphere sMash App Builder 可让您直接在浏览器中运行 WebSphere sMash 命令行接口。单击 Console 选项卡(图 9)并键入 zero update。这可确保将所有的依赖项加载到您的本地存储库中。接下来,键入 zero test。该测试任务将在 /app/scripts 或 Java™ 类中查找以“Test”名称结尾的任何脚本,并作为 JUnit 测试来执行。这时您将获得一条成功消息,如图 10 所示。

图 9. 命令行接口
图 9. 命令行接口
图 10. 成功消息
图 10. 成功消息

向上滚动并检查系统输出语句,查看正在运行的服务和协商的响应。

图 11. 系统输出消息
图 11. 系统输出消息

您已经测试了这些服务,现在让我们看看两种不同的实现。

使用资源处理程序和脚本的内容协商

第一种技术是使用基于 REST 的资源处理程序。WebSphere sMash 的目标之一是提供约定,以便更容易地实现应用程序。一种约定就是使用资源处理程序。资源处理程序实现遵守集合/成员范例的资源。集合具有一些您可以添加、移除、更新和删除的成员项。您还可以获得该集合中的一个成员列表。例如,请参见表 1。

表 1. 获得集合中的成员列表
HTTP 方法URI描述
GET/document返回一个文档列表。
POST/document创建一个成员文档。
GET/document/myDoc检索名为 myDoc 的成员文档。
PUT/document/myDoc更新成员文档 myDoc。
DELETE/document/myDoc删除成员文档 myDoc。

WebSphere sMash 支持 <apphome>/app/resources 虚拟目录中的本机集合模型。该资源目录中的每个脚本都代表一个资源处理程序,用于实现集合和成员操作。资源处理程序可基于以下模式通过一个简单的 URL 约定访问:

/resources/<collection name>[/<member identifier>[/<path info>]]

例如,假设对具有集合名称为“people”和成员标识符为“1”的 /resources/people/1 发出一个请求。该集合名称标识资源处理程序。在此用例中,<apphome>/app/resources/people.groovy 和成员标识符的值将作为请求参数提供,且名称为 <collection name>Id。其中,zget("/request/params/peopleId") == 1。资源处理程序是一个 WebSphere sMash 事件处理程序,在设计上用来处理资源 CRUD(创建、检索、更新和删除)事件。它们是一些通常对数据或者此用例中的 HTTP 资源执行的操作。

HTTP 方法将映射到集合和成员事件,如表 2 所示。

表 2. 映射列表
资源GETPUTPOSTDELETE
集合listputCollectioncreatedeleteCollection
成员retrieveupdatepostMemberdelete

因此,要编写一个列出文档集合且带有 URL 为 /resources/document 的处理程序,需要在应用程序的 /app/resources 目录中创建一个名为 document.groovy 的脚本。由于这一简单示例仅需要对文档集合上的 HTTP GET 方法作出响应,因此该脚本只需要 onList() 方法。(有关在 WebSphere sMash 中编写基于 REST 的资源处理程序的详细信息,请参阅开发人员指南中的 REST 编程部分在您的 Web 应用程序中构建基于 REST 的服务教程。)

要查看您的资源处理程序,请返回到 Explorer 选项卡,展开 /app/resources 文件夹并打开 document.groovy(图 12)。清单 5 中显示了该代码。

图 12. 资源处理程序
图 12. 资源处理程序
清单 5. 实现代码
def onList() 
{ 
    def mediaType = request.headers.in.Accept[]; 
     
    /* if Accept Header is missing, HTTP Spec says 
     * you should assume client supports all types of media types. 
     */ 
    if(!mediaType) mediaType = config.defaultMediaType[]; 
    // Media types can be a comma separated list 
    def acceptTypeHeaders = mediaType.split(","); 
    for (acceptTypeHeader in acceptTypeHeaders) 
    { 
        //Media types can have profiles such as application/atom+xml;type=entry 
        def profile = acceptTypeHeader.split(";"); 
        System.out.println (profile) 
        def contentType = profile[0]; 
        if(contentType == "application/atom+xml" || contentType == "application/xml") 
        { 
            if(profile.size() > 1) 
            { 
                /*Checking of client is asking for atom entry only,  
                but this resource can only render feeds*/ 
                def nameValue = profile[1].split("="); 
                def key = nameValue[0]; 
                def value = nameValue[1]; 
                if(key == "type") if (value != "feed") continue; 
            } 
            request.status = HttpURLConnection.HTTP_OK; 
            request.view = 'atom' 
            request.atom.output = DocData.dummyPayload 
            render() 
            return; 
        } 
        else if (contentType == "application/json") 
        { 
            request.status = HttpURLConnection.HTTP_OK; 
            request.view = 'JSON' 
            request.json.output = DocData.dummyPayload 
            render() 
            return; 
        } 
    } 
     request.status = HttpURLConnection.HTTP_NOT_ACCEPTABLE; 
    request.view = 'error'; 
    render(); 
    return; 
}

WebSphere sMash 支持通过自动填充的缺省脚本变量实现对 HTTP 标头、查询参数、负载和其他 HTTP 构件的轻松访问。在清单 5 中:

  • 可从全局上下文中的请求区域访问 Accept 标头。该全局上下文可维护您的 WebSphere sMash 应用程序的所有状态。有几个区域可为您的数据执行不同的生命周期。例如,请求区域包含当前 HTTP 请求的所有数据。您访问了如下所示的 Accept 标头:request.headers.in.Accept[]。这是一种可访问全局上下文的方法。(有关全局上下文的详细信息,请参见参考资料。)
  • 如果 Accept 标头为空,这意味着该客户端可接受任何类型,因此设置了缺省值。缺省值可通过全局上下文中如下所示的配置区域访问到:config.defaultMediaType[]。该配置区域包含您的配置。它通过您的应用程序的 config/zero.config 文件填充。(请参阅开发人员指南中的配置部分。)
    清单 6. 标头类型
    #Resource Handler Default Media Type 
    /config/defaultMediaType="application/atom+xml"
  • 接下来,该代码将使用 Groovy 字符串操作解析 Accept 标头。而且该代码还将遍历逗号分隔的列表,找到第一个匹配项并提取概要。在找到匹配项之后,它将采用数据结构并使用缺省的 WebSphere sMash 呈现框架呈现它。例如,如果找到了 application/json,则清单 7 中的代码将运行:
    清单 7. 找到 application/json 要执行的代码
    request.status = HttpURLConnection.HTTP_OK;
    request.view = 'JSON' 
    request.json.output = DocData.dummyPayload 
    render()
  • 您还会注意到 Atom 的呈现部分。如果什么也没有发现,则该代码将把 NOT_ACCEPTABLE 变量设置为响应状态。

此方法的优势是您可以精确地获得您所想要的内容。不过,这会导致许多无法管理的代码。如果您自己编写脚本,则一定很希望创建一个可重用的库并外部化该代码。

使用自定义处理程序和正则表达式的内容协商

第二种方法是编写自定义处理程序。自定义处理程序通过在 zero.config 文件中配置规则来创建。zero.config 文件不是让单个处理程序处理所有的内容协商逻辑,它将与使用 Java 正则表达式的最佳处理程序匹配。在 app/scripts 目录下,您将注意到三个处理程序(图 13)。每个处理程序都为期望的媒体类型实现特定的呈现。

图 13. 自定义处理程序
图 13. 自定义处理程序

图 14 显示了一个使用 onGET 方法处理 GET 事件的处理程序。该处理程序将数据呈现为 Atom。

图 14. 自定义处理程序将数据呈现为 Atom
图 14. 自定义处理程序将数据呈现为 Atom

类似地,customJSONDocument.groovy 文件呈现 JSON(图 15)。

图 15. 自定义处理程序将数据呈现为 JSON
图 15. 自定义处理程序将数据呈现为 JSON

最后是用来发送 406 响应代码的呈现(图 16)。

图 16. 自定义处理程序呈现错误代码
图 16. 自定义处理程序呈现错误代码

为了调用正确的处理程序,该 zero.config 文件需要具有匹配规则。在配置目录下打开 zero.config 文件(图 17)。

图 17. 打开 zero.config
图 17. 打开 zero.config

zero.config 文件包括三种配置,每个处理程序一个;这些处理程序在处理程序下的配置区域下配置。对于处理程序,您需要定义事件(如 GET)、处理程序和条件。在每个用例中,您都可以精确地匹配路径和 HTTP GET 方法,然后使用 =~ 表示使用 Java 正则表达式的匹配项。

WebSphere sMash 将把前缀 (^) 和后缀 ($) 行边界添加到该表达式中。(请参阅本教程,了解有关边界匹配问题。)比较复杂的表达式是 Atom 表达式。该表达式寻找 application/xmlapplication/atom+xml(只要其后不跟 ;type=entry)。还将检查 Accept 标头在缺省情况下是缺失还是为空。还需要检查以了解 application/json 是否未先于它使用定向排除 (negative look ahead)。WebSphere sMash 仅为 HTTP 方法发出一个事件,因此,如果找到了 Atom 匹配项,则表示成功。如果 Atom 匹配项失败,则将检查 JSON。不需要检查前面的 Atom 项,这是因为,如果 Atom 匹配项失败,该字符串将不包括任何项。最终检查将确定是否填充了 Accept 标头。如果是,将为无效并返回值 406。

清单 8. 匹配代码
#ATOM
/config/handlers += [ 
{ 
    "events" : "GET", 
    "handler" : "customATOMDocument.groovy", 
    "conditions": "(/request/path == /custom/document)  &&
      (/request/method == GET) && (/request/headers/in/Accept =~ 
	(.*)(?<!(application/json,))application/(atom\\+)??xml(?!;type=entry)
	(.*)) ||  !(/request/headers/in/Accept)" 
}] 
 
#JSON
/config/handlers += [ 
{ 
    "events" : "GET", 
    "handler" : "customJSONDocument.groovy", 
    "conditions": "(/request/path == /custom/document)  && 
	(/request/method == GET) && (/request/headers/in/Accept =~ 
	(.*)application/json(.*))" 
}] 
 
#406
/config/handlers += [ 
{ 
    "events" : "GET", 
    "handler" : "notAcceptCustomDocument.groovy", 
    "conditions": "(/request/path == /custom/document)  && 
	(/request/method == GET) &&  (/request/headers/in/Accept) " 
}]

虽然使用自定义处理程序可简化内容协商的逻辑,但最终会带来许多配置节,并且可能需要自己处理诸如集合/成员之类的模式。此外,可能没有所希望的那样精确。例如,定向排除只能是绝对的。例如,application/json,application/xml 将被正确处理并返回 JSON,但application/json,application/html,application/xml 却不会;它将返回 Atom,因为 JSON 没有直接在它前面。如果您能够指定为标头填充的内容,这可能不是一个问题。


总结

内容协商是基于 REST 的体系结构中的一个重要部分。本文演示了您可以将内容协商构建到您的 WebSphere sMash 应用程序中的一些方法。


致谢

Roland 非常感谢 Karl Bishop 和 Brandon Smith 对本文进行了审阅。


下载

描述名字大小
代码示例DocumentsSMashApp.zip76 KB

参考资料

学习

获得产品和技术

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Web development
ArticleID=405941
ArticleTitle=利用 WebSphere sMash 为您的 REST 资源实现和测试服务器驱动的内容协商
publish-date=08062009