用 python flask 開發 SOAP 界面

需求說明

Chih-Cheng Liang
6 min readMar 8, 2016

財務部想取得商家庫存自動產生報表。然而財務部的 SAP 系統是走 SOAP ,現有的庫存 API 則是 RESTful JSON API。需要一個中間件聽取 SAP 的請求,造訪庫存 API 之後再翻譯成 SOAP 傳給 SAP 。

蛤? SOAP,如果你是位幸運的開發者,祝福你不會遇到這個協定。如果很不幸遇到了,希望這篇文章能幫上小小的忙。

我的 web 啟蒙教材是 Udacity的 Web Development,印象中他有提到SOAP,重看才發現他的措辭這麼強烈XD

由於這個項目大部分依賴企業內部現有資訊架構,我們遇到的挑戰也會與其相關。最大的兩個挑戰是:

  1. 財務部 SAP 系統使用 SOAP,如何串接這個協定?
  2. 公司不是使用像 AWS 之類的雲端主機服務,我們必須佈署在有其他系統運行的主機上。如何在不影響其他系統運作的情況下,讓我們開發的應用程式順利運作呢?

粗略的說,兩個星期半的時間,第一個星期我們研究如何做 SOAP 的接口,第二個星期到最後一天同伴研究事件日誌,我研究佈署。當然,還有很多時間花在溝通、開會、寫文件。

中間件用了 python flask 實作。用 Flask-Spyne 套件做出 SOAP 接口,用 unittest 、Flask-Testing 的 LiveServerTestCase 做測試,用 suds 和 SOAP UI 模擬使用 SOAP API 狀況。

關於佈署的細節,可見下篇文章「用 Docker + pyinstaller 建置執行檔」。

SOAP接口的細節

在百度 Python + SOAP 之後,應該能夠很快的找到 spyne 這個解方(或 flask-spyne),官方範例大致如下:

app = Flask(__name__)
spyne = Spyne(app)
class SomeSoapService(spyne.Service):
__service_url_path__ = ‘/soap/someservice’
__in_protocol__ = Soap11(validator=’lxml’)
__out_protocol__ = Soap11(polymorphic=False)
@spyne.srpc(Unicode, Integer, _returns=Iterable(Unicode))
def echo(str, cnt):
for i in range(cnt):
yield str

這讓 flask app 能夠和 SOAP 對接,只要造訪 /soap/someservice 就會出現 SOAP 要的 wsdl 檔。wsdl 是個 xml 檔案,他把所有這個服務能呼叫的函式像目錄一樣列出來,而且函式參數是數字、文字、還是其他型別都要指定清楚。

但根據官網範例,你大概只能寫出回傳 cnt 次文字的函數。大多時候的需求是要回傳巢狀結果,如圖:

解答是 polymorphic_array,要用到 ComplexModel 這個類,範例躲在 spyne 的原始碼裡不太好找,請點參考連結進去看

測試

flask 是很好測試的 framework,這次專案有做到幾個測試:

  • 把使用的套件(Spyne、suds、庫存 API)官網範例寫成測試,讓維護人員可以很快上手套件用法
  • 幾個功能作到測試驅動開發
  • 用 flask-testing 的 LiveServerTestCase,可以做出類似 phantomJS 的測試效果
  • flask 原生的測試 main.app.test_client()
  • 有因為寫測試抓到庫存 API 壞掉的時候 (API 開發者剛好在維護)

希望下次有機會能用用 pytest

LiveServerTestCase 結合 suds client 使用範例

測試以 class 方式組織。在開始測試的時候,會先執行 create_app 這個函式,接著依次測試每個 test_ 開頭的方法。可用 setUp 與 tearDown 定義每次執行 test_ 開頭方法的前後需要重複做的事情。

這裡利用了 setUp 方法來偷偷創建 suds client 的物件。

class SapMiddlewareTest(LiveServerTestCase):    def create_app(self):
# 這個函式必須定義
app = main.spyne.app
app.config[‘TESTING’] = True
app.config[‘LIVESERVER_PORT’] = 5566
return app
def setUp(self):
# 這個函式會在下面每個 test 開頭的方法之前重新執行一次
main.init_db()
url = urljoin(self.get_server_url(), “/xmlapi?wsdl”)
self.client = SudsClient(url=url, cache=None)
def test_server_is_up_and_running(self):
response = self.client.service.echo()
self.assertEqual(str(response), “Service up and running”)

參考資料

財務部中間件:

SOAP 細節:

測試:

下一篇:

--

--