精通 Oracle+Python,第 7 部分:面向服务的 Python 架构

时间:2024-04-14 23:03:15

面向服务的架构 (SOA) 在当今的业务战略中具有至关重要的作用。混搭企业组件已成为所有任务关键的企业应用程序的标准要求,从而确保在企业架构的各层实现顺畅的服务编排。对此,Python 是一个不错的选择,我们可以通过这一工具使用一个免费提供的开源库快速地将这些服务连接在一起。

对于 Python 来说,处理 Web 服务描述语言 (WSDL)、简单对象访问协议 (SOAP) 或表示状态传输 (REST) 就如同使用语言固有的内置特性一样顺畅。最近几年出现了许多使用 Web 服务的模块,这让 Python 在 SOA 领域可与 Java EE 或 .NET 等大型编程体系分庭抗争。

在本部分中,我们将探讨如何通过一个名为 suds 的使用 SOAP 的新库来处理 SOAP 和 REST 通信,如何只使用标准库来部署一个符合 WSGI 的简单 Web 服务,并了解 Oracle Application Express 如何轻松地与 Python 通信。

使用 SOAP

过去,人们编写了许多 Python 模块来处理 SOAP 通信,这些模块包括 ZSI、SOAPpy 和 soaplib。不过在本教程中,我们将使用一个新的名为 suds 的轻型库。这个库旨在高效地使用 Web 服务,它提供了一种轻松使用 WSDL 端点的方法。

一切均从一个发出底层 Web 服务调用的 Client 对象的实例化开始。随后,suds 对响应进行处理,产生一个纯 XML 格式或经过解析的 Python 对象表示。返回对象的类型取决于服务的定义,并由 suds 自动转换。

在此我们将访问两种不同的 Web 服务:一个将返回纯 XML 响应,另一个将返回经过解析的表示。现在我们就来看看 Oracle Corporation(纳斯达克代号:ORCL)是如何做的。

清单 1. orcl_quote.py:通过 WSDL 端点访问 Oracle 的股票信息。
import suds
from xml.etree import ElementTree

quote_ws = suds.client.Client("http://www.webservicex.net/stockquote.asmx?WSDL")
orcl_quote_resp = quote_ws.service.GetQuote("ORCL")
orcl_quote_xml = ElementTree.fromstring(orcl_quote_resp)
orcl_last = orcl_quote_xml.findtext("Stock/Last")
orcl_datetime = orcl_quote_xml.findtext("Stock/Date") + "@" + orcl_quote_xml.findtext("Stock/Time")
print "ORCL stock value = %s at %s" % (orcl_last, orcl_datetime

如果设置了防火墙,则将第一行代码更改为如下内容,使用相应的代理名称和端口进行替换:

quote_ws = suds.client.Client("http://www.webservicex.net/stockquote.asmx?WSDL",
     proxy = dict(http = “http://myproxy:myproxyport”))

结果是,我们获得类似如下的输出:

[oracle@xe ~]$ python orcl_quote.py
ORCL stock value = 32.96 at 11/15/2011@4:00pm

如果我们仔细查看响应对象,就会发现 suds 在后台使用了 Python 的 SAX XML 库来提供对接收的 XML 对象的表示。

>>> type(orcl_quote_resp)
<class 'suds.sax.text.Text'>
>>> print orcl_quote_resp
<StockQuotes><Stock><Symbol>ORCL</Symbol><Last>32.96</Last><Date>11/15/2011</Date><Time>4:00pm</Time><Change>0.00</Change><Open>N/A</Open><High>N/A</High><Low>N/A</Low><Volume>0</Volume><MktCap>166.3B</MktCap><PreviousClose>32.96</PreviousClose><PercentageChange>0.00%</PercentageChange><AnnRange>24.72 - 36.50</AnnRange><Earns>1.758</Earns><P-E>18.75</P-E><Name>Oracle Corporatio</Name></Stock></StockQuotes>

此时就需要 xml.ElementTree 的帮助了,它让我们能轻松地以 orcl_quote_xml.findtext(“Stock/Last”) 调用的形式使用 findtext() 方法提取所需信息。

如果我们要获取 SOAP 响应本身,则情况稍有变化。suds 透明地将该响应转换为 Python 对象。

清单 2. redwood.py 检查 Oracle 总部当前的天气状况
import suds
weather_ws = suds.client.Client("http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL")
redwood = weather_ws.service.GetCityWeatherByZIP("94065")
print redwood

作为结果,此脚本以格式化的字符串表示形式返回一个 Python 对象,如下所示:

[oracle@xe ~]$ python redwood.py
(WeatherReturn){
   Success = True
   ResponseText = "City Found"
   State = "CA"
   City = "Redwood City"
   WeatherStationCity = "Hayward"
   WeatherID = 11
   Description = "Clear"
   Temperature = "N/A"
   RelativeHumidity = "N/A"
   Wind = "NE5"
   Pressure = "30.17S"
   Visibility = None
   WindChill = None
   Remarks = None
 }

仔细观察该对象,我们发现 redwood 变量下现在是一个 Python 对象实例,该对象实例有一些可访问的属性,如上面的字符串表示形式所示。这意味着我们现在可以引用 redwood.Pressure 或 redwood.Wind,并分别获得类似于 30.17S 或 NE5 的结果。

所有可用服务及其端口连同其绑定和方法的文本表示形式都通过 client.wsdl.service 属性公开:

>>> weather_ws.wsdl.services
[(Service){
   name = "Weather"
   qname = "(Weather, http://ws.cdyne.com/WeatherWS/)"
   ports[] =
       (Port){
       name = "WeatherSoap"
       qname = "(WeatherSoap, http://ws.cdyne.com/WeatherWS/)"
       _Port__service = (Service)...
       binding =
              (Binding){
              name = "WeatherSoap"
              qname = "(WeatherSoap, http://ws.cdyne.com/WeatherWS/)"
              operations =
              {
                      GetCityForecastByZIP =
                      (Operation){
                             name = "GetCityForecastByZIP"
                             soap =
...

suds 模块本身能够通过 suds.factory 命名空间处理复杂的类型,并且可以处理多个 WSDL 端口、SOAP 请求中的自定义头、HTTP 身份验证和 WS-security 的子集。

现在说说 REST

现在无处不在的另一种 Web 服务标准是表示状态传输 (REST)。REST 的开销远低于 WSDL 和 SOAP,其工作方式如同用户与互联网资源交互的方式,可访问统一资源定位器 (URL) 和统一资源标识符 (URI) 来获得所需的资源内容。

要使用 REST,您不需要外部库或专用框架;Python 语言库内部的模块足以满足您的所有 REST 功能需求。要快速查看 Oracle Corporation 内部发生的情况,我们只需查看其 Twitter 信源。有了 Python,其实现非常简单。请参见清单 3 了解最新状态检查的基本代码。

清单 3. twitter.py:查看给定用户的 Twitter 状态信源
import json
import urllib2
import sys

twitter_api = "https://api.twitter.com/1/statuses/user_timeline.json?screen_name=%s&count=5"
endpoint = urllib2.urlopen(twitter_api % sys.argv[1])
tweets = json.loads(endpoint.read())

for tweet in tweets:
  print "%s\n%s\n" % (tweet["created_at"], tweet["text"])

如果设置了防火墙,则在该脚本的开头插入如下所示的一行,使用相应的代理名称和端口进行替换:

urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler({'https':'myproxy:myproxyport'})))

运行 twitter.py 将输出五条最新微博及其发布日期:

[oracle@xe ~]$ python twitter.py Oracle
Tue Nov 15 20:30:09 +0000 2011
Have a need for speed? Find out why #Oracle #Exalogic Elastic #Cloud puts your enterprise on the fast track: http://t.co/AE5btu15

Tue Nov 15 16:00:09 +0000 2011
Webcast today at 1pm ET: Storage Strategies for Accelerating Database Test & Development: Register @ http://t.co/7WKGVXB4

Mon Nov 14 21:45:12 +0000 2011
Register now for 11/17 Webcast: Privileged User Access Control with #Oracle Database 11g. Details @ http://t.co/QCcwxJvk

Mon Nov 14 20:00:42 +0000 2011
Are your customers loyal? Would you like them to be? Join us for our upcoming Webcast 11/17. Register @ http://t.co/48ZNUSCS

Mon Nov 14 16:00:56 +0000 2011
Storage Strategies for Accelerating Database Test & Development:Increase the speed, efficiency of test environment http://t.co/AtxrD8aT

现在,正如使用 Web 服务所经历的那样,我们转到更复杂的部分,这里我们在 Oracle Database 11g 快捷版的 HR(人力资源)模式之上提供 REST 式 API。

清单 4 中的示例除了使用我们已经非常熟悉的 cx_Oracle 模块之外,还将使用 json、urlparse 和 wsgiref 模块。这里不使用任何可用的 Web 框架,我们的目的是使用广泛采用的 Web 服务器网关接口 (WSGI) 在 Python 的标准库之上实现简单的基于 JSON 的 REST 服务。此服务会响应请求,以应用程序/json 内容类型的 JSON 表示返回完整的员工信息。wsgiref.simple_server 模块中的 make_server 过程处理端口 8001 上的 HTTP 请求,直至出现 KeyboardInterrupt 异常(按 CTRL+C 可触发该异常)。

清单 4. restserver.py:员工信息 JSON Web 服务
import json
import cx_Oracle
import urlparse
from wsgiref.simple_server import make_server

def hr_web_service(environ, start_response):
  start_response('200 OK', [('Content-Type', 'application/json')])
  resp = ""
  url = urlparse.urlparse(environ['PATH_INFO'])
  parameters = urlparse.parse_qs(environ['QUERY_STRING'])
  if url.path=="/emp":
    empid = parameters['id']
    with cx_Oracle.connect('hr/hr') as db:
      cursor = db.cursor()
      cursor.execute("select * from employees e where e.employee_id=:empid", empid)
      rows = []
      for row in cursor:
        rowdict = {}
        for pos, col in enumerate(cursor.description):
          rowdict[col[0]] = str(row[pos])
        rows += [rowdict]
    return json.dumps(rows)
  return "Invalid request."

if __name__=="__main__":
  ws = make_server('', 8001, hr_web_service)
  ws.serve_forever()

运行 restserver.py 并将浏览器定向到 http://localhost:8001/emp?id=110,将会转换有关员工 110 的 Python 字典对象并输出 JSON 编码的表示:

[{"PHONE_NUMBER": "515.123.4567", "SALARY": "24000.0", "FIRST_NAME": "Steven", "LAST_NAME": "King", "JOB_ID": "AD_PRES", "HIRE_DATE": "2003-06-17 00:00:00", "COMMISSION_PCT": "None", "EMPLOYEE_ID": "100", "MANAGER_ID": "None", "EMAIL": "SKING", "DEPARTMENT_ID":"90"}]

REST 式 API 远比相应的 SOAP API 轻便,时至今日,REST 式 API 代表了在企业和互联网环境中使用和提供 Web 服务的主要趋势。REST 自身依赖 HTTP,从而将焦点从有效载荷类型转为仅端点。SOAP 和 WSDL 尽管功能极其强大,但会带来大量开销,并且使用起来更复杂。无论您选择哪一个,Python 都允许以“Python 式”方式轻松地工作 — 一致、高效、代码很少。

响应 Oracle Application Express 调用

Oracle Application Express 4.0 支持 REST 式 Web 服务和 SOAP Web 服务。了解此功能之后,我们可以利用另一种基于 Application Express-WSGI 桥的 Oracle-Python 模式。

我们来利用清单 4 中的 restserver.py 提供通过 Application Express 调用的服务。如果您计划在远程位置运行 Python 服务器,则需要启用对此计算机的远程 APEX 调用。清单 5 显示了 PL/SQL 代码,它需要以 SYSDBA 身份运行启用对任何计算机的调用(就如何处理外部网络调用,您可能需要咨询网络管理员)。

清单 5. 为用户 APEX_040000 授予所有主机的连接权限

DECLARE
  ACL_PATH  VARCHAR2(4000);
  ACL_ID    RAW(16);
BEGIN

SELECT ACL INTO ACL_PATH FROM DBA_NETWORK_ACLS
   WHERE HOST = '*' AND LOWER_PORT IS NULL AND UPPER_PORT IS NULL;

SELECT SYS_OP_R2O(extractValue(P.RES, '/Resource/XMLRef')) INTO ACL_ID
    FROM XDB.XDB$ACL A, PATH_VIEW P
   WHERE extractValue(P.RES, '/Resource/XMLRef') = REF(A) AND
         EQUALS_PATH(P.RES, ACL_PATH) = 1;

DBMS_XDBZ.ValidateACL(ACL_ID);
   IF DBMS_NETWORK_ACL_ADMIN.CHECK_PRIVILEGE(ACL_PATH, 'APEX_040000', 
     'connect') IS NULL THEN 
      DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(ACL_PATH, 'APEX_040000', TRUE, 'connect'); 
  END IF;

EXCEPTION
  WHEN NO_DATA_FOUND THEN
  DBMS_NETWORK_ACL_ADMIN.CREATE_ACL('python_apis.xml',
    'ACL - Mastering Oracle-Python, Part 7','APEX_040000', TRUE, 'connect');
  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL('python_apis.xml','*');
END;
/

COMMIT;

(有关详细信息,请参见此 Oracle 文档。)

现在,我们登录到 APEX 工作区,并从 Application Builder 中选择 Create > Application Type:Database > From Scratch > Name:PYTHONWS (schema:HR) > Add Page (Blank) >,然后选择两次 Create。现在我们有了一个名为 PYTHONWS 的应用程序,它包含一个名为“Page 1”的空白页面。

在我们实际使用 Web 服务之前,首先需要定义对此服务的引用。为此,我们需要将浏览器指向 Application Builder > Shared Components > Web Service References > Create > REST > Name:EMPLOYEE_WEBSRV, URL:http://localhost:81/emp(或者您将运行 Python 服务的服务器)> REST Input Parameters:Add Parameter “id” of type “string” > REST Output Parameters:Output format:Text, Name:json, Path:1 > Create。此时,我们应拥有了一个有效引用,我们可以轻松通过“Test REST Web Reference”页面来验证该引用,当 APEX 内的列表视图中显示出 Web 服务引用时,可以通过 Test 列图标访问此页面。

精通 Oracle+Python,第 7 部分:面向服务的 Python 架构 
图 1 通过 APEX REST Web 引用验证 restserver.py

有了正确定义的服务,我们现在可以将此功能添加到页面中。在 Page 1 上创建新区域,为此选择 Form > Form on Web Service > Web Service Reference:EMPLOYEE_WEBSRV > Page Number:1 > P1_ID > P1_JSON > Create。这些基本步骤足以为页面提供输入域 P1_ID,通过 Web 服务请求过程将来自 Web 服务的响应返回到 P1_JSON 域中。

总结

本文介绍了通过开源模块和 Python 的标准库本身使用 SOAP 和 REST Web 服务的重要事项。我们了解了如何轻松地接入 Twitter 流以及实现 Oracle Application Express 和 Python Web 服务之间的通信。Python 的动态特性和极快的开发速度再一次证明了它是用于实现 SOA 的可靠工具。 .