认识 Atom 发布协议,第 2 部分: 应用 Atom 发布协议

与已部署应用程序(如 weblog 或 calendar)进行交互

本系列文章的 上一期 简要介绍了 Atom 发布协议(Atom Publishing Protocol,APP)。本文继续介绍该协议,举例说明如何用它与一些已经部署好的应用程序交互。

与以前一样,本文假设您了解使用 Atom 1.0 Syndication Format 的内容连锁,并对 HTTP 有基本的了解。本文的例子使用 Java 1.5 编写,并使用了 Apache Software Foundation 开发的 Apache Abdera 开放源码 Atom 实现的最新预览版本。

James Snell (jasnell@us.ibm.com), 软件工程师, EMC

Photo of James M SnellJames Snell 是 IBM WebAhead 开发实验室的成员,主要研究前沿性软件技术的原型开发和 IBM 自己使用的标准。他的研究和开发方向涉及到当前的各种技术趋势,包括 Atom、AJAX、REST、开放源码、个人发布系统、语义 Web 和语境应用程序。他积极参与 Apache Abdera 项目,目前正在筹划创建 Atom Syndication Format 和 Atom Publishing Protocol 高性能、功能完善的实现。


developerWorks 投稿作者

2007 年 1 月 26 日

准备开始

首先要保证安装了 Apache Abdera 的当前版本。源代码可以从 Apache Subversion 资料库 http://svn.apache.org/repos/asf/incubator/abdera/java/trunk 下载。要检索源代码,需要安装 subversion 客户机并使用下面的命令:

> svn co http://svn.apache.org/repos/asf/incubator/abdera/java/trunk

下载源代码的镜像之后就可以用 Ant version 1.6.5 或更高版本构建 Abdera 了。

> cd trunk
> ant -f build/build.xml dist

构建完成后,编译后的 jar 和 dependency 放在新建的 dist 目录中。要运行这些例子,需要在类路径中添加下列 jar:

表 1. 运行例子需要的 jar
Abdera (dist)Dependency (dist/lib)
  • abdera.client.0.2.0-incubating-SNAPSHOT.jar
  • abdera.core.0.2.0-incubating-SNAPSHOT.jar
  • abdera.parser.0.2.0-incubating-SNAPSHOT.jar
  • abdera.protocol.0.2.0-incubating-SNAPSHOT.jar
  • axiom-api-1.0.jar
  • axiom-impl-1.0.jar
  • commons-codec-1.3.jar
  • commons-httpclient-3.0.1.jar
  • commons-logging-1.0.4.jar
  • geronimo-activation_1.0.2_spec-1.1.jar
  • geronimo-javamail_1.3.1_spec-1.1.jar
  • log4j-1.2.12.jar
  • stax-1.1.2-dev.jar
  • stax-api-1.0.jar

Weblog

根据 IETF Atom Publishing Format and Protocol 工作组的章程,Atom 发布协议(Atom Publishing Protocol)的设计目标主要用于发布和管理 weblog 记录。毫不奇怪,随后很多 blogging 软件提供商如 Google、SixApart 和 Roller 已经开始初步支持该协议。

Google 的 Blogger Beta

2006 年 8 月初,Google 宣布将对其提供的网络日记服务进行期待已久的升级。服务新增加的一个特性就是支持使用 Atom 发布协议创建和编辑公告。

创建公告非常简单。首先需要知道新记录所要发送到的 Atom 集合的 URL。对于 Blogger 来说,Atom 提要用于连锁 blog 的内容组成 Atom 发布协议集合供提要阅读器和聚合器使用。因此要确定集合的 URI,只要查看 weblog 主页头部的替代链接即可。

清单 1. Blogger 主页的头部
<html>
  <head>
    <title>testing</title>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
    <meta content='true' name='MSSmartTagsPreventParsing'/>
    <meta content='blogger' name='generator'/>
    <link rel="alternate" type="application/atom+xml" 
      title="testing - Atom" 
      href="http://beta.blogger.com/feeds/7352231422284704069/posts/full" />
    <link rel="alternate" type="application/rss+xml" 
      title="testing - RSS" 
      href="http://beta.blogger.com/feeds/7352231422284704069/posts/full?alt=rss" />
    <link rel="service.post" type="application/atom+xml" 
      title="testing - Atom" 
      href="http://beta.blogger.com/feeds/7352231422284704069/posts/full" />
    ...

知道 Atom 提要的地址之后就可以向博客上发表记录了。清单 2 显示了要发送的贴子示例:

清单 2. Blogger 贴子
POST /feeds/7352231422284704069/posts/full HTTP/1.1
Host: beta.blogger.com
Content-Type: application/atom+xml
Content-Length: 349
Authorization: GoogleLogin auth={auth token}

<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <id>urn:uuid:1332534422684714363</id>
  <title>Posting to Blogger</title>
  <author><name>James</name></author>
  <updated>2006-09-02T12:12:12Z</updated>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <p>This is an example post to the new blogger beta</p>
    </div>
  </content>
</entry>

清单 2 中的请求有几点需要注意。首先也是最重要的是 Authorization 头部。Google 中支持 Atom Publishing Protocol 的服务要求使用一种私有的身份验证方法,所幸的是,实现起来并不麻烦。其次,Google 的实现要求请求中包含 Content-Length 头部。虽然看起来似乎无关紧要,但要求使用 Content-Length 带来了一个明显的副作用,即向服务器发送记录的客户机在发送之前必须计算请求的大小,因而降低了请求的效率。最后,发出的记录包含了 Blogger 将完全忽略的 ID、author 和 updated 元素。

第 1 步. 身份验证

向 Blogger 发送帖子的第一步是用 GoogleLogin 身份验证方法进行身份验证。为此需要创建 GoogleLogin 工具类。该工具需要输入访问 Google 帐户的用户 ID 和口令以及要访问的服务名称。该例中的服务名称为“blogger”。

清单 3. GoogleLogin 身份验证方法需要向身份验证服务器发送简单的 HTTP POST 请求
public final class GoogleLogin {
  ...
  public static String getAuth(
    Client client, 
    String service, 
    String id, 
    String pwd) {
    try {
      StringRequestEntity stringreq =
        new StringRequestEntity(getRequest(id,pwd,service));
      RequestOptions options = client.getDefaultRequestOptions();
      options.setContentType("application/x-www-form-urlencoded");
      ClientResponse response = client.post(URI, stringreq, options);
      String auth = read(response.getInputStream());
      response.release();
      return auth.split("\n")[2].replaceAll("Auth=", "auth=");
    } catch (Exception e) {}
    return null;
  }
  ...
}

用适当的证书调用 GoogleLogin.getAuth(...) 方法将得到一个身份验证标记,可用于验证发表或者编辑 Blogger 记录的请求。

第 2 步. 创建记录

向 Blogger 发帖子的第二步是用 Apache Abdera 的 Feed Object Model API 创建发送的 Atom 记录:

清单 4. 创建 Blogger 记录
Abdera abdera = new Abdera();
Factory factory = abdera.getFactory();
Entry entry = factory.newEntry();
entry.setId(FOMHelper.generateUuid());
entry.setUpdated(new java.util.Date());
entry.addAuthor("James");
entry.setTitle("Posting to Blogger");
entry.setContentAsXhtml(
  "<p>This is an example post to the new blogger beta</p>");

第 3 步. 发表记录

最后一步是把记录发送到 Blogger 服务器上:

清单 5. 向 Blogger 发送记录
Client client = new CommonsClient(abdera);
String auth = GoogleLogin.getAuth(
  client, "blogger", 
  "your@user.id", 
  "your.password");
    
RequestOptions options = client.getDefaultRequestOptions();
options.setAuthorization("GoogleLogin " + auth);
    
BaseRequestEntity bre = new BaseRequestEntity(entry, false);
    
Response response = client.post(
  "http://beta.blogger.com/feeds/7352231422284704069/posts/full", 
  bre, options);

if (response.getStatus() == 201) 
  System.out.println("Success!");
else 
  System.out.println("Failed!");

清单 5 中可以看到把上述各步骤都结合起来了。首先检索得到身份验证标记并作为请求选项设置。然后使用 Abdera 的 BaseRequestEntity 工具类作为记录的包装器,以保证正确地计算所需 Content-Length 头部。最后将记录发送到 Blogger 主页头部给出的 Atom 提要 URL。如果成功发布,服务器将返回 201 HTTP 状态码。

图 1. Blogger 中成功发帖
Blogger 中成功发帖

编辑和删除记录

通过 Atom 发布协议也可编辑和删除已有的记录:

清单 6. 更新已有的记录
String location = // get the URI of the entry to edit
    
Document<Entry> entry_doc = client.get(location).getDocument();
entry = (Entry) entry_doc.getRoot().clone();
entry.setTitle("This is the changed title");
    
response = client.put(
  location, new BaseRequestEntity(entry,false), options);
清单 7. 删除记录
String location = // get the URI of the entry to delete

response = client.delete(location, options);

Roller Weblogger

但 Google 并不是惟一准备支持 Atom 发布协议的博客提供商。流行的开放源码 Roller Weblogger 包是很多大型公司博客网络的后端,如 Sun 的 http://blogs.sun.com 网站和 IBM 的内部 Intranet 博客服务,目前正准备升级以便支持 APP。除了一两处值得一提的区别外,向 Roller 上发贴基本上与 Blogger 相同:

清单 8. Roller 发帖的例子
POST /app/myblog/entries HTTP/1.1
Host: example.org
Content-Type: application/atom+xml
Authorization: Basic {user:password}

<entry xmlns="http://www.w3.org/2005/Atom">
  <id>urn:uuid:4BA4E6A3334F88813011571535258641</id>
  <title type="text">Posting to Roller</title>
  <updated>2006-09-01T23:32:05.880Z</updated>
  <author><name>James</name></author>
  <content type="html">
    <p>This is an example post to Roller</p>
  </content>
</entry>

Roller Atom 接口和 Blogger 的主要区别是 Roller 提供了 APP Service Document 来发现可用的集合(而不是在 blog 主页中使用的替代链接),另外 Roller 使用的是标准 Basic 身份验证方案:

清单 9. 向 Roller 发贴
String start = "http://roller.example.org/app";
    
Abdera abdera = new Abdera();
Factory factory = abdera.getFactory();
Entry entry = factory.newEntry();
entry.setId(FOMHelper.generateUuid());
entry.setUpdated(new java.util.Date());
entry.addAuthor("James");
entry.setTitle("Posting to Roller");
entry.setContentAsHtml("<p>This is an example post to Roller</p>");
    
Client client = new CommonsClient(abdera);
client.addCredentials(
  start, null, null, 
  new UsernamePasswordCredentials(
    "username", "password"));
    
// Get the collection URI from the service document
Document<Service> service_doc = client.get(start).getDocument();
Service service = service_doc.getRoot();
Collection collection = 
  service.getWorkspaces().get(0)
    .getCollections().get(0);
String uri = collection.getHref().toString();
      
Response response = client.post(uri, entry);

if (response.getStatus() == 201) 
  System.out.println("Success!");
else
  System.out.println("Failed!");

发布媒体资源

Roller Atom Publishing 实现的另一个重要特性是支持上传任意的媒体资源。比方说,向服务器上传播客只需要将上例中的记录替换为代表要上传的 MP3 的请求实体:

清单 10. 向 Roller Weblog 发布音频资源
FileInputStream fis = new FileInputStream(
  "mypodcast.mp3");
InputStreamRequestEntity re = 
  new InputStreamRequestEntity(fis, "audio/mp3");
Client client = // init the client    
String uri = // get the collection uri
      
RequestOptions options = client.getDefaultRequestOptions();
options.setHeader("Title", "mypodcast.mp3");
    
Response response = client.post(uri, re, options);
    
if (response.getStatus() == 201)
  System.out.println("Success!");
else
  System.out.println("Failed!");

还可以用 Atom 发布协议操作来更新、删除和列出上传到服务器上的所有资源。

日历管理

Atom Publishing 工作组的一个重要目标就是要设计这样一种协议,该协议虽然主要针对向 Weblog、Wiki 及类似应用程序发布内容,但也能用于其他多种应用程序。目前对 Atom Publishing 的支持已经延伸到很多不属于博客的应用程序。一个例子就是 Google 的 Calendar 应用程序。

Google Calendar

Google Calendar 是一种托管的日历管理服务,让用户能够通过基于浏览器的界面或者 Atom 发布协议管理公共或个人的日程表:

清单 11. Google Calendar 的 Atom 贴子
POST /calendar/feeds/default/private/full HTTP/1.1
Host: www.google.com
Authorization: GoogleLogin auth={auth}
Content-Length: 834
Content-Type: application/atom+xml

<entry xmlns="http://www.w3.org/2005/Atom"
  xmlns:gd="http://schemas.google.com/g/2005" >
  <category scheme="http://schemas.google.com/g/2005#kind" 
    term="http://schemas.google.com/g/2005#event" />
  <id>urn:uuid:421D94DE83D298A91211571550147641</id>
  <title type="text">Tennis with Beth</title>
  <content type="text">Meet for a quick lesson</content>
  <updated>2006-09-01T23:56:54.772Z</updated>
  <author><name>James</name></author>
  <gd:transparency value="http://schemas.google.com/g/2005#event.opaque" />
  <gd:eventStatus value="http://schemas.google.com/g/2005#event.confirmed" />
  <gd:where valueString="Rolling Lawn Courts" />
  <gd:when startTime="2006-09-01T23:56:54.932Z" 
    endTime="2006-09-01T23:56:54.932Z" /></entry>

需要注意的是,Google Calendar 和新的 Blogger Beta 一样都基于相同的后端 Atom 发布协议基础设施。和前面的 Blogger 例子一样,Calendar 也使用私有的 GoogleLogin 身份验证方法,也要求 Content-Length 头部。此外 Atom 记录中还需要一些扩展元素:

清单 12. 建立 Calendar 记录
Abdera abdera = new Abdera();
Factory factory = abdera.getFactory();
Entry entry = factory.newEntry();
entry.setId(FOMHelper.generateUuid());
entry.setUpdated(new java.util.Date());
entry.addAuthor("James");
entry.setTitle("New Calendar Event");
entry.setContentAsXhtml("<p>A new calendar event</p>");
    
entry.addExtension(TRANSPARENCY).setAttributeValue(
  "value", "http://schemas.google.com/g/2005#event.opaque");
entry.addExtension(EVENTSTATUS).setAttributeValue(
  "value", "http://schemas.google.com/g/2005#event.confirmed");
entry.addExtension(WHERE).setAttributeValue(
  "valueString", "Rolling Lawn Courts");
Element el = entry.addExtension(WHEN);
el.setAttributeValue("startTime", AtomDate.valueOf(new Date()).toString());
el.setAttributeValue("endTime", AtomDate.valueOf(new Date()).toString());

建立记录后,剩下的就与向 Blogger 发贴基本一样了:

清单 13.发布到 Google Calendar
Client client = new CommonsClient(abdera);
String auth = GoogleLogin.getAuth(client, "cl", "username", "password"); 
RequestOptions options = client.getDefaultRequestOptions();
options.setAuthorization("GoogleLogin " + auth);
    
BaseRequestEntity bre = new BaseRequestEntity(entry, false);   
String uri = "http://www.google.com/calendar/feeds/default/private/full";    
Response response = client.post(uri, bre, options);
    
// calendar may return a 302 with a new URI to post to
if (response.getStatus() == 302) {
  uri = response.getLocation().toString();
  response = client.post(uri, bre, options);
}
    
if (response.getStatus() == 201) 
  System.out.println("Success!");
else
  System.out.println("Failed!");

另一种方法

Google 日历管理所采用的 Atom 方法面临的一个挑战是记录中需要使用厂商专用的扩展。IBM® Lotus® 的研究人员提出了一种替代方法,即利用 Atom 支持任何媒体类型这个特点使用现有的标准日历格式如 iCal 和 xCal:

清单 14. 向 Atom 集合发送 xCalendar 事件
POST /calendar HTTP/1.1
Host: example.org
Content-Type: application/calendar+xml
Content-Length: nnnn

<?xml version="1.0" encoding="UTF-8"?>
<vcalendar version="2.0"
  prodid="-//hacksw/handcal//NONSGML 1.0//EN">
 <vevent>
  <uid>19970901T130000Z-123401@host.com</uid>
  <dtstamp>20060901T130000Z</dtstamp>
  <dtstart>20060901T163000Z</dtstart>
  <dtend>20060901T164000Z</dtend>
  <summary>Tennis with Beth</summary>
  <class>PUBLIC</class>
 </vevent>
</vcalendar>

发送到集合后,服务器创建表代表该事件的 Atom Entry:

清单 15. Atom 服务器创建的日历项
HTTP/1.1 201 Created
Date: Fri, 7 Oct 2005 17:17:11 GMT
Content-Length: nnn
Content-Type: application/atom+xml; charset="utf-8"
Content-Location: http://example.org/calendar/1.atom
Location: http://example.org/calendar/1.atom

<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
 <title>Tennis with Beth</title>
 <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
 <updated>2006-09-01T18:30:02Z</updated>
 <author><name>James</name></author>
 <summary type="text">Tennis with Beth</summary>
 <link rel="alternate" type="text/calendar"
   href="http://example.org/calendar/1.ics" />
 <content type="application/calendar+xml"
   src=http://example.org/calendar/1.xml/>
 <link rel="edit" type="application/atom+xml"
   href="http://example.org/calendar/1.atom" />
 <link rel="edit-media" type="application/calendar+xml"
   href="http://example.org/calendar/1.xml" />
</entry>

请注意,这种方法不需要对 Atom 格式做任何扩展,实现可以继续使用已有的日历格式。

存储数据

还有人尝试使用 Atom 发布协议作为一种通用的前端 API 实现各种数据存储服务。其中包括文档和内容管理服务、软件资料库、数据库服务器、工作流和情景应用程序等等。

比如,Google 最近宣布新的 Google Base 服务 beta 版本将为各种应用程序服务存储任意数据:

清单 16. 发布到 Google Base
POST /base/feeds/items HTTP/1.1
Host: www.google.com
Authorization: GoogleLogin auth={auth}
X-Google-Key: key={key}
Content-Type: application/atom+xml
Content-Length: 632

<?xml version='1.0'?>
<entry xmlns="http://www.w3.org/2005/Atom"
  xmlns:g="http://base.google.com/ns/1.0">
  <id>urn:uuid:1215d395-cfb1-4faa-b1cd-12da123e3a7a</id>
  <category scheme="http://base.google.com/categories/itemtypes" 
    term="products"/>
  <title type='text'>Acme 2000 series laptop</title>
  <content type='xhtml'>
    <div xmlns='http://www.w3.org/1999/xhtml'>
      The fastest Acme Laptop yet...
    </div>
  </content>
  <link rel='alternate' type='text/html' 
    href='http://www.provider-host.com/123456789'/>
  <g:label>Computer</g:label>
  <g:label>Laptop</g:label>
  <g:label>fastest laptop</g:label>
  <g:item_type>products</g:item_type>
</entry>

由于灵活的设计以及对任意媒体资源的支持,Atom 发布协议有望成为一种通用 Web 数据管理 API。

结束语

本文介绍了一些实际的 Atom 发布实现,这些实现已经部署并被成千上万的用户使用。为了支持文中的例子,本文使用开放源码的 Apache Abdera 项目示范了与各种博客、日历和数据管理服务的交互。本系列文章的下一篇将详细介绍 Apache Abdera 项目,包括对 Feed Object Model API 与各种特性(如对 XPath、XSLT 转换、内容筛选和 XML 数字签名的支持)的介绍。


下载

描述名字大小
样例 Eclipse 项目及示例代码x-atompp2-appexamples.zip46KB

参考资料

学习

获得产品和技术

  • IBM 试用版软件:用这些试用版软件开发您的下一个项目,这些软件可直接从 developerWorks 下载。

讨论

条评论

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=XML
ArticleID=192355
ArticleTitle=认识 Atom 发布协议,第 2 部分: 应用 Atom 发布协议
publish-date=01262007