| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.web.test.test_soap -*- | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 | |
| 6 """ | |
| 7 SOAP support for twisted.web. | |
| 8 | |
| 9 Requires SOAPpy 0.10.1 or later. | |
| 10 | |
| 11 Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} | |
| 12 | |
| 13 Future plans: | |
| 14 SOAPContext support of some kind. | |
| 15 Pluggable method lookup policies. | |
| 16 """ | |
| 17 | |
| 18 # SOAPpy | |
| 19 import SOAPpy | |
| 20 | |
| 21 # twisted imports | |
| 22 from twisted.web import server, resource, client | |
| 23 from twisted.internet import defer | |
| 24 | |
| 25 | |
| 26 class SOAPPublisher(resource.Resource): | |
| 27 """Publish SOAP methods. | |
| 28 | |
| 29 By default, publish methods beginning with 'soap_'. If the method | |
| 30 has an attribute 'useKeywords', it well get the arguments passed | |
| 31 as keyword args. | |
| 32 """ | |
| 33 | |
| 34 isLeaf = 1 | |
| 35 | |
| 36 # override to change the encoding used for responses | |
| 37 encoding = "UTF-8" | |
| 38 | |
| 39 def lookupFunction(self, functionName): | |
| 40 """Lookup published SOAP function. | |
| 41 | |
| 42 Override in subclasses. Default behaviour - publish methods | |
| 43 starting with soap_. | |
| 44 | |
| 45 @return: callable or None if not found. | |
| 46 """ | |
| 47 return getattr(self, "soap_%s" % functionName, None) | |
| 48 | |
| 49 def render(self, request): | |
| 50 """Handle a SOAP command.""" | |
| 51 data = request.content.read() | |
| 52 | |
| 53 p, header, body, attrs = SOAPpy.parseSOAPRPC(data, 1, 1, 1) | |
| 54 | |
| 55 methodName, args, kwargs, ns = p._name, p._aslist, p._asdict, p._ns | |
| 56 | |
| 57 # deal with changes in SOAPpy 0.11 | |
| 58 if callable(args): | |
| 59 args = args() | |
| 60 if callable(kwargs): | |
| 61 kwargs = kwargs() | |
| 62 | |
| 63 function = self.lookupFunction(methodName) | |
| 64 | |
| 65 if not function: | |
| 66 self._methodNotFound(request, methodName) | |
| 67 return server.NOT_DONE_YET | |
| 68 else: | |
| 69 if hasattr(function, "useKeywords"): | |
| 70 keywords = {} | |
| 71 for k, v in kwargs.items(): | |
| 72 keywords[str(k)] = v | |
| 73 d = defer.maybeDeferred(function, **keywords) | |
| 74 else: | |
| 75 d = defer.maybeDeferred(function, *args) | |
| 76 | |
| 77 d.addCallback(self._gotResult, request, methodName) | |
| 78 d.addErrback(self._gotError, request, methodName) | |
| 79 return server.NOT_DONE_YET | |
| 80 | |
| 81 def _methodNotFound(self, request, methodName): | |
| 82 response = SOAPpy.buildSOAP(SOAPpy.faultType("%s:Client" % | |
| 83 SOAPpy.NS.ENV_T, "Method %s not found" % methodName), | |
| 84 encoding=self.encoding) | |
| 85 self._sendResponse(request, response, status=500) | |
| 86 | |
| 87 def _gotResult(self, result, request, methodName): | |
| 88 if not isinstance(result, SOAPpy.voidType): | |
| 89 result = {"Result": result} | |
| 90 response = SOAPpy.buildSOAP(kw={'%sResponse' % methodName: result}, | |
| 91 encoding=self.encoding) | |
| 92 self._sendResponse(request, response) | |
| 93 | |
| 94 def _gotError(self, failure, request, methodName): | |
| 95 e = failure.value | |
| 96 if isinstance(e, SOAPpy.faultType): | |
| 97 fault = e | |
| 98 else: | |
| 99 fault = SOAPpy.faultType("%s:Server" % SOAPpy.NS.ENV_T, | |
| 100 "Method %s failed." % methodName) | |
| 101 response = SOAPpy.buildSOAP(fault, encoding=self.encoding) | |
| 102 self._sendResponse(request, response, status=500) | |
| 103 | |
| 104 def _sendResponse(self, request, response, status=200): | |
| 105 request.setResponseCode(status) | |
| 106 | |
| 107 if self.encoding is not None: | |
| 108 mimeType = 'text/xml; charset="%s"' % self.encoding | |
| 109 else: | |
| 110 mimeType = "text/xml" | |
| 111 request.setHeader("Content-type", mimeType) | |
| 112 request.setHeader("Content-length", str(len(response))) | |
| 113 request.write(response) | |
| 114 request.finish() | |
| 115 | |
| 116 | |
| 117 class Proxy: | |
| 118 """A Proxy for making remote SOAP calls. | |
| 119 | |
| 120 Pass the URL of the remote SOAP server to the constructor. | |
| 121 | |
| 122 Use proxy.callRemote('foobar', 1, 2) to call remote method | |
| 123 'foobar' with args 1 and 2, proxy.callRemote('foobar', x=1) | |
| 124 will call foobar with named argument 'x'. | |
| 125 """ | |
| 126 | |
| 127 # at some point this should have encoding etc. kwargs | |
| 128 def __init__(self, url, namespace=None, header=None): | |
| 129 self.url = url | |
| 130 self.namespace = namespace | |
| 131 self.header = header | |
| 132 | |
| 133 def _cbGotResult(self, result): | |
| 134 result = SOAPpy.parseSOAPRPC(result) | |
| 135 if hasattr(result, 'Result'): | |
| 136 return result.Result | |
| 137 elif len(result) == 1: | |
| 138 ## SOAPpy 0.11.6 wraps the return results in a containing structure. | |
| 139 ## This check added to make Proxy behaviour emulate SOAPProxy, which | |
| 140 ## flattens the structure by default. | |
| 141 ## This behaviour is OK because even singleton lists are wrapped in | |
| 142 ## another singleton structType, which is almost always useless. | |
| 143 return result[0] | |
| 144 else: | |
| 145 return result | |
| 146 | |
| 147 def callRemote(self, method, *args, **kwargs): | |
| 148 payload = SOAPpy.buildSOAP(args=args, kw=kwargs, method=method, | |
| 149 header=self.header, namespace=self.namespace) | |
| 150 return client.getPage(self.url, postdata=payload, method="POST", | |
| 151 headers={'content-type': 'text/xml', | |
| 152 'SOAPAction': method} | |
| 153 ).addCallback(self._cbGotResult) | |
| 154 | |
| OLD | NEW |