社区网络是目前网络上最热门的事情之一,所以毫不奇怪,许多 Web 应用程序试图加上社区网络内容来吸引更多的访问者。尽管 API 正在标准化 [1] ,但大多数现有的社区网站依然提供专有 API,或者对 OpenSocial 的不同版本提供支持, 而这正是许多开发人员所面临的挑战。结果是目前 OpenSocial 有多版本的的 API 和大量的开源代码支持他们 [2] 。在这篇文章中我将讨论 Apache Shindig [3] 如何实现 OpenSocial,如何减少其中的一些问题。
我首先讨论 OpenSocial 的标准和 Shingig 架构,然后说明它们如何为一个应用程序添加社区网络内容。最后,将以 OpenSocial Gypsii 社区网络 [4] 为例,讲解启用 Shindig 的方法。
OpenSocial APIs
OpenSocial 所定义的规范 [5] :
“OpenSocial 是一组用于构建在网络上运行的社交应用程序的 API。OpenSocial 的目标是通过提供一个可在很多不同的环境中使用的通用 API ,让更多的应用程序被更多的用户访问。开发人员可以使用标准的 javascript 和 html 创建应用程序,运行在那些已经应用 OpenSocial 的社交网站中。这些网站作为 OpenSocial 的容器,允许开发者访问他们的社区信息;反过来,又为他们的用户收到了大量的应用程序。
OpenSocail API 暴露了在一个 [社区网络] 容器中获取用户以及他们的好友信息的方式。”
社区应用程序围绕人、活动和关系展开。人是社区网络软件和 OpenSocial API 的基本组成部分。Person 对象提供对用户信息的访问接口,它包含了用户的基本信息或其他扩展的信息,如“最喜爱的电视节目”、“我所期望的工作”以及“最喜欢的地方”。OpenSocial API 提供了用于查看和更改个人资料的支持。另一个重要的用户信息是他们在社交图中的连接。为了能够共享信息和体验朋友动态变化,OpenSocial 支持用户的朋友概念,并提供检索用户朋友信息的 API。当应用程序想进行交互或显示“朋友的朋友”的数据,OpenSocial 允许增加一个你所做操作的可选过滤器来支持扩展查询,如用户可以通过朋友的距离来排序。容器可以有选择地支持“朋友的朋友”查询,“朋友的朋友的朋友”的查询,等等。 OpenSocial 还允许朋友分享他们最近一直在做的信息 - 这些被称为活动。OpenSocial 的公开活动流是一个用户所采取的行动的集合。活动模板允许应用程序开发人员在消息中为应用程序或用户数据块定义占位符。这种数据层和显示层的分离允许多个活动合并成活动包:活动综合包,让用户知道他们朋友消息,而不必辛苦地搜集大量消息。社区互动的另一个重要方面是能够读取,张贴和删除用户之间在网络中的消息。 OpenSocial 定义的消息类型,包括公共消息(如文件的评论)和私人消息(仅限于某些个人和团体)。消息归纳,其中包括一个唯一的 ID,名称和信息数量大小。
目前的规范定义了两种版本的 API──JavaScript [6] 和 REST [7] 。
OpenSocial 的 JavaScript 规范是提供一个小工具 Gadget [8] 设计来支持 OpenSocial 的功能。架构如图 1:
图 1 使用 JavaScript OpenSocial API 典型架构
在这种情况下,一个用户应用程序被作为一个 Gadgets 的集合,Gadget 自己用 OpenSoical API 可以获取社交信息。因此, JavaScript API 与 Gadget API 的紧密结合, 并非总是适合不是基于 Gadget 的实现。
相反的,OpenSocail REST API 不直接与 Gadgets 关联,允许任何客戶端应用程序来查看和发布用户的 OpenSocial 数据。我们将用这些 API 来实现 OpenSocial 应用。
Apache Shindig
Apache Shindig 是 Gadget API 和 OpenSocial API 的开源实现。下面将简单回顾一下什么是 Apache Shindig;Apache Shindig 是一个 OpenSocial 的容器,帮助您通过提供代码托管、代理请求和处理 REST 和 RPC 请求来快速启用 OpenSocial。
图 2 Apache Shindig [9]
对于 OpenSocial,Shindig 同时实现 JavaScript 和 REST 的 API。 Shindig OpenSocial 实现 [10] 总体结构如图 3:
图 3 Shindig 实现 Open Social 的整体结构图
从图 3 中我们可以看到 Shindig 实现包括两个主要部分:客户端的 JavaScript 容器和 Shindig 服务器。 服务器是一个 WAR 文件,它可以运行在任何 Servlet 容器中 - 我们使用 Tomcat。正如上面所定义的,因为我们正在直接用 REST OpenSocial API,所以在这里我们不会讨论客户端 JavaScript 容器。
服务器执行包含两个监听器:JSON RPC Servlet 和数据服务(REST)Servlet。 每个 Servlet 被它自己的处理程序支持。 因此有两个处理器:RPC 和 REST 的处理器。这两种处理器共享同一基础类库,包括日期转换器(这里没有显示)和一系列 OpenSocial 的处理器:
- 人处理器──种支持用户管理操作的处理器,如检索有关用户信息和他们的关系信息。
- 活动处理器──种支持检索和存储一个用户活动信息的处理器。
- 数据处理器──种支持存储和检索应用数据的处理器。应用数据是一项信息,如一个偏好设置,由社区网络对于某个给定的应用根据一个用户的行为来存储。
- 信息处理器──种用于社会网络中发送信息和检索信息的处理器。
Shindig 服务实现基于依赖注入框架 Guice [11] 。它使用的是绑定类(在 web.xml 文件中配置),所定义的服务被每一个处理器调用:即每个处理程序使用相应的服务。这种架构极其易于使用自定义实现覆盖 Shindig 发布服务一部分的实现,如实现与现有社区网络应用程序的集成。
Gypsii
Gypsii [12] 是一个位置感知的社区网络。为实现共同的社区网络, 除了传统的用户归属,它还允许其参与者存储和共享地点与位置信息,包括地址,地理坐标,可选的图片和说明。 GyPSii 中一个特殊的地点是当前用户的位置。它也支持 POI(兴趣点)和广告。
GyPSii 提供了一套 API:GyPSii OenExperience API(OEx) [13] 、XML-RPC API,还提供了访问 GyPSii 所有功能的方法。 GyPSii XML-RPC 的变种是由 XML - RPC 规范 [14] 创造的,但已适应 GyPSii 的需要。该协议定义使用 XML 双方沟通时一个客户端服务器的通信模式。 Gypsii 为开发人员提供了 XML-RPC 协议和应用了该协议的 JAVA 客户端实现的文档。
实现
整体实现架构
实现的整体架构如图 4。实现一个 Gypsii 的适配器,通过自定义 OpenSocial 容器替换 Shindig 提供的默认 OpenSocial 容器的方式,使得现有的 Shindig 支持 OpenSocial REST。 请注意,这个自定义提供者可以连接到任何其他社区网络供应商,如 Facebook,MySpace 或 Ning。
图 4 整体解决方案框架
这种方法的好处是有效地将我们的应用程序与特定的社区网络的实现 / 集成方法屏蔽开来。客户端 API 由 Shindig 提供和内部控制。因此,我们的应用程序不依赖于具体的技术和社区网络供应商公开的 API。这些细节被封装在一个特定的网络适配器中。
安全
与多个社区网络供应商集成的复杂问题之一是安全 / 证书支持,范围涉及从一个登录用户名称 / 密码到 OpenID [15] ,再到 OAuth [16] 。我们的实现是基于一个简单的身份转换,如图 5 所示。
图 5 身份转换
例如,用户 ID 123468@gypsii 意味着 NAVTEQ 用户以 ID123468 访问 Gypsii。
用户请求具有 NAVTEQ’s 用户 ID 的 Shindig:令牌。网络要求一种特定的社会网络适配器将令牌转换成证书。基于用户存储的转换包含转换所需的信息:用户名 / 密码,OpenID,特定的键或其他任何一个特定的转换要求。这允许我们使用 Navteq 公司的用户 ID 作为 OpenSocial REST API 的标准化参数。它还允许多个供应商之间的社区网络的请求,基于以下命名约定简单实现:
OpenSocialUID = NAVTEQUID@Network
例如用户 ID 123468 @ GyPSii 意思是 ID 123468 的 NAVTEQ 用户访问 Gypsii。这允许构建一个非常简单的网络适配器(图 4),社区网络适配器根据所提供的用户名选择线路。
替换默认的社区服务
在依赖注入支持下,替换默认的社区网络服务就相当简单,主要包括以下主要步骤:
- 实现自定义 OpenSocial 服务。自定义服务要么作为一个单独的类(清单 1)实现,要么为一组类共同实现 Shindig 所定义的 API。
<span color="#800040"><strong>public</strong></span> <span color="#800040"><strong>class</strong></span> CustomOpenSocialService <strong><span color="#800040">implements</span></strong> ActivityService, AppDataService, PersonService, MessageService { ………………………………….. }
清单 1 客户自定义的 OpenSocial 服务
- 创建绑定类。一旦自定义的 OpenSocial 服务到位(我们假定一个:清单 1), 一个绑定类必须实现(清单 2)并告诉 Shindig 调用它处理请求。
<span color="#800040"><strong>public class</strong></span> NAVTEQBind <strong><span color="#800040">extends</span></strong> SocialApiGuiceModule { ……………… bind(ActivityService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>); bind(AppDataService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>); bind(PersonService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>); bind(MessageService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>); ……………… }
清单 2 绑定自定义 OpenSocial 服务
- 用绑定类配置 Shindig。最后,这个绑定类必须知道 Shindig 的实现。通过一个配置文件 Web.xml 改变来处理(清单 3). 这个配置文件是通过
org.apache.shindig.common.servlet.GuiceServletContextListener
类调用 Shindig 应用程序和设置正确的绑定。
<span color="#008080"><context-param><br></br></context-param></span><span color="#008080"><context-param><br></br> <param-name></span>guice-modules<span color="#008040"></param-name></span> <span color="#008080"><param-value><br></br></span> <u>org</u>.<u>apache</u>.<u>shindig</u>.common.PropertiesModule:com.<u>navteq</u>.<u>opensocial</u>.bind.NAVTEQBind <span color="#008080"> </param-value><br></br> </context-param></span>
清单 3 Web.config 变化
从浏览器中访问 OpenSocial REST API
我们需要从浏览器中访问 OpenSocial REST APIs。这里的问题是现在浏览器都具有同源策略限制 [17] :
“同源策略是指阻止代码获得或者更改从另一个域名下获得的文件或者信息。也就是说我们的请求 URL 域必须和当前网站的域相同。这基本上意味着,Web 浏览器将不同域的内容隔离以阻止它们彼此对终端用户的操作。”
这里有几种克服限制的常用方法:
- 具有 GET/POST 数据的网页,通过服务器端发送请求,服务器充当一个到达第三方服务器代理。虽然使用广泛,但是这个方法却不够灵活,无法伸缩,并增加了处理请求的延迟(负载大时会更明显)。
- 在页面中使用框架元素创建新区域来访问任何第三方内容。没有返回数据的 POSTs 方式运行的很好,框架间由于有限的访问很少应用 GET 方式。但是试图下载文件时也会受到同源政策限制。
- 实现 GET 返回 JSONP(JSON with Padding):在一个函数调用中包装的 JSON 数据,数据在一个方法调用中返回。当脚本加载时执行。因为同源策略不会阻止动态插入的代码,不会测试脚本是否与 Web 页面来自相同的域。
由 Shindig 提供的 OpenSocial API 是利用 GET 从社区网络中获取信息和 POST 更新信息。为了支持 GET 方式,我们充分利用 JSONP,而为了支持 POST 方式,我们使用框架技术。
为了实现服务器上 JSONP 支持,我们使用了开源 JSONP 过滤器 [18] ,不要求对 Shindig 的方案变动支持。在 Shindig 的 Web.xml 中定义过滤器用法(清单 4):
<span color="#008080"><filter><br></br> <display-name><span color="#000000"><u>jsonp</u></span></display-name><br></br> <filter-name><span color="#000000"><u>jsonp</u></span></filter-name><br></br> <filter-class><span color="#000000"><u>org</u>.<u>jsonp</u>.JsonpFilter</span></filter-class><br></br> <init-param><br></br> <param-name><span color="#000000"><u>jsonp</u></span></param-name><br></br> <param-value><span color="#000000">jsonpCallback</span></param-value><br></br> </init-param><br></br> <init-param><br></br> <param-name><span color="#000000"><u>json</u>-<u>mime</u>-types</span></param-name><br></br> <param-value><span color="#000000">application/<u>json</u></span></param-value><br></br> </init-param><br></br> </filter><br></br> <filter-mapping><br></br> <filter-name><span color="#000000"><u>jsonp</u></span></filter-name><br></br> <url-pattern><span color="#000000">*</span></url-pattern><br></br> </filter-mapping></span>
清单 4 配置 JSONP 过滤器
我们还必须稍微修改 Shindig 代码使之能够支持 Post 方式。Shindig 发布版不允许内容类型为application/x-www-form-urlencoded
,因为它与 OAuth body signing 冲突。因为我们没有使用 OAuth 和application/x-www-form-urlencoded
的内容类型的 POST 浏览器,我们必须修改类org.apache.shindig.protocol.ContentTypes
(清单 5)和org.apache.shindig.protocol.DataServiceServlet
(清单 6)允许application/x-www-form-urlencoded
:
…………………. <strong><span color="#800040">public static void</span></strong> checkContentTypes(Set<String> allowedContentTypes, String contentType, <strong><span color="#800040">boolean</span></strong> disallowUnknownContentTypes) <strong><span color="#800040">throws</span></strong> InvalidContentTypeException { <strong><span color="#800040">if</span></strong> (StringUtils.isEmpty(contentType)) { <strong><span color="#800040">if</span></strong> (disallowUnknownContentTypes) { <span color="#800040"><strong>throw</strong> <strong>new</strong></span> InvalidContentTypeException( <span color="#0000ff">"No Content-Type specified. One of "<br></br></span> + StringUtils.join(allowedContentTypes, ", ") + " is required"); } <strong><span color="#800040">else</span></strong> { <span color="#008080">// No content type specified, we can fail in other ways later.<br></br></span> <strong><span color="#800040">return</span></strong>; } } contentType = ContentTypes.extractMimePart(contentType); <span color="#008080">//BL. comented out to support <br></br> // if (ContentTypes.FORBIDDEN_CONTENT_TYPES.contains(contentType)) {<br></br> // throw new InvalidContentTypeException(<br></br> // "Cannot use disallowed Content-Type " + contentType);<br></br> //</span> } ..........................................<string></string>
清单 5 修改 ContentTypes 类
…………… <strong><span color="#800040">public static final</span></strong> Set<String> <span color="#0000ff">ALLOWED_CONTENT_TYPES</span> = <strong><span color="#800040">new</span></strong> ImmutableSet.Builder<String>().addAll(ContentTypes.<span color="#0000ff">ALLOWED_JSON_CONTENT_TYPES)</span> .addAll(ContentTypes.<span color="#0000ff">ALLOWED_XML_CONTENT_TYPES</span>) .addAll(ContentTypes.<span color="#0000ff">FORBIDDEN_CONTENT_TYPES</span>) .addAll(ContentTypes.<span color="#0000ff">ALLOWED_ATOM_CONTENT_TYPES</span>).build(); ……………………<string><string></string></string>
清单 6 修改 DataServiceServlet 类
另一个问题是,浏览器 POST 方式提交名称 / 值。虽然,Shindig 支持 POST 名称 / 值,它也支持由 POST body 直接处理的数据。作为一个执行增加了一个额外的名称 / 值参数的主体内容。问题是,在浏览器提交的情况下,Body 是空的而其参数的内容被覆盖。 org.apache.shindig.protocol.DefaultHandlerRegistry
类(清单 7)更改修复了这个问题。
…………….. <strong>public</strong> Future<?> execute(Map<String, String[]> parameters, Reader body, {1} SecurityToken token, BeanConverter converter) { <strong>try</strong> { <span color="#008080">// bind the body contents if available<br></br></span> <strong>if</strong> (body != <strong>null</strong>) { String bString = IOUtils.toString(body); <strong>if</strong>(bString.length() > 0) parameters.put(<span color="#0000ff">operation</span>.bodyParam(), <strong>new</strong> String[]{bString}); } RequestItem item = <span color="#0000ff">methodCaller</span>.getRestRequestItem(parameters, token, converter, <span color="#0000ff">beanJsonConverter</span>); <span color="#0000ff">listener</span>.executing(item); <strong>return</strong> <span color="#0000ff">methodCaller</span>.call(<span color="#0000ff">handlerProvider</span>.get(), item); ………………………………………..
清单 7 禁止覆盖空消息体
最后,当 POST 方式不会返回任何数据,Shindig 实现将空数据转换成一个空的 JSON 对象,返回的类型为“application/json”。这将导致浏览器尝试主动与用户会话。org.apache.shindig.protocol.DataServiceServlet
(清单 7)解决了这一问题:
org.apache.shindig.protocol.DataServiceServlet (Listing 7) solves this problem: servletResponse.setContentType(converter.getContentType()); <strong><span color="#800040">if</span></strong> (responseItem.getErrorCode() >= 200 && responseItem.getErrorCode() < 400) { Object response = responseItem.getResponse(); <span color="#008080">// TODO: ugliness resulting from not using RestfulItem<br></br></span> <strong>if</strong> (!(response <strong><span color="#800040">instanceof</span></strong> DataCollection) && !(response <strong><span color="#800040">instanceof</span></strong> RestfulCollection)) { <span color="#008080">//BL - modified to not return fake responce</span> <strong><span color="#800040">if</span></strong>(response <strong><span color="#800040">instanceof</span></strong> Map){ <strong><span color="#800040">if</span></strong>(((Map)response).isEmpty()){ servletResponse.setContentType("<span color="#0000ff">text/plain</span>"); <strong><span color="#800040">return</span></strong>; } } response = ImmutableMap.of("entry", response); }
清单 8 禁用空响应
示例应用程序
在这篇文章中,我们描述 OpenSococial 的实现,用于构造 GeoSpatial OpenSocial mashup,允许用户和他的朋友把他们最喜欢的地方放在地图上(图 6)
图 6 地图上用户和他的朋友
它还允许将用户信息和他最爱的地方以及社区网络信息(图 7)发送给另一个朋友。
图 7 社区网络信息
这种简单的实现只是社区网络和地理空间数据之间许多潜在协同效应之一。基于以上所描述的结合两者的方法,能实现许多其他有趣的混搭网站。
结论
开源社区 API 的扩展使它很难开发出应用程序支持多种现有的社区网络。在本文中提出的一个解决方案,通过第三方提供的标准 API“隐藏”前端实现差异来克服这些问题。 Apache Shindig,提供 API 和“插件”架构,大大简化了整个实现。
查看英文原文: Bringing in Social Content to Custom Applications with Apache Shindig 。
[1] http://www.opensocial.org/
[2] http://www.thegoodnamesweregone.com/post/83-open-source-web-api-frameworks.aspx
[3] http://incubator.apache.org/shindig/
[5] http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/OpenSocial-Specification.html
[6] http://wiki.opensocial.org/index.php?title=JavaScript_API_Reference
[7] http://wiki.opensocial.org/index.php?title=OpenSocial_REST_Developer%27s_Guide
[8] http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/Gadgets-API-Specification.html
[9] Source : http://incubator.apache.org/shindig/
[10] Rajdeep Dua. Architectural Overview of Shindig , an OpenSocial Reference Implementation. http://chrisschalk.com/shindig_docs/rajdeep/shindig-overview/onjava-shindig-overview-tidy.html and Rajdeep Dua. Overview of REST Implementation in Shindig - Java Version. http://sites.google.com/site/opensocialarticles/Home/shindig-rest-java
[11] http://code.google.com/p/google-guice/
[12] http://www.gypsii.com/home.cgi
[13] http://developer.gypsii.com/docs.cgi?op=view&id=api.html
[17] Seda Özses, Salih Ergül Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups. http://www.ibm.com/developerworks/library/wa-aj-jsonp1/
[18] http://code.google.com/p/jsonp-java/
感谢曹云飞对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论