使用 Twisted 作为 SUDS 的传输方式

49 阅读5分钟

在使用 Twisted 与网络设备通信的项目中,我需要为新供应商(Citrix NetScaler)添加 SOAP API 的支持。不幸的是,Twisted 中对 SOAP 的支持仍然依赖于 SOAPpy,而 SOAPpy 已经严重过时。事实上,截至我提出这个问题时(我刚刚检查过),twisted.web.soap 在过去 21 个月里甚至都没有更新过。 我希望了解是否有任何人愿意分享他们利用 Twisted 的出色异步传输功能配合 SUDS 的经验。我感觉在 SUDS 的 Client.options.transport 中插入一个自定义的 Twisted 传输方式会很合适,但我现在很难理解。 我确实想出了一个利用 twisted.internet.threads.deferToThread() 来异步调用 SOAP 方法的方法,但这对我来说感觉像是一种变通方法。 以下是我所做事情的一个示例,以帮助您了解:

import netscaler
import os
import sys
from twisted.internet import reactor, defer, threads

# netscaler 是我使用 suds 编写的模块,用于与 NetScaler SOAP 进行交互
# 源码:http://bitbucket.org/jathanism/netscaler-api/src
host = 'netscaler.local'
username = password = 'nsroot'
wsdl_url = 'file://' + os.path.join(os.getcwd(), 'NSUserAdmin.wsdl')
api = netscaler.API(host, username=username, password=password, wsdl_url=wsdl_url)

results = []
errors = []

def handleResult(result):
    print '\tgot result: %s' % (result,)
    results.append(result)

def handleError(err):
    sys.stderr.write('\tgot failure: %s' % (err,))
    errors.append(err)

# 这将 api.login() 调用转换为 Twisted 线程。
# api.login() 应该返回 True,并且等同于:
# api.service.login(username=self.username, password=self.password)
deferred = threads.deferToThread(api.login)
deferred.addCallbacks(handleResult, handleError)

reactor.run()

按预期工作,并将 api.login() 调用的返回延迟到它完成,而不是阻塞。但正如我所说,它感觉不对。 提前感谢任何帮助、指导、反馈、批评、侮辱或彻底的解决方案。 更新:我找到的唯一解决方案是 twisted-suds,这是一个修改后的 Suds 分支,可与 Twisted 一起使用。

解决方案

Twisted 中传输的默认解释可能是一个 twisted.internet.interfaces.ITransport 的实现。在这个层面上,你基本上处理的是通过某种套接字(UDP、TCP 和 SSL是最常用的三种)发送和接收的原始字节。这并不是 SUDS/Twisted 集成库真正感兴趣的。相反,您需要的是一个 HTTP 客户端,SUDS 可以使用它来发出必要的请求,并以 SUDS 可以确定结果是什么的方式呈现所有响应数据。也就是说,SUDS 并不真正关心网络上的原始字节。它关心的是 HTTP 请求和响应。 如果您检查 twisted.web.soap.Proxy(Twisted Web SOAP API 的客户端部分)的实现,您会发现它实际上并没有做太多事情。它大约有 20 行代码,将 SOAPpy 粘合到 twisted.web.client.getPage。也就是说,它正在以我上面描述的方式将 SOAPpy 钩入 Twisted。 理想情况下,SUDS 会提供沿 SOAPpy.buildSOAP 和 SOAPpy.parseSOAPRPC(API 可能会更复杂一些,或者接受更多参数 - 我不是 SOAP 专家,所以我不知道 SOAPpy 的特定 API 是否缺少一些重要的东西 - 但基本思想应该是相同的)之类的内容。然后您可以根据 SUDS 编写类似 twisted.web.soap.Proxy 的东西。如果 twisted.web.client.getPage 没有提供对请求的足够控制权或对响应的足够信息,您还可以使用 twisted.web.client.Agent,它最近被引入并且对整个请求/响应过程提供了更多控制权。但是,这与当前基于 getPage 的代码真的没什么两样,只是更灵活/更具表现力的实现。 刚刚查看了 Client.options.transport 的 API 文档,听起来 SUDS 传输基本上就是一个 HTTP 客户端。这种集成的缺点在于,SUDS 希望发送请求,然后能够立即收到响应。由于 Twisted 在很大程度上依赖于回调来公开事件,因此基于 Twisted 的 HTTP 客户端 API 无法立即向 SUDS 返回响应。它只能返回 Deferred(或等效项)。 这就是如果关系倒转,事情会变得更好的原因。不要给 SUDS 一个 HTTP 客户端来玩,而是给 SUDS 和一个 HTTP 客户端交由第三部分代码,并让它协调交互。 通过创建一个基于 Twisted 的 SUDS 传输(又名 HTTP 客户端)来完成任务可能并非不可能。Twisted 主要使用 Deferred(又名回调)来公开事件这一事实并不意味着这是唯一可行的方式。通过使用绿线程之类的第三方库,可以提供基于协程的 API,其中异步操作的请求涉及从一个协程切换执行到另一个协程,并且通过切换回原始协程来传递事件。有一个名为 corotwine 的项目可以做到这一点。有可能用它来为 SUDS 提供它想要的 HTTP 客户端 API;然而,这是无法保证的。这取决于 SUDS 在突然插入上下文切换(以前没有上下文切换)时不会崩溃。这是 SUDS 的一个非常微妙且脆弱的特性,而且很容易被 SUDS 开发者在未来的版本中无意(甚至是无意)地更改,因此即使您现在可以让它工作,它也不是一个理想的解决方案(除非您可以从 SUDS 维护者那里得到承诺,以确保他们以这种配置测试其代码,以确保它继续工作)。 顺便说一句,Twisted Web 的 SOAP 支持仍然基于 SOAPpy 并且将近两年没有被修改的原因是,SOAPpy 的替代品从未明确出现。有很多竞争者(存在哪些 SOAP 客户端库,它们在哪里?它们的文档在哪里?)涵盖了其中几个。如果事情最终尘埃落定,尝试更新 Twisted 的内置 SOAP 支持可能是合理的。在此之前,我认为单独完成这些集成库更有意义,这样可以更轻松地更新它们,并且 Twisted 本身不会最终堆积一堆没人想要的不同 SOAP 集成(这比目前的情况更糟,目前只有一个没人想要的 SOAP 集成模块)。