级别: 初级 Benoit Marchal (bmarchal@pineapplesoft.com), 顾问, Pineapplesoft
2003 年 11 月 01 日 Benoit 继续开发轻量级的 XML 客户机。他在本文中说明了如何通过 XSLT 创建 SOAP 交易。与文本-XML 转换引擎 XT 的结合简化了 SOAP 消息的创建。最终,目标是从业务应用程序导出的数据生成 SOAP 消息。请您在本文的 讨论论坛 上与作者及其他读者交流您的想法。
“使用 XML”专栏启动了一个新的项目,目的是构造一个轻量级的 XML 客户机,使小型组织(最多 50 人左右)也能与 XML 服务器交换
XML 交易的工具,本文是该项目的第二部分。
您是说 e-business 吗?
我发现大多数 business-to-business (B2B)电子商务项目都需要这样一个的简单客户机。关于这种需求的详细讨论,请参阅上一部分,“
一种轻量级的 XML 客户机”。下面我介绍主要的依据。
在 B2B 项目中,不同的业务伙伴通过更有效地使用技术联合它们的力量以提高自身的竞争力。这种努力背后的商业理由千差万别。许多情况下,目标是实现管理操作(与订货、交货、计价和付款有关的所有文书工作)的自动化以降低成本。简化文书工作可能还有助于降低库存需要(通过推迟订货)或交货成本(通过供应商直接将货物发送给用户或消费者)。
在其他情况中,目标是组合两个或更多伙伴的服务和产品制造新的服务或产品。
按单加工是一个基本的例子,消费者订购的货物在工厂中定制。这种方式最常用于直销,因为如果控制了销售渠道和工厂就更容易实行。为了在转销渠道中有效地采用按单加工,需要有力的 e-business 解决方案。
尽管商业动机不同,技术实现的核心却总是业务伙伴间更有效的数据交换。在许多方面,e-commerce 项目类似于企业级应用集成(eAI),但是延伸到了企业的边界之外(顺便说一下,这一点极大地增加了复杂程度,因为要应付不同的企业文化和商业策略)。
e-commerce 项目经常涉及到少量大型企业和许多较小的企业。规模可能相差甚远,我记得有一个项目,其中的一个伙伴每年要处理上百万笔交易,而其他伙伴的交易量只有一千到一万笔。
既然规模不同,显然需求也不一样,不幸的是多数 e-commerce 工具都只是针对最大的组织的需要。这些工具被设计成自动处理大量的交易,而实际上最终对多数伙伴而言过于复杂和昂贵。一些项目没有意识到这种差异,也从来没有试图修正(只要试试在仅供家庭使用的机器上部署 J2EE 服务器就知道了)。另一方面,成功的项目则提供了某种轻量级的解决方案,充分考虑到了较小企业的要求。
在
上一篇文章中,我根据自己从事 e-commerce XML 项目的经验,讨论了这种轻量级解决方案的需求。本文中我将讨论通信协议,包括 Simple Object Access Protocol (简单对象访问协议,SOAP),并创建轻量级客户机的第一个版本。
与服务器对话
轻量级客户机最重要的一个功能是能够用存储在业务程序中的数据准备 XML 交易。我原来提出的解决方案是使用 XI,以及我以前在该专栏中开发的文本-XML转换工具(有关以前各部分的链接请参阅
参考资料)。
事实上,多数商业程序都能以逗号分隔值(CSV)文件保存数据。通过 XI 所用的正则表达式不难解析这些数据。另外一种流行的格式使用定长字段(也称为扁平文件)。后一种格式在 IBM iSeries(原来的 AS/400)的用户中特别受欢迎。用正则表达式解析这些文件同样没有什么大不了的。
接下来需要向 XML 服务器发送交易。可以使用不同的方法,如:
- E-mail
- 通过 HTML 表单上传
- 使用 SOAP
初看起来,e-mail 似乎是最简单的方法。它也非常适合于没有永久性 Internet 连接的组织。但有一个问题,即 e-mail 采用什么样的结构:在邮件体还是附件中发送 XML 交易?自动发送邮件还是插入用户的邮箱中?
还有一种办法是使用
<input type="file"> HTML 标签,要求用户手动上载 XML 交易。但实际上许多用户认为这种办法不好理解。
照我看来,SOAP 是最好的解决方案。SOAP 是一个简单的协议,使用 HTTP 或 e-mail 自动发送 XML 交易。SOAP 代替了使用 e-mail 和 HTML 表单的方法。
SOAP 基础及其 Java API
Java Community 至少提出了三种 API 处理 SOAP:
- Java API for XML-based RPC (JAX-RPC)
- Java API for XML Messaging (JAXM)
- SOAP with Attachments API for Java (SAAJ)
为何有三种 API 呢?因为 SOAP 是一个模块化的规范,可以满足多种不同的需要。根据目标的不同,开发人员可以使用这三种 API 中的一种。
图 1 说明了 SOAP 协议栈:
图 1. SOAP 协议栈
/p>
协议栈的底层是通信协议——主要是 HTTP 和 SMTP,尽管也可以使用其他的协议(如 HMS)。通信协议的上面是可选的文件附件和安全层,用 MIME 实现。这两层上面的一切都使用 XML 实现。SOAP 消息是单纯用于交换 XML 交易的协议。主要有信封和错误处理组成。
最后面的但同样重要的是 RPC 层,规定了如何在 XML 中调编码方法。这是 SOAP 最知名的特性。
不同的 Java API 针对协议栈中的不同层:
- SAAJ 针对附件层
- JAXM 针对消息层
- JAX-RPC 针对 RPC 层
因为将使用 XI 准备交易,所以不需要 Java 对 RPC 的支持。本项目中将使用 JAXM,可能还有 SAAJ。
观察 JAXM
如果观察 JAXM,您会发现它是对 HTTP 库一个很简陋的包装。JAXM 之所以如此简陋,是因为您主管——或者更明确地说,负责创建 XML 请求和解码响应(尽管 JAXM 确实会自动标记某些错误)。
这种设计与 JAX-RPC 恰好相反,后者向开发人员隐藏了所有的 XML。再说一次,如果不愿意看到任何 XML,应该选择 JAX-RPC。如果希望管理 XML 则应选择 JAXM,这恰恰我们的目标。
我已经在类
SOAPLink 中增加了 XI 对 JAXM 的支持。本文中不再详细介绍
SOAPLink ,因为多数代码都很熟悉。可以从在线资料库(请参阅
参考资料)下载源代码。清单1是 JAXM 集成的摘要。
清单 1. JAXM 在 XI 中的集成
public synchronized File apply(File source,boolean overwrite)
throws exception, SAXException,
TransformerException, SOAPException
{
File requestFile = requestLink.applyTo(source,overwrite);
SOAPMessage request = messageFactory.createMessage(),
response = null;
SOAPPart part = request.getSOAPPart();
InputStream is = new FileInputStream(requestFile);
try
{
part.setContent(new StreamSource(is));
SOAPConnection connection =
connectionFactory.createConnection();
try
{
response = connection.call(request,endpoint);
}
finally
{
connection.close();
}
}
finally
{
is.close();
}
if(response == null)
return null;
String fname = requestFile.getName();
int pos = fname.lastIndexOf('.');
String base = pos != -1 ? fname.substring(0,pos + 1)
: fname + '.';
File responseFile = new File(output,base + "soap");
int index = 1;
while(responseFile.exists() && !overwrite)
{
responseFile = new File(output,base + index + ".soap");
index++;
}
OutputStream os = new FileOutputStream(responseFile);
try
{
response.writeTo(os);
}
finally
{
os.close();
}
return responseLink.applyTo(responseFile,overwrite);
}
|
创建 SOAP 消息只需要实例化
SOAPMessage 对象。
SOAPMessage 允许访问消息中的所有元素: SOAP 信封、附件、MIME 头等等。
从
SOAPMessage 可以检索
SOAPPart 对象,它包括 SOAP 请求本身(不含附件)。初始化
SOAPPart 最简单的方式是调用
setContent() 方法并传递一个 XML 文档。
发送文档则使用
SOAPConnection 对象的
call() 方法。
call() 方法返回一个
SOAPMessage 对象,包含服务器的响应。
解码
清单 1中的结果,一种办法是用
writeTo() 方法将其保存到文件中然后再解析结果。注意这样做是不安全的,因为如果服务器用附件响应,解析就会失败。
这就突出了 JAXM 的一个问题:JAXM 与其他 XML API 的交互不容易。比如,Java 语言通常使用 DOM API 表示 XML 树。JAXM 定义了一组不同的对象(如
SOAPElement ),基本上是 DOM 的复制品。我没有发现 JAXM 从这里获得了什么好处,因为它使得用类似的 Java XML 库处理 JAXM 消息非常困难。
但目前我仍然坚持使用这种方案,因为它足以处理简单的请求。我计划在下篇文章中进一步研究这个问题,试图提出一种更好的解决方案。如果您有什么建议,我非常愿意听一听。(您也可以单击本文顶部或底部的
讨论来访问论坛。)我正在考虑的一种办法是 DOM-to-JAXM (或 SAX-to-JAXM) 转换库。
我使用
XSLTLink 创建交易并处理响应。一般读者应该熟悉
XSLTLink :它是 XI 的一个主要入口。
消息和样式表
现在我开始讨论 SOAP 交易本身和管理它的 XSLT 样式表。
概言之,关于 SOAP 消息最重要的一点就是:
- SOAP 消息是一个 XML 文档。
- 整个交易包括在一个
Envelope 元素中。
- 信封包含可选的
Header 和必需的
Body 。
- Body 包含交易的主要部分,可以是 RPC 调用或其他任何 XML 文档。
- Header 包含了对处理调用非常有用的附加信息,
如安全、验证、会话信息或者其他任何有用的信息。
- 如果不能识别,服务器可以忽略全部或部分 Header(比如,无状态的服务器可能会忽略 Header 中的会话信息)。
- 忽略 Header 并非总是合理的(比如可能不希望处理不带安全信息的请求)。那些不允许
忽略的头元素必须用
mustUnderstand="1" 属性标记。
- 可以用
encodingStyle 属性指定如何
创建头元素。
- 如果出现错误,Body 中将包含
fault
元素。
- 服务器用包含相同的信封、Header 和 Body 的另一个消息应答。
 |
W3C recommendation
SOAP 1.2 于2003年6月被批准为 W3C 推荐标准。
SOAP 1.1 (非正式版本)到 SOAP 1.2 的修改主要是对
原有规范的澄清。
SOAP 元素的命名空间 URI 也从
http://schemas.xmlsoap.org/soap/envelope/ 改为
http://www.w3.org/2003/05/soap-envelope 。
|
|
为了说明如何用 XSLT 创建 SOAP 请求,我使用了 Apache 的 AXIS 1.1 版工具包(最初是一个 IBM 项目,后来捐献给 Apache Foundation)。AXIS 主要是为 JAX-RPC 设计的,到目前为止只实现了 JAXM 的一个子集。
注意,AXIS 1.1 版实现的是 SOAP 1.1,而不是 W3C 最近批准的修订版。
AXIS 带有许多例子。为了测试这个轻量级客户机,我决定重新实现计算器服务。计算器服务是 AXIS 用户指南中的第二个例子,因此并不很难。该服务接受两个数字进行加减运算。样式表接受只有两行的文本文件,如清单
2 所示:
清单 2. 文本文件中的请求参数
第一行包括
+ 号或
- 号。第二行包括用逗号分开的两个数。
文本文件包含了请求参数,但还不是 SOAP 编码。SOAP 编码由清单 3 中的样式表完成,它只是像前面所述的那样创建信封、体和体请求。
清单 3. 准备 SOAP 交易
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://ananas.org/2002/xi/rules"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://ananas.org/2003/xi/params"
version="1.0">
<xi:rules version="1.0"
defaultPrefix="p"
targetNamespace="http://ananas.org/2003/xi/params">
<xi:ruleset name="data">
<xi:match name="plus" pattern="^\+$"/>
<xi:match name="minus" pattern="^\-$"/>
<xi:match name="line">
<xi:filler pattern="^"/>
<xi:group pattern=".*" name="op1"/>
<xi:filler pattern=","/>
<xi:group pattern=".*" name="op2"/>
<xi:filler pattern="$"/>
</xi:match>
<xi:error message="could not build request, please check file"/>
</xi:ruleset>
</xi:rules>
<xsl:template match="/">
<soapenv:Envelope>
<soapenv:Body>
<xsl:apply-templates/>
</soapenv:Body>
</soapenv:Envelope>
</xsl:template>
<xsl:template match="p:data">
<add soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<op1 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op1"/></op1>
<op2 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op2"/></op2>
</add>
</xsl:template>
<xsl:template match="p:data[p:minus]">
<subtract soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<op1 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op1"/></op1>
<op2 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op2"/></op2>
</subtract>
</xsl:template>
</xsl:stylesheet>
|
请求体的词汇表由服务器指定。在这个具体的例子中,它就是一个 SOAP RPC 请求,但我确实没在这上面花多少时间,我从一个样本请求开始,然后在合理的地方插入
<xsl:value-of> 指令。
响应由第二个样式表处理,如清单 4 所示。这个样式表要复杂一些,因为还要处理错误。注意清单中针对头元素的模板,它捕获
fault
元素(来自服务器的错误报告)。它还检查是否存在带有
mustUnderstand 属性的头元素。这是 SOAP 最基本的错误处理。
清单
4中的其他模板从文档中提取响应并格式化。
清单 4. 解码 SOAP 响应
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="soapenv:Envelope">
<html>
<head><title>Math result</title></head>
<body>
<xsl:apply-templates
select="soapenv:Header//node()[soapenv:mustUnderstand='1']"/>
<xsl:apply-templates select="soapenv:Body"/>
</body>
</html>
</xsl:template>
<xsl:template match="soapenv:Header//node()">
<p>Unknown header data: <xsl:copy-of select="."/></p>
</xsl:template>
<xsl:template match="soapenv:Body[soapenv:Fault]">
<p>Error, could not process the document.<br/>
Error message was: <xsl:value-of select="soapenv:Fault/faultcode"/><br/>
<xsl:value-of select="soapenv:Fault/faultstring"/></p>
</xsl:template>
<xsl:template match="soapenv:Body">
<p>The answer is: <xsl:value-of select="*/*"/></p>
</xsl:template>
</xsl:stylesheet>
|
这样做是否值得?
这个轻量级客户机的价值现在可能清楚了,因为我使用了一个非常简单(有人说过分单纯)的例子。
非常幸运的巧合,我正在写这篇文章的时候,与一位客户讨论了要实现一个轻量级的 XML 客户机。当然,他们那一个要远比这里讲的复杂。但是技术原理是相似的。更重要的是,他们发现这个轻量级客户机能够与许多商业伙伴联系,从而极大提高了他们的解决方案的价值。
参考资料
关于作者
对本文的评价
|