Python Web 服务开发者 第三部分

软件资源库,第 2 部分

用于内容更新的 Web 服务

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Python Web 服务开发者 第三部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Python Web 服务开发者 第三部分

敬请期待该系列的后续内容。

在上一篇文章中(请参阅 参考资料),我们开始介绍一个软件资源库的示例,阐明了如何通过使用可用的 Web 工具和开放的 Web 标准(例如,XML、XSLT 和 RDF)使数据和元数据管理的大量繁重工作比以前容易得多。因为构建软件资源库的核心是如此的简单,所以我们现在有时间来思考 Web 服务了。

在本文中,我们将考察用 HTTP POST 和 SOAP 把内容添加到软件资源库中的方式。最后,我们要看看结果 Web 服务的 WSDL 描述,以及 4Suite Server 能怎样有助于公开这个描述。您将不但需要熟悉 HTML 表单和超文本传输协议(Hypertext Transport Protocol,HTTP)协议,也需要熟悉上一篇文章中的工作(请参阅 参考资料)。

要公布的软件

首先我们要看看使用简单的 HTTP POST 进行的更新。这种方式可以作为通过操作人员或软件代理提供更新的一种方法,前者通过浏览器表单的方式。

先要确保软件资源库核心是用我们在上一篇文章中说明的步骤建立的。遗憾的是,这里存在一个曲解。上一篇文章是基于 4Suite Server 版本 0.10.2 的,自那以后我们又发布了版本 0.11.0,它有了明显的改进。关于涵盖了 0.11.0 发行版的上一篇文章的更新,请参阅 参考资料

这么说吧,供操作人员用来把条目添加到资源库的表单如 图 1所示。其对应的 HTML 如 清单 1所示。

图 1:将新软件添加到资源库的表单
将新的软件添加到资源库的表单
将新的软件添加到资源库的表单
清单 1:新软件表单的 HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head>
  <title>Add a software entry to the repository</title>
 </head>
 <body>
  <h1>Add a software entry to the repository</h1>
  <form method='POST' action='http://borgia:8080/softrepo/submit-new-software-entry?template-xslt=submit-entry.xslt'>
   <p>Fields with asterisks are required.</p>
   <table border='0'>
   <tr>
    <td>Title*:</td><td><input name='title'/></td>
   </tr>
   <tr>
    <td>Creator*:</td><td><input name='creator'/></td>
   </tr>
   <tr>
    <td>Home page*:</td><td><input name='home'/></td>
   </tr>
   <tr>
    <td>Current version*:</td><td><input name='version'/></td>
   </tr>
   <tr>
    <td>Description:</td><td><textarea name='description'></textarea></td>
   </tr>
   </table>
   <br>
   <input type='SUBMIT' value=' Submit '/>
  </form>
 </body>
</html>

4Suite Server 允许我们建立一个 XSL 转换来处理 POST 要求(例如,这个表单新生成的),创建一个要被添加到 XML 资源库的新文档。我们用来把条目添加到软件资源库的 XSL 转换如 清单 2所示。

用 URI 中的路径下的文档把我们的 HTML 源代码中的表单元素的 action 属性解释成源,把 template-xslt参数解释成 XSL 转换,用于生成新文档。

清单 2:把 HTTP POST 数据转换成一个新 XML 文档的 XSLT 转换
<?xml version="1.0"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:ftext="http://xmlns.4suite.org/ext"
  extension-element-prefixes="ftext"
  version="1.0"
>
  <xsl:param name='title'/>
  <xsl:param name='creator'/>
  <xsl:param name='home'/>
  <xsl:param name='version'/>
  <xsl:param name='description'/>
  <xsl:template match="/">
<rdf:RDF
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1"
  xmlns="http://namespaces.4suite.org/www/software-map"
>
  <Software rdf:ID="{$title}">
    <dc:Title><xsl:value-of select='$title'/></dc:Title>
    <dc:Creator><xsl:value-of select='$creator'/></dc:Creator>
    <dc:Description><xsl:value-of select='$description'/></dc:Description>
    <CurrentVersion><xsl:value-of select='$version'/></CurrentVersion>
    <Home rdf:resource="{$home}"/>
  </Software>
</rdf:RDF>
    <ftext:set-post-template-params
      new-doc-uri="incoming/{$title}-{$version}.xml"
      response-uri="thanks.xhtml"
      docdef="dublin core"
    />
  </xsl:template>
</xsl:stylesheet>

首先要注意的一件事就是参数组(例如, titlecreator)。这些参数是由服务器根据同名表单元素中的值自动设置的,它们变成了 HTTP POST 查询参数。接着,我们就可以使用 XSLT 为我们提供的所有工具,用这些参数来精心生成输出。您会看到,转换将以本系列第一部分所介绍的形式创建软件描述文档。为了简单起见,我们省略了一些可选域。

最后,请观察转换结尾处的 ftext:set-post-template-params 扩展元素。这是一个由服务器建立的特殊扩展,它允许您设置诸如要添加的文档的 URI、该文档的文档定义以及服务器生成要发送出去作为 HTTP POST 的响应的 HTML(例如,就是单击“提交”按钮提交条目后出现在浏览器中的 HTML)所用的 URI 这样的重要参数。

这里简要列出了所需的步骤:

  1. 创建一个容器并且给它全局的读访问权限。
  2. 为 HTTP POST 操作创建一个文档。
  3. 创建 XSLT 模板。
  4. 创建提交响应文档。
  5. 创建容器来存储输出。

要自己尝试一下,首先要创建一个合适的容器并赋予它全局的读访问权限:

清单 3a
$ 4ss create container /softrepo
$ 4ss set acl --world-read /softrepo

然后创建一个简单的哑文档(只是“”)作为 HTTP POST 目标。例如:

清单 3b
$ 4ss create document - BASE XML /softrepo/submit-new-software-entry
<null/>
$ 4ss set acl --world-read /softrepo/submit-new-software-entry

如果您指定“-”作为 XML 文档的源,命令将从标准输入中读取 XML 源。接下来,创建模板 XSLT 文档,如下所示:

清单 3c
$ 4ss create document submit-entry.xslt BASE XSLT /softrepo/submit-entry.xslt
$ 4ss set acl --world-read softrepo/submit-entry.xslt

现在创建提交响应文档,如下所示:

清单 3d
$ 4ss create document - BASE XML softrepo/thanks.xhtml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Thanks for your submission</title>
</head>
<body>
  <p>Thanks for your submission to the software repository.</p>
</body>
$ 4ss set acl --world-read softrepo/thanks.xhtml

最后,创建将要存放提交的软件记录的容器。为了使匿名用户也能添加文档,这个容器必须是全局可写的。

清单 3e
$ 4ss create container softrepo/incoming
$ 4ss set acl --world-write softrepo/incoming

一旦建立了该容器,人们就可以把数据输入到表单中,或者也可以通过软件代理用所选语言中的 HTTP 库输入数据。不过,另一种方法,即数据的软件到软件(software-to-software)访问,正日渐流行,这种方法不需人工干涉。

一条绳子上的软件

4Suite Server 为它的 API 提供了基本的 SOAP 支持,我们可以用它来作为我们 Web 服务的基础。

清单 4是一个简单的 Python 命令行程序,用直接的 SOAP API 添加软件条目文件。请注意直接 4Suite Server API 中使用的特殊编码风格 http://4suite.org/4ss/direct。这种编码基本上是由一个代表方法调用的元素,以及代表各个参数的各个属性组成的,有些情况下还包括一个主体(它包含一个充当参数的文档)。除此之外,清单 4还是相当直观的代码,它构造了一个代表来自命令行参数的 SOAP 请求文档的 XML,把这个 XML 放到一个 HTTP 请求中,发送出去,然后侦听响应。

清单 4:根据核心 API 发送 SOAP 请求的 Python 程序
import sys, string, httplib, base64, mimetools
SERVER ADDR = '127.0.0.1'
SERVER PORT = 8080
BODY TEMPLATE = """<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ft="http://xmlns.4suite.org/services"
  SOAP-ENV:encodingStyle="http://4suite.org/4ss/direct"
>
  <SOAP-ENV:Body>
    <ft:Create doc-def-name="%s" uri="%s">
%s
    </ft:Create>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""
#define the function
def AddSoftwareFromFile(entry, docdef, uri):
    body = BODY TEMPLATE%(docdef, uri, base64.encodestring(entry))
    blen = len(body)
    requestor = httplib.HTTP(SERVER ADDR, SERVER PORT)
    requestor.putrequest('POST', '/soap-handler')
    requestor.putheader('Host', SERVER ADDR)
    requestor.putheader('Content-Type', 'text/plain; charset="iso-8859-1"')
    requestor.putheader('Content-Length', str(blen))
    requestor.putheader('SOAPAction', "")
    requestor.endheaders()
    requestor.send(body)
    (status code, message, reply headers) = requestor.getreply()
    reply body = requestor.getfile().read()
    print status code
    print message
    print reply body
if   name   == "  main  ":
    fname = sys.argv[1]
    docdef = sys.argv[2]
    uri = sys.argv[3]
    entry = open(fname, 'r').read()
    AddSoftwareFromFile(entry, docdef, uri)

运行这个程序的样本会话如下所示:

[uogbuji@borgia code]$ python add software1.py Saturnalia-1.6.xml dublin core softrepo/incoming/Saturnalia-1.6.xml
200
OK
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE SOAP-ENV:Envelope>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'
    xmlns:ftsoap='http://xmlns.4suite.org/services'
    SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
 <SOAP-ENV:Body>
 <ftsoap:Document uri='/softrepo/incoming/Saturnalia-1.6.xml' imt='text/xml'>
PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz4KPHJkZjpSREYgeG1sbnM6ZGM9
J2h0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEnIHhtbG5zOnJkZj0naHR0cDovL3d3dy53
My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+PFNvZnR3YXJlIHJkZjpJRD0nU2F0dXJu
YWxpYScgeG1sbnM9J2h0dHA6Ly9uYW1lc3BhY2VzLjRzdWl0ZS5vcmcvd3d3L3NvZnR3YXJlLW1h
cCc+PGRjOlRpdGxlPlNhdHVybmFsaWE8L2RjOlRpdGxlPjxkYzpDcmVhdG9yPkNocm9ub3MgJmx0
O0Nocm9ub3NAT2x5bXBvcy5ncj48L2RjOkNyZWF0b3I+PGRjOkRlc2NyaXB0aW9uPk9uZSBoZWxs
dXZhIGZlc3RpdmFsPC9kYzpEZXNjcmlwdGlvbj48Q3VycmVudFZlcnNpb24+MS42PC9DdXJyZW50
VmVyc2lvbj48SG9tZSByZGY6cmVzb3VyY2U9J2h0dHA6Ly9zYXR1cm5hbGlhLm9seW1wb3MuZ3In
Lz48L1NvZnR3YXJlPjwvcmRmOlJERj4KCg==
</ftsoap:Document>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

您会看到,服务器的响应仅仅是表明文档被添加了到资源库中。

但是,如果我们只想给服务器传送一点儿有关我们的信息并且让服务器构造软件资源库条目,就像我们在上面的 HTTP POST 示例中做的一样,需要做些什么呢?4Suite Server 允许您编写自己专用的 SOAP 处理程序,如 清单 5中所示的示例。这个示例处理由描述软件的各种域组成的 SOAP 消息,组成服务器端上的描述文件,然后把该文件添加到资源库中。

清单 5:4Suite Server 中定制的 SOAP 处理程序
import base64
from xml import xpath
from xml.dom import implementation, ext
from FtServer.Protocols.Http import SoapHandler
SOFTREPO SOAP NS = 'http://spam.com/softrepo'
DOCDEF = 'dublin core'
URI STEM = 'softrepo/incoming'
TEMPLATE = """<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:dc="http://purl.org/dc/elements/1.1"
 xmlns="http://namespaces.4suite.org/www/software-map"
>
 <Software rdf:ID="%s">
 <dc:Title>%s</dc:Title>
 <dc:Creator>%s</dc:Creator>
 <Home rdf:resource="%s"/>
 <CurrentVersion>%s</CurrentVersion>
 <dc:Description>%s</dc:Description>
 </Software>
</rdf:RDF>
"""
def AddEntry(repo, reqbody, respbody):
 context = xpath.Context.Context(reqbody,
 processorNss={'s': SOFTREPO SOAP NS})
 title = xpath.Evaluate('string(s:Title)', context=context)
 creator = xpath.Evaluate('string(s:Creator)', context=context)
 home = xpath.Evaluate('string(s:Home)', context=context)
 version = xpath.Evaluate('string(s:Version)', context=context)
 desc = xpath.Evaluate('string(s:Description)', context=context)
 source = TEMPLATE%(title, title, creator, home, version, desc)
 uri = '%s/%s-%s.xml'%(URI STEM, title, version)
 res = repo.createDocument(DOCDEF, uri, source)
 imt = res.getImt()
 result = respbody.ownerDocument.createElementNS(SOFTREPO SOAP NS, 'ftsoap:Document')
 result.setAttributeNS('', 'uri', res.getUri())
 result.setAttributeNS('', 'imt', res.getImt())
 result.appendChild(respbody.ownerDocument.createTextNode(base64.encodestring(source)))
 respbody.appendChild(result)
 return 1
class SoftRepoSoapHandler(SoapHandler.SoapHandler):
 NS TO HANDLER MAPPING = {
 SOFTREPO SOAP NS: {'Add': AddEntry,
 },
 }
def Register(props):
 handler = SoftRepoSoapHandler()
 return [(handler.handle, 'POST')]

这个模块最重要的部分是 SoftRepoSoapHandler 类,它是 4Suite Server SoapHandler 的一个子类。定制的类所要做的只是定义一个映射 NS&#160TO&#160HANDLER&#160MAPPING,它为每个名称空间定义另一个从 SOAP 主体元素名到处理程序函数的映射。在我们的例子中,我们建立 AddEntry 函数来处理我们希望的请求。

为了使用这个处理程序模块,我们必须向 4Suite Server 注册它。通过把 Python 文件复制到 PYTHONPATH 上的某一处并把诸如下面所示的一节附加到配置文件上,就可以做到这一点。参见 清单6a

清单 6a
 <rdf:Description ID='SoftRepoSoapHandler'>
    <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
    <Priority>30</Priority>
    <Module>SoftRepoSoapHandler</Module>
  </rdf:Description>

您还需要向 PythonServer 或 ApacheServer 配置节添加一行代码,指向我们建立的新处理程序:参见 清单6b

清单 6b
   <Handler resource='#SoftRepoSoapHandler'/>

一旦建立了处理程序,您就可以进行更简单的 SOAP 调用给软件资源库添加一个条目。 清单 7是实现此目的的示例客户机代码。

清单 7:软件资源库的示例客户机
import sys, httplib
SERVER ADDR = '127.0.0.1'
SERVER PORT = 8080
BODY TEMPLATE = """<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:s="http://spam.com/softrepo"
  SOAP-ENV:encodingStyle="http://spam.com/softrepo/encoding"
>
  <SOAP-ENV:Body>
    <s:Add>
      <s:Title><![CDATA[%s</s:Title>
      <s:Creator><![CDATA[%s</s:Creator>
      <s:Home><![CDATA[%s</s:Home>
      <s:Version><![CDATA[%s</s:Version>
      <s:Description><![CDATA[%s</s:Description>
    </s:Add>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""
#define the function
def AddEntry(title, version):
    creator = raw input("Creator: ")
    home = raw input("Home: ")
    desc = raw input("Description: ")
    body = BODY TEMPLATE%(title, creator, home, version, desc)
    blen = len(body)
    requestor = httplib.HTTP(SERVER ADDR, SERVER PORT)
    requestor.putrequest('POST', '/softrepo/soap-handler')
    requestor.putheader('Host', SERVER ADDR)
    requestor.putheader('Content-Type', 'text/plain; charset="iso-8859-1"')
    requestor.putheader('Content-Length', str(blen))
    requestor.putheader('SOAPAction', "")
    requestor.endheaders()
    requestor.send(body)
    (status code, message, reply headers) = requestor.getreply()
    reply body = requestor.getfile().read()
    print status code
    print message
    print reply body
if   name   == "  main  ":
    title = sys.argv[1]
    version = sys.argv[2]
    AddEntry(title, version)

将清单 7 保存为 add&#160software2.py,并按如下所示对它进行试验:

[uogbuji@borgia code]$ python add software2.py Saturnalia 1.7
Creator: Chronos@Olympos.gr
Home: http://saturnalia.olympos.gr
Description: One helluva festival
200
OK
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE SOAP-ENV:Envelope>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'
    xmlns:ftsoap='http://spam.com/softrepo'
    SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
 <SOAP-ENV:Body>
 <ftsoap:Document uri='/softrepo/incoming/Saturnalia-1.7.xml' imt='text/xml'>
PHJkZjpSREYKICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5
bnRheC1ucyMiCiAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEiCiAg
eG1sbnM9Imh0dHA6Ly9uYW1lc3BhY2VzLjRzdWl0ZS5vcmcvd3d3L3NvZnR3YXJlLW1hcCIKPgog
IDxTb2Z0d2FyZSByZGY6SUQ9IlNhdHVybmFsaWEiPgogICAgPGRjOlRpdGxlPlNhdHVybmFsaWE8
L2RjOlRpdGxlPgogICAgPGRjOkNyZWF0b3I+Q2hyb25vc0BPbHltcG9zLmdyPC9kYzpDcmVhdG9y
PgogICAgPEhvbWUgcmRmOnJlc291cmNlPSJodHRwOi8vc2F0dXJuYWxpYS5vbHltcG9zLmdyIi8+
CiAgICA8Q3VycmVudFZlcnNpb24+MS43PC9DdXJyZW50VmVyc2lvbj4KICAgIDxkYzpEZXNjcmlw
dGlvbj5PbmUgaGVsbHV2YSBmZXN0aXZhbDwvZGM6RGVzY3JpcHRpb24+CiAgPC9Tb2Z0d2FyZT4K
PC9yZGY6UkRGPgo=
</ftsoap:Document>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

接下来......

SOAP 支持我们把软件资源库和正迅速形成的 Web Service 基础结构集成起来,而基本的 HTTP POST 支持允许人工操作和更传统的基于 HTTP 的自动操作。我们已经说明了,用基于 Python 的服务将这两种接口构建到同一个应用程序中是相当简单的。在下一篇文章中,我们将结束对把我们的软件资源库开发成 Web 服务的技术的讲解。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=20783
ArticleTitle=Python Web 服务开发者 第三部分: 软件资源库,第 2 部分
publish-date=04012001