使用 python 開發 Web Service

https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonws/


搭建開發環境

一個基本的 python web service 開發環境由以下這些工具組成:

Python2.4,Eclipse WTP,PyDev plug-in,Python ZSI包。

安裝 python2.4

Python2.4 可以在網站 http://www.python.org/download/realses/2.4,下載安裝包,安裝過程非常簡單,在此不再贅述。

安裝 Eclipse WTP

Eclipse WTP 可以在 http://download.eclipse.org/webtools/downloads/ 下載。

安裝 pyDev

在 http://pydev.sourceforge.net/download.html 下載 pyDev 插件,解壓後將 features 目錄和 plugin 目錄下的所有文件都拷貝到 Eclipse 的相同目錄下就可以了。本文中使用的是 release1.3.9。

安裝 python ZSI

從 ZSI 的網站上下載最新的安裝包,在寫這篇文章的時候,最新的 ZSI 版本爲 2.0。解壓縮後運行如下命令:

圖 1.
圖片示例

ZSI 還依賴與一些其它的 python 開源包:SOAPpy,pyXML。本文在後面列出了它們的下載地址。必須安裝這些 python 包 ZSI2.0 才能正常運行。

本文提供的 Web service 描述

在本文中我們將利用 python 提供幾個 web service,然後用 Java 客戶端調用它們。作爲演示,我們僅設計了三個簡單的 web service:

getTime():返回代表當前時間的字符串。沒有輸入參數。

sayHello(username):返回一個字符串,內容爲“Hello username”,其中 username 爲傳入參數。

showUser(username):根據輸入的 username 返回一個複雜數據類型 userInfo,描述用戶的年齡,性別,聯繫地址。

創建工程

首先創建一個工程。打開 Eclipse,選擇 File->New Project。當 pyDev 插件安裝好後,會有一個 pyDev project 的選項。選擇該選項並創建一個 pyDev 工程。

編寫 wsdl 文件

Wsdl 是一個 XML 文件,它主要用來描述所提供的網絡服務的細節。用戶通過 wsdl 文件就可以瞭解您所提供的網絡服務的調用接口的所有細節。

感謝 Eclipse WTP 提供的 WSDL Editor,它使得編寫 WSDL 文件變成了一件輕鬆的事情。WSDL Editor 提供了可視化的編輯界面。利用 WSDL Editor,用戶可以完全從服務本身來思考,無需深入瞭解 WSDL 的細節。

首先要在工程中增加一個 WSDL 文件,點擊菜單 File->New 選擇 Other,在彈出的對話框中選擇WSDL文件。如下圖所示:

圖 2.
圖片示例

單擊 next,給新文件命名爲 myServices.wsdl。選擇缺省選項並點擊 finish 按鈕就會生成一個缺省的空 wsdl 文件。此時,將看到如下所示的編輯界面:

圖 3.
圖片示例

缺省的 WSDL 包含了三個部分 Service,Binding 和 operation。對於一般的應用,只需修改 operation,即上圖中最右邊的方框。爲了提供前面所描述的三個網絡服務,我們需要在這個方框中增加三個新的 operation。

第一個網絡服務是 getTime。將缺省 wsdl 文件的 operation 方框中的第一個表項修改一下,就可以完成 getTime 的定義了。首先單擊標題 NewOperation,將名字高亮選中,如下圖所示:

圖 4.
圖片示例

將其重新命名爲 getTime。getTime 服務沒有入口參數。但有一個返回值,我們定義返回值爲一個表示時間的字符串。注意圖二中方框外右邊的兩個箭頭,雙擊它們就可以分別進入定義入口和出口參數的窗口。首先雙擊 Input 後面的箭頭 (上圖中用紅色圓圈標出),可以進入如下圖所示的參數定義窗口:

圖 5.
圖片示例

在新打開的 Inline Schema of myServices.wsdl 窗口中,我們可以設置 getTime 的入口參數。getTime 不需要入口參數,因此將缺省的入口參數 in 刪除:

圖 6.
圖片示例

關閉新打開的窗口,回到圖一所示的 wsdl 編輯界面。雙擊 getTime 的 output 後面的箭頭,定義返回值。將缺省的返回值名字 out 修改爲 timeStr,類型 string,不需要改變:

圖 7.
圖片示例

這樣 getTime 就設計好了。下面回到主窗口,添加 sayHello 的定義。在 Operation 方框上單擊鼠標右鍵,選擇 Add Operation:

圖 8.
圖片示例

同樣,雙擊方框右邊的箭頭,分別設置其入口參數和返回值。sayHello 的入口參數爲字符型,名字爲 username。返回值也是 string 類型。

添加新的 operation,命名爲 showUser。這個服務的入口參數也是用戶名,類型爲 string,但是它的返回值是一個複雜類型。用 WSDL Editor 可以方便地定義複雜類型。進入返回值設計窗口(雙擊 output 後的箭頭),在 element 上單擊鼠標右鍵,彈出菜單中選擇 Set Type->New。在彈出對話框中選擇 Complex Type,並將新類型命名爲 userInfo。

在eclipse的outline 窗口中選中 types->userInfo,定義 userInfo。

圖 9.
圖片示例

主窗口顯示出 userInfo 的設計界面,鼠標右鍵單擊 userInfo,選擇彈出菜單的 add element,增加三個 string 類型的元素 name, gender 和 address。如下圖所示:

圖 10.
圖片示例

現在可以存盤了,三個服務都已經設計好。下一步,我們將用 python ZSI 提供的腳本處理 WSDL 文件,並生成服務代碼框架。

編寫 web service 服務端代碼

ZSI 包提供了兩個腳本用來根據 wsdl 文件生成相應的 server 端和 client 端的 python 代碼框架。下面的命令生成 server 端代碼:

圖 11.
圖片示例

腳本 wsdl2py 的 -b 選項會生成一些輔助代碼,後面的描述中將會看到這些輔助代碼能簡化編程。運行以上兩條命令後,會生成三個文件:

myServices_services.py , myServices_services_server.py , myServices_services_types.py

這三個 python 文件就是服務端的代碼框架。爲了提供最終的 web 服務,我們還需要添加一個文件,用來實現每個 web 服務的具體代碼。將新文件命名爲 serviceImpl.py(完整的源代碼可以在文章最後下載)。僅實現 getTime 的 serviceImpl.py 如下:

from myServices_services_server import *
from time import time,ctime
from ZSI.ServiceContainer import AsServer
class mySoapServices(myServices):
    def soap_getTime(self,ps):
        try:
            rsp = myServices.soap_getTime(self, ps)
            request = self.request
            rsp.set_element_timeStr(ctime())
        except Exception, e:
            print str(e)
        return rsp

首先導入 myServices_services_server,它是由 wsdl2py 腳本生成的 Web 服務框架代碼。類 myServices 是 web 服務的基礎類,每一個 web 服務都對應其中的一個方法。getTime 對應 myServices 類中的 soap_getTime 方法。缺省的 soap_getTime 方法只是一個基本框架,但完成了 soap 解析並且能返回該服務的入口參數對象。

爲了實現 getTime,我們需要重載 soap_getTime 方法。定義新類 mySoapServices,繼承自 myServices。在 mySoapServices 類中重載父類的 soap_getTime() 方法。

getTime 的主要功能是返回一個表示當前時間的字符串。python 系統函數 ctime,就可以得到當前的系統時間。重載 soap_getTime() 函數中,首先調用父類的 soap_getTime() 方法,得到返回值對象rsp。

調用返回值對象 rsp 的 set_element_xxx() 方法,就可以對返回值對象中的元素進行賦值。這個方法是由 wsdl2py 的 -b 選項生成的。

set_element_timeStr(ctime()) 將返回值的 timeStr 元素賦值爲代表當前時間的字符串。

sayHello() 的代碼與此類似。但是與 getTime 不同,sayHello 服務還需要處理客戶端調用時傳入的入口參數。sayHello 方法的源代碼:

    def soap_sayHello(self,ps):
        try:
            rsp = myServices.soap_sayHello(self,ps)
            request = self.request
            usrName = request.get_element_userName()
            rsp.set_element_helloStr("Hello "+usrName)
        except Exception, e:
            print str(e)
        return rsp

request 代表入口參數對象。對於 sayHello 服務,入口參數只有一個元素 userName。調用 request 對象的 get_element_userName() 方法就可以得到該元素的值。

調用返回值對象 rsp 的 set_element_helloStr 將返回字符串賦值給 helloStr 元素。

showUser 服務與前面兩個服務的不同在於返回值是一個複雜對象,該複雜對象在 python 中可以用下面這個類來表示:

class userInfo:
    def __init__(self,nm,gen,addr):
        self.name = nm
        self.gender = gen
        self.address = addr

showUser 服務根據客戶端傳入的用戶名在數據庫中查找該用戶的詳細信息並填充 userInfo 對象,相應代碼如下:

    def soap_showUser(self,ps):
        try:
            rsp = myServices.soap_showUser(self,ps)
            request = self.request
            uName = request.get_element_userName()
            userDetail = rsp.new_user()
            nm=self.users[uName].name
            userDetail.set_element_name(nm)
            gender=self.users[uName].gender
            userDetail.set_element_gender(gender)
            addr=self.users[uName].address
            userDetail.set_element_address(addr)
            rsp.set_element_user(userDetail)
        except Exception, e:
            print str(e)
        return rsp

調用 request 對象的 get_element_userName 方法得到入口參數,並賦值給 uName。然後在數據庫中查找用戶uName的詳細信息,將詳細信息填充到 userInfo 類對象中,並返回。作爲演示,我們並沒有真的到數據庫中查詢,而是在內存中建立一個字典:

        u1 = userInfo("u1","M","Shanghai")
        u2 = userInfo("u2","F","Beijing")
        self.users={}
        self.users["u1"]=u1
        self.users["u2"]=u2

該字典中有兩個用戶:u1 和 u2。演示代碼在該字典中查詢用戶,將查選結果返回用戶。

調用 rsp 對象的 new_element_user() 方法創建一個新的返回對象,並用 userDetail 保存。

調用 userDetail.set_element_gender 將用戶性別信息設置到返回值對象的 gender 元素中。同樣方法設置用戶名和地址。

最後將新建的 userDetail 對象設置到返回值 rsp 中:rsp.set_elememnt_user(userDetail)。

發佈 web service

所有 web 服務代碼都已經寫好了,需要服務器代碼來發布它們。在複雜並且有較高要求的應用環境中,用戶可能需要用 apache 等強大的 web server 來發布 web services。限於篇幅,本文不打算介紹如何在 apache 上發佈 python web services。本文將使用 ZSI 自帶的 SOAP server。

正如下圖所示,使用 ZSI soap server 只需要很少的幾行代碼:

from ZSI.ServiceContainer import AsServer
from serviceImpl import mySoapServices
from ZSI import dispatch
if __name__ == "__main__":
    port = 8888
    AsServer(port,(mySoapServices('test'),))

這段代碼無需太多解釋。port 定義了 web service 發佈的端口號。ZSI 包的 AsServer 方法只有兩個參數:一個是端口;另外一個是包含了 web 服務實現代碼的類,在我們的實驗中就是 mySoapServices。字符串 test,表示 web 服務發佈時的虛擬路徑。當上述代碼成功運行之後,就會在 localhost 上開啓一個 web server,並在端口 8888 發佈 myServices 服務。一切都非常簡單,體現了用 python 語言的最吸引人的特點,快速而強大!

我們將在本機訪問 myServices,相應的 URL 爲 http://localhost/test?wsdl。

編寫 java 客戶端

現在我們使用 eclipse 集成環境來開發 web services 的客戶端程序,調用前面章節描述的那些 web services。

Eclipse 提供了一個簡單的方法來創建 web service 應用程序,即 Web Service Wizard。

首先創建一個 Web Project。

打開 File->New->Other…->Dynamic Web Project,創建一個新的工程。

圖 12.
圖片示例

然後就可以創建 java 客戶端。選擇 File -> New -> Other... -> Web Services -> Web Service Client

圖 13.
圖片示例

選擇 Next,在下一個窗口中的 Service Definition 中填寫相應的 webservice 的發佈地址 URL。在本文中爲: http://localhost:8888/test?wsdl

圖 14.
圖片示例

選擇 Finish 按鈕。將自動生成 java 代碼。包括以下幾個文件: MyService_PortType.java MyService_Service.java MyService_ServiceLocatior.java MyServiceProxy.java MyServiceSOAPStub.java

另外 showUser() 返回一個複雜對象,所以 eclipse 還創建了一個 java 類表示該複雜對象類,文件名爲 UserInfo.java

作爲測試,我們寫了一個 java 小程序,調用 getTime。

import org.example.www.myService.MyServiceProxy;
public class HelloClient {
  public static void main(String[] args){
  try {
	  System.out.println("Step1");
	  MyServiceProxy hello = new MyServiceProxy();
	  System.out.println("Step2");

	  java.lang.String str = hello.getTime();
	  System.out.println("step over");
	  System.out.println(str);
  } 
  catch (Exception ex)
  {
	  System.out.println(ex.getMessage());
  }
  }
}

sayHello 和 showUser 的調用代碼與上面的示例類似。

總結

用 Eclipse 的 WTP 開發 WSDL 文件,用 python 實現 Web 服務都比較簡單而快速。用這兩個強大的工具能夠迅速地開發 Web 服務應用,適用於原型產品的快速開發。 這樣就能抓住先機,比對手更快的推出新的Web應用,從而在市場上立於不敗之地。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章