Kafka 樣式的 soap 端點(diǎn)
Christopher Dix 所開(kāi)發(fā)的“Kafka — XSL SOAP 工具箱”(請(qǐng)參閱 參考資料)是一種用于構(gòu)造 SOAP 端點(diǎn)的 XSLT 框架。它只涵蓋了 SOAP 1.1,但 Kafka 端點(diǎn)演示了傳遞 UserLand SOAP 驗(yàn)證器(UserLand SOAP Validator)的能力,并且根據(jù) SOAP 1.2 對(duì)它進(jìn)行更新似乎并不太困難。 清單 1展示了一個(gè)樣本 Kafka 端點(diǎn):求兩數(shù)之和的 SOAP 服務(wù)器(一個(gè)典型而簡(jiǎn)單的 SOAP 樣本)。
清單 1. 求兩數(shù)之和的 Kafka SOAP 端點(diǎn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<?xml version = "1.0" encoding = "UTF-8" ?> <xsl:stylesheet version = "1.0" xmlns:method = "http://www.topxml.com/" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" > <! - - add.xsl : Kafka SOAP Endpoint Example, with modifications - - > <! - - Import soap.xsl to use the framework - - > <xsl: import href = "kafka/soap.xsl" / > <xsl:output method = "xml" encoding = "utf-8" omit - xml - declaration = "yes" / > <! - - Define the global variables for the framework - - > <xsl:variable name = "Method" >Add< / xsl:variable> <xsl:variable name = "MethodNS" >http: / / www.topxml.com / < / xsl:variable> <! - - Add : Add two numbers and return the sum - - > <! - - Function Add( A as Double, B as Double ) as Double - - > <xsl:template name = "ProcessPayload" > <xsl:param name = "Payload" / > <xsl: for - each select = "$Payload" > <! - - This is how to retrieve parameters from the input - - > <xsl:variable name = "A" select = "number(A|method:A)" / > <xsl:variable name = "B" select = "number(B|method:B)" / > <! - - The WriteParameter template takes the qualified name for a response parameter as well as its value and a QName specifying the tpe ( for the xsi: type attribute) - - > <xsl:call - template name = "WriteParameter" > <xsl:with - param name = "p" select = "'Result'" / > <xsl:with - param name = "v" select = "$A + $B" / > <xsl:with - param name = "t" select = "'xsd:double'" / > < / xsl:call - template> < / xsl: for - each> < / xsl:template> < / xsl:stylesheet> |
XSLT 端點(diǎn)導(dǎo)入 SOAP 框架(文件 kafka/soap.xsl),然后設(shè)置該框架將要使用的參數(shù),并設(shè)置它在處理構(gòu)成 SOAP 消息的整個(gè) XML 文檔的過(guò)程中將要分派的模板。全局變量 Method 和 MethodNS 聲明了組成消息的 XML 元素。在處理完 SOAP 信封之后,該框架調(diào)用 ProcessPayload 模板,該模板傳入了 XML 主體的有效負(fù)載。 xsl:for-each 是將上下文切換成想要的節(jié)點(diǎn)的標(biāo)準(zhǔn)技巧。參數(shù) A 和 B 是使用簡(jiǎn)單 XPaths 從這個(gè)元素讀取的,而框架被再次調(diào)用以幫助寫(xiě)出響應(yīng)參數(shù)。 WriteParameter 模板讓您指定元素名稱、數(shù)據(jù)類型和每個(gè)輸出參數(shù)的值。本示例中的響應(yīng)值是將兩個(gè)輸入?yún)?shù)相加所得的結(jié)果。
將這個(gè)端點(diǎn)部署為服務(wù)器相當(dāng)于設(shè)置一個(gè) HTTP 偵聽(tīng)器。Python 的 BaseHTTPServer 模塊向您提供了所需的機(jī)制,能夠輕而易舉地處理該協(xié)議的 HTTP 部分。請(qǐng)參閱 清單 2。
清單 2. 用于清單 1 中所實(shí)現(xiàn)的 Kafka SOAP 端點(diǎn)的 Python HTTP 框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
#HTTP Listener code for SOAP server import BaseHTTPServer #The processor class is the core of the XSLT API from Ft.Xml.Xslt import Processor #4XSLT uses an InputSource system for reading XML from Ft.Xml import InputSource SOAP_IMPL_FILE = "add.xsl" class KafkaSoapHandler(BaseHTTPServer.BaseHTTPRequestHandler): def init( cls ): from Ft.Lib import Uri #Set up a processor instance to use KafkaSoapHandler.processor = Processor.Processor() #Load it with add.xsl add_uri = Uri.OsPathToUri(SOAP_IMPL_FILE, attemptAbsolute = 1 ) transform = InputSource.DefaultFactory.fromUri(add_uri) KafkaSoapHandler.processor.appendStylesheet(transform) #Now the processor is prepped with a transform and can be used #over and over for the same transform return #Make init() a static method of the class init = classmethod (init) def do_POST( self ): clen = self .headers.getheader( 'content-length' ) if clen: clen = int (clen) else : print 'POST ERROR: missing content-length' return if self .path ! = '/add' : self .send_error( 404 ) input_body = self .rfile.read(clen) #input_body is the request SOAP envelope and contents response_body = self ._run_through_kafka(input_body) #response_body is the response SOAP envelope and contents self ._send_response( 200 , 'OK' , response_body) return def _run_through_kafka( self , body): #In 4Suite all InputSources have base URIs in case they refer to #other URIs in some way and resolution is required. #The SOAP messages will not have any such URI references, #So use a dummy base URI source = InputSource.DefaultFactory.fromString(body, "urn:dummy" ) response = self .processor.run(source) return response def _send_response( self , code, msg, body): #Prepare a normal response self .send_response( 200 , 'OK' ) #Send standard HTP headers self .send_header( 'Content-type' , 'text/html; charset=utf-8' ) self .send_header( "Connection" , "close" ) self .send_header( "Accept-Ranges" , "bytes" ) self .send_header( 'Content-length' , len (body) - 1 ) self .end_headers() #Send the response prepared by the SOAP end point self .wfile.write(body) return listen_on_port = 8888 #Set up to run on local machine server_address = ( '127.0.0.1' , listen_on_port) KafkaSoapHandler.init() httpd = BaseHTTPServer.HTTPServer(server_address, KafkaSoapHandler) print "Listening on port" , listen_on_port #Go into a the main event loop httpd.serve_forever() |
我們?cè)敿?xì)地注釋了該清單,因此它應(yīng)該是易于理解的。請(qǐng)注意,這段代碼非常簡(jiǎn)單,這是因?yàn)樗鼉H需處理該協(xié)議的 HTTP 部分,而將 XML 和 SOAP 部分的工作交由 Kafka 框架完成。該服務(wù)器專用于一個(gè)端點(diǎn),因此它只須對(duì) XSLT 轉(zhuǎn)換進(jìn)行一次解析和設(shè)置,然后它就可以簡(jiǎn)單地反復(fù)為每次新的請(qǐng)求運(yùn)行該轉(zhuǎn)換。這就是將處理器設(shè)置遷移到特殊的類方法中的原因,處理程序一注冊(cè)到服務(wù)器就立即調(diào)用該方法。 classmethod 內(nèi)置方法是 Python 2.2 中的新功能,實(shí)際上該版本是本例和后面的示例所必需的版本。它提供了隱式類對(duì)象 (cls) ,您可以將靜態(tài)數(shù)據(jù)(如已準(zhǔn)備好的處理器實(shí)例)附加到該對(duì)象上,然后通常可以通過(guò)普通方法上的 self 實(shí)例引用來(lái)使用該數(shù)據(jù)。
我們使用 SOAPpy 0.10.1 的最新發(fā)行版(請(qǐng)參閱 參考資料)測(cè)試了該端點(diǎn),該發(fā)行版具有許多很棒的新功能,稍后我們將在本專欄中進(jìn)行討論。 清單 3是使用該端點(diǎn)的 SOAPpy 客戶機(jī)。打開(kāi)一個(gè)命令 shell 并為服務(wù)器運(yùn)行 python listing2.py。然后打開(kāi)另一個(gè) shell 并運(yùn)行 python listing3.py,該命令將報(bào)告正確的響應(yīng),形如 Add result: 7.0。
清單 3: 用于求兩數(shù)之和的 SOAPpy 客戶機(jī)
1
2
3
4
5
6
|
import SOAPpy ENDPOINT = "http://localhost:8888/add" ADD_NS = "http://www.topxml.com/" remote = SOAPpy.SOAPProxy(ENDPOINT, namespace = ADD_NS) print "Add result:" , remote.Add(A = 3 , B = 4 ) |
使用描述
正如我們先前所說(shuō)的,不僅 XML 中的有效負(fù)載是有用的 Web 服務(wù)特性,描述也是有用的特性。 清單 4是一個(gè)用于添加服務(wù)的 WSDL 文件,它是根據(jù) Christopher Dix 的原始文件修改而得到的。它是 WSDL 1.1 版本的。
清單 4. 用于添加服務(wù)的 WSDL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<?xml version = "1.0" encoding = "UTF-8" ?> <definitions name = "adder" targetNamespace = "http://www.topxml.com/" xmlns:tns = "http://www.topxml.com/" xmlns:xsd = "http://www.w3.org/1999/XMLSchema" xmlns = "http://schemas.xmlsoap.org/wsdl/" > <message name = "Add" > <part name = "A" type = "xsd:double" / > <part name = "B" type = "xsd:double" / > < / message> <message name = "AddResponse" > <part name = "param" type = "xsd:double" / > < / message> <portType name = "adder-port-type" > <operation name = "Add" > < input message = "tns:Add" / > <output message = "tns:AddResponse" / > < / operation> < / portType> <binding name = "adder-soap-binding" type = "tns:adder-port-type" xmlns:soap = "http://schemas.xmlsoap.org/wsdl/soap/" > <soap:binding transport = "http://schemas.xmlsoap.org/soap/http" style = "rpc" / > <operation name = "Add" > <soap:operation soapAction = "http://tempuri.org/" / > < input > <soap:body use = "encoded" namespace = "http://www.topxml.com/" encodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" / > < / input > <output> <soap:body use = "encoded" namespace = "http://www.topxml.com/" encodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" / > < / output> < / operation> < / binding> <service name = "adder-service" > <port name = "adder-port" binding = "tns:adder-soap-binding" > <soap:address location = "http://127.0.0.1:8888/add" xmlns:soap = "http://schemas.xmlsoap.org/wsdl/soap/" / > < / port> < / service> < / definitions> |
清單 5提供了一個(gè)為端點(diǎn)用戶呈現(xiàn)有用信息的 XSLT 腳本。它是從先前的 developerWorks 文章“WSDL processing with XSLT”(請(qǐng)參閱 參考資料)中所開(kāi)發(fā)的一個(gè)轉(zhuǎn)換改編而來(lái)的。它使用了許多自由方式(liberty)和快捷方式(尤其是在它處理 WSDL 上下文中的限定名時(shí)),但它也許可用于目前使用的大多數(shù) WSDL 1.1 文件。
清單 5. XSLT 腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
<?xml version = "1.0" encoding = "utf-8" ?> <xsl:stylesheet version = '1.0' xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:wsdl = "http://schemas.xmlsoap.org/wsdl/" xmlns:soap = "http://schemas.xmlsoap.org/wsdl/soap/" > <xsl:output method = 'html' / > <! - - Lookup tables for messages, portTypes, bindings and services - - > <xsl:key name = 'message' match = "wsdl:definitions/wsdl:message" use = '@name' / > <xsl:key name = 'port-type' match = "wsdl:definitions/wsdl:portType" use = '@name' / > <xsl:key name = 'binding' match = "wsdl:definitions/wsdl:binding" use = '@name' / > <xsl:key name = 'service' match = "wsdl:definitions/wsdl:service" use = '@name' / > <xsl:template match = '/' > <html> <head> <title> Service summary: <xsl:value - of select = 'wsdl:definitions/@name' / > < / title> <meta http - equiv = "content-type" content = "text/html" charset = "UTF-8" / > < / head> <body> <h1> Service summary: <xsl:value - of select = 'wsdl:definitions/@name' / > < / h1> <p><xsl:value - of select = 'wsdl:definitions/@documentation' / >< / p> <xsl: apply - templates select = "wsdl:definitions/wsdl:service" / > < / body> < / html> < / xsl:template> <xsl:template match = 'wsdl:service' > <div style = "background: #ccffff" > Service "<xsl:value-of select='@name'/>" hosted at <code> <xsl:value - of select = 'wsdl:port/soap:address/@location' / > < / code> <xsl:variable name = "binding" select = "key( 'binding' , substring - after(wsdl:port / @binding, ':' ))" / > <xsl:variable name = "port-type" select = "key( 'port-type' , substring - after($binding / @ type , ':' ))" / > <xsl: apply - templates select = "$port-type/wsdl:operation" / > < / div> < / xsl:template> <xsl:template match = 'wsdl:operation' > <p>Operation "<b><xsl:value-of select='@name'/></b>" message details:< / p> <! - - Yes, should sue CSS, but keep this example simple - - > <table border = "1" width = "50%" > <tbody> <xsl: if test = "wsdl:input" > <xsl:call - template name = 'message-role' > <xsl:with - param name = "role-node" select = "wsdl:input" / > < / xsl:call - template> < / xsl: if > <xsl: if test = "wsdl:output" > <xsl:call - template name = 'message-role' > <xsl:with - param name = "role-node" select = "wsdl:output" / > < / xsl:call - template> < / xsl: if > <xsl: if test = "wsdl:fault" > <xsl:call - template name = 'message-role' > <xsl:with - param name = "role-node" select = "wsdl:fault" / > < / xsl:call - template> < / xsl: if > < / tbody> < / table> < / xsl:template> <xsl:template name = 'message-role' > <xsl:param name = "role-node" / > <xsl:variable name = "role-name" select = "local-name($role-node)" / > <xsl:variable name = "message" select = "key( 'message' , substring - after($role - node / @message, ':' ))" / > <tr> <td><xsl:value - of select = '$role-name' / >< / td> <td> <table width = "100%" > <xsl: apply - templates select = "$message/wsdl:part" / > < / table> < / td> < / tr> < / xsl:template> <xsl:template match = 'wsdl:part' > <tr> <td width = "50%" ><b><xsl:value - of select = '@name' / >< / b>< / td> <td><xsl:value - of select = '@type' / >< / td> < / tr> < / xsl:template> < / xsl:stylesheet> |
通常在 Web 服務(wù)本身所在的主機(jī)上提供該服務(wù)人性化的 WSDL 描述是很方便的。 清單 6是 清單 2的變體,它也完成這一任務(wù)。它實(shí)際上提供三種功能:
- 對(duì)于端口 9000 上的 GET 請(qǐng)求:提供該 Web 服務(wù)調(diào)用消息的易于理解的描述
- 對(duì)于端口 8888 上的 GET 請(qǐng)求:提供未經(jīng)處理的 WSDL 文件
- 對(duì)于端口 8888 上的 POST 請(qǐng)求:執(zhí)行 SOAP 請(qǐng)求。
清單 6. 清單 2 的變體
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
#HTTP Listener code for SOAP server import BaseHTTPServer #The processor class is the core of the XSLT API from Ft.Xml.Xslt import Processor #4XSLT uses an InputSource system for reading XML from Ft.Xml import InputSource SOAP_IMPL_FILE = "add.xsl" WSDL_FILE = "listing4.xml" HTML_VIEW_TRANSFORM = "listing5.xslt" class KafkaSoapHandler(BaseHTTPServer.BaseHTTPRequestHandler): def init( cls ): from Ft.Lib import Uri #Set up a processor instance to use cls .processor = Processor.Processor() #Load it with add.xsl add_uri = Uri.OsPathToUri(SOAP_IMPL_FILE, attemptAbsolute = 1 ) transform = InputSource.DefaultFactory.fromUri(add_uri) cls .processor.appendStylesheet(transform) #Now the processor is prepped with a transform and can be used #over and over for the same transform #Prep for WSDL requests cls .wsdl = open (WSDL_FILE).read() return #Make init() a static method of the class init = classmethod (init) def do_POST( self ): clen = self .headers.getheader( 'content-length' ) if clen: clen = int (clen) else : print 'POST ERROR: missing content-length' return if self .path ! = '/add' : self .send_error( 404 ) input_body = self .rfile.read(clen) #input_body is the request SOAP envelope and contents response_body = self ._run_through_kafka(input_body) #response_body is the response SOAP envelope and contents _send_response( self , 200 , 'OK' , response_body) return def do_GET( self ): #response_body is the WSDL file _send_response( self , 200 , 'OK' , self .wsdl) return def _run_through_kafka( self , body): #In 4Suite all InputSources have base URIs in case they refer to #other URIs in some way and resolution is required. #The SOAP messages will not have any such URI references, #So use a dummy base URI source = InputSource.DefaultFactory.fromString(body, "urn:dummy" ) response = self .processor.run(source) return response class HtmlHandler(BaseHTTPServer.BaseHTTPRequestHandler): def init( cls ): from Ft.Lib import Uri #Perform the transform once and store the result processor = Processor.Processor() html_desc_uri = Uri.OsPathToUri(HTML_VIEW_TRANSFORM, attemptAbsolute = 1 ) transform = InputSource.DefaultFactory.fromUri(html_desc_uri) processor.appendStylesheet(transform) wsdl_uri = Uri.OsPathToUri(WSDL_FILE, attemptAbsolute = 1 ) source = InputSource.DefaultFactory.fromUri(wsdl_uri) cls .html_desc = processor.run(source) return #Make init() a static class method init = classmethod (init) def do_GET( self ): #response_body is the WSDL file _send_response( self , 200 , 'OK' , self .html_desc) return #Turn _send_response into a global function #for sharing between the classes def _send_response(handler, code, msg, body): #Prepare a normal response handler.send_response( 200 , 'OK' ) #Send standard HTP headers handler.send_header( 'Content-type' , 'text/html; charset=utf-8' ) handler.send_header( "Connection" , "close" ) handler.send_header( "Accept-Ranges" , "bytes" ) handler.send_header( 'Content-length' , len (body) - 1 ) handler.end_headers() #Send the response prepared by the SOAP end point handler.wfile.write(body) return def soap_listener_function(): listen_on_port = 8888 #Set up to run on local machine server_address = ( '127.0.0.1' , listen_on_port) KafkaSoapHandler.init() httpd = BaseHTTPServer.HTTPServer(server_address, KafkaSoapHandler) print "Listening for GET and POST on port" , listen_on_port #Go into a the main event loop httpd.serve_forever() def html_listener_function(): listen_on_port = 9000 #Set up to run on local machine server_address = ( '127.0.0.1' , listen_on_port) HtmlHandler.init() httpd = BaseHTTPServer.HTTPServer(server_address, HtmlHandler) print "Listening for GET on port" , listen_on_port #Go into a the main event loop httpd.serve_forever() return import time from threading import Thread soap_thread = Thread( None , soap_listener_function) html_thread = Thread( None , html_listener_function) soap_thread.start() #Pause before spawning the next thread time.sleep( 1 ) html_thread.start() |
通過(guò)在服務(wù)器上定義 do_GET 和 do_POST ,您可以在單個(gè)服務(wù)器實(shí)例上處理 GET 和 POST 請(qǐng)求,但是因?yàn)樗褂玫暮?jiǎn)單事件循環(huán)的性質(zhì),您可以使用線程技術(shù)在不同端口上進(jìn)行偵聽(tīng)。這讓您同時(shí)運(yùn)行兩個(gè)服務(wù)器實(shí)例。線程技術(shù)是方法之一,而使用異步事件處理程序是另一種方法。Python 2.2 為更輕松地支持后一種技術(shù)而引入了 asyncore 模塊,我們?cè)诒緦诘纳弦黄恼轮薪榻B了這種方法(請(qǐng)參閱 參考資料)。這一次我們將舉例說(shuō)明線程技術(shù)的用法。關(guān)于使用線程技術(shù)還是使用異步技術(shù)的問(wèn)題,Python 2.2 文檔提出了很好的建議。
僅當(dāng)您的程序很大程度上受 I/O 限制時(shí),[異步方法才是] 真正實(shí)用的。如果您的程序受處理器限制,那么搶先式調(diào)度的線程可能是您所真正需要的。但是,網(wǎng)絡(luò)服務(wù)器很少受處理器限制。
圖 1顯示了易于理解的 Web 服務(wù)描述的瀏覽器視圖。
結(jié)束語(yǔ)
請(qǐng)將這一切都看作實(shí)驗(yàn)素材。Kafka 已經(jīng)相當(dāng)落伍了 — 它似乎從 2001 年以來(lái)就沒(méi)有得到過(guò)維護(hù),并且它使用了相當(dāng)差勁的 XSLT 樣式(其作者坦率地承認(rèn)自己是個(gè) XSLT 菜鳥(niǎo))。但其思想是非常有用的,并且很有價(jià)值。只需要作很小的努力就可以將它更新到 SOAP 1.2 并擴(kuò)展其能力。我們所提供的 WSDL 表示轉(zhuǎn)換也只是一個(gè)起點(diǎn)。也可以將它更新到 WSDL 1.2 并可擴(kuò)展它以顯示關(guān)于 Web 服務(wù)的更多信息。還應(yīng)該更新它以利用名稱空間軸和其它 XSLT 功能以便進(jìn)行更為正確的處理。
XSLT 是一個(gè)沙箱,使用各種語(yǔ)言和環(huán)境的開(kāi)發(fā)人員都可以在其中施展身手。Kafka 是由一位堅(jiān)定的 .NET 開(kāi)發(fā)人員開(kāi)發(fā)的,但我們也可以很快地學(xué)會(huì)它和利用它。這就是擁有一種既可以處理 XML 也可處理 Web 服務(wù)的通用語(yǔ)言(lingua franca)的威力。我們預(yù)計(jì)可以使用用于 Web 服務(wù)的 XSLT 模塊的領(lǐng)域?qū)⒗^續(xù)擴(kuò)展。如果是這樣,本文所提供的基本技術(shù)可能會(huì)促使 Python 程序員們馬上使用這些有用的技術(shù)。