def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" # TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict( method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns], ) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request, method).import_node(param) else: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response
def dispatch(self, xml, action=None): "Receive and proccess SOAP call" # default values: prefix = self.prefix ret = fault = None soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' name = None try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ("http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env",): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) if action: # method name = action name = action[len(self.action)+1:-1] prefix = self.prefix if not action or not name: # method name = input message name name = method.get_local_name() prefix = method.get_prefix() log.debug('dispatch method %s', name) function, returns_types, args_types, doc = self.methods[name] # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request':method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) log.debug('%s', ret) except Exception, e: import sys etype, evalue, etb = sys.exc_info() if DEBUG: import traceback detail = ''.join(traceback.format_exception(etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': unicode(evalue), 'detail': detail}
def help(self, method=None): "Generate sample request and response messages" (function, returns, args, doc) = self.methods[method] xml = """ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body><%(method)s xmlns="%(namespace)s"/></soap:Body> </soap:Envelope>""" % {'method':method, 'namespace':self.namespace} request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) if args: items = args.items() elif args is None: items = [('value', None)] else: items = [] for k,v in items: request(method).marshall(k, v, add_comments=True, ns=False) xml = """ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body><%(method)sResponse xmlns="%(namespace)s"/></soap:Body> </soap:Envelope>""" % {'method':method, 'namespace':self.namespace} response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) if returns: items = returns.items() elif args is None: items = [('value', None)] else: items = [] for k,v in items: response('%sResponse'%method).marshall(k, v, add_comments=True, ns=False) return request.as_xml(pretty=True), response.as_xml(pretty=True), doc
def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns]) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request, method).import_node(param) else: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response( "Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response
def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: if kwargs: parameters = kwargs.items() username, password = args else: (username, password), parameters = args created = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') expires = (datetime.utcnow() + timedelta(0, 300)).strftime('%Y-%m-%dT%H:%M:%SZ') header = self.header try: header = header % dict(Username = username, Password = password, Created=created, Expires=expires) except KeyError: pass xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, header = header, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns], body_xmlns = self.body_xmlns) request = SimpleXMLElement(xml,namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request,method).import_node(param) else: # marshall parameters: for k,v in parameters: # dict: tag=valor getattr(request,method).marshall(k,v) self.xml_request = request.as_xml() try: self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response except urllib2.HTTPError, error: self.xml_response = error.read() try: response = SimpleXMLElement(self.xml_response, namespace=self.namespace) except: raise error if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): try: detail = str(response.detail.Message) except: detail = "" if "ID3242" in detail: raise NotAuthorizedError() else: raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) else: raise error
def dispatch(self, xml): "Receive and proccess SOAP call" # default values: prefix = self.prefix ret = fault = None soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ( "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env", ): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) name = method.get_local_name() prefix = method.get_prefix() if DEBUG: print "dispatch method", name function, returns_types, args_types, doc = self.methods[name] # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request': method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) if DEBUG: print ret except Exception, e: import sys etype, evalue, etb = sys.exc_info() if DEBUG: import traceback detail = ''.join(traceback.format_exception( etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault = { 'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': unicode(evalue), 'detail': detail }
def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns]) request = SimpleXMLElement(xml,namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request,method).import_node(param) elif parameters: # marshall parameters: for k,v in parameters: # dict: tag=valor getattr(request,method).marshall(k,v) elif not self.__soap_server in ('oracle', ) or self.__soap_server in ('jbossas6',): # JBossAS-6 requires no empty method parameters! delattr(request("Body", ns=soap_namespaces.values(),), method) # construct header and parameters (if not wsdl given) except wsse if self.__headers and not self.services: self.__call_headers = dict([(k, v) for k, v in self.__headers.items() if not k.startswith("wsse:")]) # always extract WS Security header and send it if 'wsse:Security' in self.__headers: #TODO: namespaces too hardwired, clean-up... header = request('Header' , ns=soap_namespaces.values(),) k = 'wsse:Security' v = self.__headers[k] header.marshall(k, v, ns=False, add_children_ns=False) header(k)['xmlns:wsse'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' #<wsse:UsernameToken xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> if self.__call_headers: header = request('Header' , ns=soap_namespaces.values(),) for k, v in self.__call_headers.items(): ##if not self.__ns: ## header['xmlns'] header.marshall(k, v, ns=self.__ns, add_children_ns=False) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response
def preprocess_schema(schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir): """Find schema elements and complex types""" for element in schema.children() or []: if element.get_local_name() in ( 'import', 'include', ): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: log.debug("Schema location not provided for %s!" % (schema_namespace, )) continue if schema_location in imported_schemas: log.debug("Schema %s already imported!" % (schema_location, )) continue imported_schemas[schema_location] = schema_namespace log.debug("Importing schema %s from %s" % (schema_namespace, schema_location)) # Open uri and read xml: xml = fetch(schema_location, http, cache, force_download, wsdl_basedir) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir) element_type = element.get_local_name() if element_type in ('element', 'complexType', "simpleType"): element_name = unicode(element['name']) log.debug("Parsing Element %s: %s" % (element_type, element_name)) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element("restriction", ns=xsd_uri) elif element.get_local_name() == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(elements, element_name, children, element_type, xsd_uri, dialect)
def preprocess_schema(schema): "Find schema elements and complex types" for element in schema.children(): if element.get_local_name() in ('import', ): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: if debug: print "Schema location not provided for %s!" % ( schema_namespace, ) continue if schema_location in imported_schemas: if debug: print "Schema %s already imported!" % ( schema_location, ) continue imported_schemas[schema_location] = schema_namespace if debug: print "Importing schema %s from %s" % ( schema_namespace, schema_location) # Open uri and read xml: xml = fetch(schema_location) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema) if element.get_local_name() in ('element', 'complexType', "simpleType"): element_name = unicode(element['name']) if debug: print "Parsing Element %s: %s" % ( element.get_local_name(), element_name) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element("restriction", ns=xsd_uri) elif element.get_local_name( ) == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(element_name, children)
def wsdl(self, url, debug=False, cache=False): "Parse Web Service Description v1.1" soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s) REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()]) def fetch(url): "Fetch a document from a URL, save it locally if cache enabled" import os, hashlib # make md5 hash of the url for caching... filename = "%s.xml" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename = os.path.join(cache, filename) if cache and os.path.exists(filename): if debug: print "Reading file %s" % (filename, ) f = open(filename, "r") xml = f.read() f.close() else: if debug: print "Fetching url %s" % (url, ) f = urllib.urlopen(url) xml = f.read() if cache: if debug: print "Writing file %s" % (filename, ) f = open(filename, "w") f.write(xml) f.close() return xml # Open uri and read xml: xml = fetch(url) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service['name'] if not service_name: continue # empty service? if debug: print "Processing service", service_name serv = services.setdefault(service_name, {'ports': {}}) serv['documentation'] = service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { 'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] if debug: print "Processing binding", service_name soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation['name'] op = operation('operation', ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations.setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) #if action: #TODO: separe operation_binding from operation if action: d["action"] = action #TODO: cleanup element/schema/types parsing: def process_element(element_name, node): "Parse and define simple element types" if debug: print "Processing element", element_name for tag in node: if tag.get_local_name() in ("annotation", "documentation"): continue elif tag.get_local_name() in ('element', 'restriction'): if debug: print element_name, "has not children!", tag children = tag # element "alias"? alias = True elif tag.children(): children = tag.children() alias = False else: if debug: print element_name, "has not children!", tag continue #TODO: abstract? d = OrderedDict() for e in children: t = e['type'] if not t: t = e['base'] # complexContent (extension)! if not t: t = 'anyType' # no type given! t = t.split(":") if len(t) > 1: ns, type_name = t else: ns, type_name = None, t[0] if element_name == type_name: continue # prevent infinite recursion uri = ns and e.get_namespace_uri(ns) or xsd_uri if uri == xsd_uri: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # complex type, postprocess later fn = elements.setdefault(unicode(type_name), OrderedDict()) if e['name'] is not None and not alias: e_name = unicode(e['name']) d[e_name] = fn else: if debug: print "complexConent/simpleType/element", element_name, "=", type_name d[None] = fn if e['maxOccurs'] == "unbounded": # it's an array... TODO: compound arrays? d.array = True if e is not None and e.get_local_name( ) == 'extension' and e.children(): # extend base element: process_element(element_name, e.children()) elements.setdefault(element_name, OrderedDict()).update(d) # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get( 'targetNamespace', self.namespace) imported_schemas = {} def preprocess_schema(schema): "Find schema elements and complex types" for element in schema.children(): if element.get_local_name() in ('import', ): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: if debug: print "Schema location not provided for %s!" % ( schema_namespace, ) continue if schema_location in imported_schemas: if debug: print "Schema %s already imported!" % ( schema_location, ) continue imported_schemas[schema_location] = schema_namespace if debug: print "Importing schema %s from %s" % ( schema_namespace, schema_location) # Open uri and read xml: xml = fetch(schema_location) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema) if element.get_local_name() in ('element', 'complexType', "simpleType"): element_name = unicode(element['name']) if debug: print "Parsing Element %s: %s" % ( element.get_local_name(), element_name) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element("restriction", ns=xsd_uri) elif element.get_local_name( ) == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(element_name, children) def postprocess_element(elements): "Fix unresolved references (elements referenced before its definition, thanks .net)" for k, v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v != elements: #TODO: fix recursive elements postprocess_element(v) if None in v and v[None]: # extension base? if isinstance(v[None], dict): for i, kk in enumerate(v[None]): # extend base -keep orginal order- elements[k].insert(kk, v[None][kk], i) del v[None] else: # "alias", just replace if debug: print "Replacing ", k, " = ", v[None] elements[k] = v[None] #break if isinstance(v, list): for n in v: # recurse list postprocess_element(n) # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema(schema) postprocess_element(elements) for message in wsdl.message: if debug: print "Processing message", message['name'] part = message('part', error=False) element = {} if part: element_name = part['element'] if not element_name: element_name = part['type'] # some uses type instead element_name = get_local_name(element_name) element = {element_name: elements.get(element_name)} messages[message['name']] = element for port_type in wsdl.portType: port_type_name = port_type['name'] if debug: print "Processing port type", port_type_name binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation['name'] op = operations[op_name] op['documentation'] = unicode( operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input['message']) output = get_local_name(operation.output['message']) op['input'] = messages[input] op['output'] = messages[output] if debug: import pprint pprint.pprint(services) return services
def wsdl_parse(self, url, debug=False, cache=False): "Parse Web Service Description v1.1" if debug: print "wsdl url: %s" % url # Try to load a previously parsed wsdl: force_download = False if cache: # make md5 hash of the url for caching... filename_pkl = "%s.pkl" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename_pkl = os.path.join(cache, filename_pkl) if os.path.exists(filename_pkl): if debug: print "Unpickle file %s" % (filename_pkl, ) f = open(filename_pkl, "r") pkl = pickle.load(f) f.close() # sanity check: if pkl['version'][:-1] != __version__.split(" ")[0][:-1] or pkl['url'] != url: import warnings warnings.warn('version or url mismatch! discarding cached wsdl', RuntimeWarning) if debug: print 'Version:', pkl['version'], __version__ print 'URL:', pkl['url'], url force_download = True else: self.namespace = pkl['namespace'] self.documentation = pkl['documentation'] return pkl['services'] soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri="http://schemas.xmlsoap.org/wsdl/" xsd_uri="http://www.w3.org/2001/XMLSchema" xsi_uri="http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s) REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()]) def fetch(url): "Download a document from a URL, save it locally if cache enabled" # make md5 hash of the url for caching... filename = "%s.xml" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename = os.path.join(cache, filename) if cache and os.path.exists(filename) and not force_download: if debug: print "Reading file %s" % (filename, ) f = open(filename, "r") xml = f.read() f.close() else: if debug: print "Fetching url %s using urllib2" % (url, ) f = urllib2.urlopen(url) xml = f.read() if cache: if debug: print "Writing file %s" % (filename, ) if not os.path.isdir(cache): os.makedirs(cache) f = open(filename, "w") f.write(xml) f.close() return xml # Open uri and read xml: xml = fetch(url) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v== xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name=service['name'] if not service_name: continue # empty service? if debug: print "Processing service", service_name serv = services.setdefault(service_name, {'ports': {}}) serv['documentation']=service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = {'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] if debug: print "Processing binding", service_name soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation['name'] op = operation('operation',ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations.setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) #if action: #TODO: separe operation_binding from operation if action: d["action"] = action #TODO: cleanup element/schema/types parsing: def process_element(element_name, node): "Parse and define simple element types" if debug: print "Processing element", element_name for tag in node: if tag.get_local_name() in ("annotation", "documentation"): continue elif tag.get_local_name() in ('element', 'restriction'): if debug: print element_name,"has not children!",tag children = tag # element "alias"? alias = True elif tag.children(): children = tag.children() alias = False else: if debug: print element_name,"has not children!",tag continue #TODO: abstract? d = OrderedDict() for e in children: t = e['type'] if not t: t = e['base'] # complexContent (extension)! if not t: t = 'anyType' # no type given! t = t.split(":") if len(t)>1: ns, type_name = t else: ns, type_name = None, t[0] if element_name == type_name: continue # prevent infinite recursion uri = ns and e.get_namespace_uri(ns) or xsd_uri if uri==xsd_uri: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # complex type, postprocess later fn = elements.setdefault(unicode(type_name), OrderedDict()) if e['name'] is not None and not alias: e_name = unicode(e['name']) d[e_name] = fn else: if debug: print "complexConent/simpleType/element", element_name, "=", type_name d[None] = fn if e['maxOccurs']=="unbounded": # it's an array... TODO: compound arrays? d.array = True if e is not None and e.get_local_name() == 'extension' and e.children(): # extend base element: process_element(element_name, e.children()) elements.setdefault(element_name, OrderedDict()).update(d) # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get('targetNamespace', self.namespace) imported_schemas = {} def preprocess_schema(schema): "Find schema elements and complex types" for element in schema.children(): if element.get_local_name() in ('import', ): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: if debug: print "Schema location not provided for %s!" % (schema_namespace, ) continue if schema_location in imported_schemas: if debug: print "Schema %s already imported!" % (schema_location, ) continue imported_schemas[schema_location] = schema_namespace if debug: print "Importing schema %s from %s" % (schema_namespace, schema_location) # Open uri and read xml: xml = fetch(schema_location) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema) if element.get_local_name() in ('element', 'complexType', "simpleType"): element_name = unicode(element['name']) if debug: print "Parsing Element %s: %s" % (element.get_local_name(),element_name) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element("restriction", ns=xsd_uri) elif element.get_local_name() == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(element_name, children) def postprocess_element(elements): "Fix unresolved references (elements referenced before its definition, thanks .net)" for k,v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v!=elements: #TODO: fix recursive elements postprocess_element(v) if None in v and v[None]: # extension base? if isinstance(v[None], dict): for i, kk in enumerate(v[None]): # extend base -keep orginal order- elements[k].insert(kk, v[None][kk], i) del v[None] else: # "alias", just replace if debug: print "Replacing ", k , " = ", v[None] elements[k] = v[None] #break if isinstance(v, list): for n in v: # recurse list postprocess_element(n) # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema(schema) postprocess_element(elements) for message in wsdl.message: if debug: print "Processing message", message['name'] part = message('part', error=False) element = {} if part: element_name = part['element'] if not element_name: element_name = part['type'] # some uses type instead element_name = get_local_name(element_name) element = {element_name: elements.get(element_name)} messages[message['name']] = element for port_type in wsdl.portType: port_type_name = port_type['name'] if debug: print "Processing port type", port_type_name binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation['name'] op = operations[op_name] op['documentation'] = unicode(operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input['message']) output = get_local_name(operation.output['message']) op['input'] = messages[input] op['output'] = messages[output] if debug: import pprint pprint.pprint(services) # Save parsed wsdl (cache) if cache: f = open(filename_pkl, "wb") pkl = { 'version': __version__.split(" ")[0], 'url': url, 'namespace': self.namespace, 'documentation': self.documentation, 'services': services, } pickle.dump(pkl, f) f.close() return services
result = response['AddResult'] print result, type(result), "auto-unmarshalled" if '--raw' in sys.argv: # raw (unmarshalled parameter) local sample webservice exposed by web2py from client import SoapClient client = SoapClient( location="http://127.0.0.1:8000/webservices/sample/call/soap", action= 'http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction namespace="http://127.0.0.1:8000/webservices/sample/call/soap", soap_ns='soap', trace=True, ns=False) params = SimpleXMLElement( """<?xml version="1.0" encoding="UTF-8"?><AddIntegers><a>3</a><b>2</b></AddIntegers>""" ) # manully convert returned type response = client.call('AddIntegers', params) result = response.AddResult print int(result) # manully convert returned type if '--ctg' in sys.argv: # test AFIP Agriculture webservice client = SoapClient( location="https://fwshomo.afip.gov.ar/wsctg/services/CTGService", action= 'http://impl.service.wsctg.afip.gov.ar/CTGService/', # SOAPAction namespace="http://impl.service.wsctg.afip.gov.ar/CTGService/", trace=True, ns=True) response = client.dummy()
def wsdl(self, url, debug=False): "Parse Web Service Description v1.1" soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri="http://schemas.xmlsoap.org/wsdl/" xsd_uri="http://www.w3.org/2001/XMLSchema" xsi_uri="http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s) REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()]) # Open uri and read xml: f = urllib.urlopen(url) xml = f.read() # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v== xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name=service['name'] if debug: print "Processing service", service_name serv = services.setdefault(service_name, {'ports': {}}) serv['documentation']=service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = {'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] if debug: print "Processing binding", service_name soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation['name'] op = operation('operation',ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations.setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) #if action: #TODO: separe operation_binding from operation d["action"] = action def process_element(element_name, children): if debug: print "Processing element", element_name for tag in children: d = OrderedDict() for e in tag.children(): t = e['type'].split(":") if len(t)>1: ns, type_name = t else: ns, type_name = xsd_ns, t[0] if ns==xsd_ns: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # complex type, postprocess later fn = elements.setdefault(unicode(type_name), OrderedDict()) e_name = unicode(e['name']) d[e_name] = fn if e['maxOccurs']=="unbounded": # it's an array... TODO: compound arrays? d.array = True elements.setdefault(element_name, OrderedDict()).update(d) for element in wsdl.types("schema", ns=xsd_uri).children(): if element.get_local_name() in ('element', 'complexType'): element_name = unicode(element['name']) if debug: print "Parsing Element %s: %s" % (element.get_local_name(),element_name) if element.get_local_name() == 'complexType': children = element.children() else: children = element.children() if children: children = children.children() if children: process_element(element_name, children) def postprocess_element(elements): for k,v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v!=elements: #TODO: fix recursive elements postprocess_element(v) postprocess_element(elements) for message in wsdl.message: if debug: print "Processing message", message['name'] part = message('part', error=False) element = {} if part: element_name = get_local_name(part['element']) element = {element_name: elements.get(element_name)} messages[message['name']] = element for port_type in wsdl.portType: port_type_name = port_type['name'] if debug: print "Processing port type", port_type_name binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation['name'] op = operations[op_name] op['documentation'] = unicode(operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input['message']) output = get_local_name(operation.output['message']) op['input'] = messages[input] op['output'] = messages[output] if debug: import pprint pprint.pprint(services) return services
def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: if kwargs: parameters = kwargs.items() username, password = args else: (username, password), parameters = args created = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') expires = (datetime.utcnow() + timedelta(0, 300)).strftime('%Y-%m-%dT%H:%M:%SZ') header = self.header try: header = header % dict(Username=username, Password=password, Created=created, Expires=expires) except KeyError: pass xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, header=header, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns], body_xmlns=self.body_xmlns) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request, method).import_node(param) else: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) self.xml_request = request.as_xml() try: self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response( "Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response except urllib2.HTTPError, error: self.xml_response = error.read() try: response = SimpleXMLElement(self.xml_response, namespace=self.namespace) except: raise error if self.exceptions and response( "Fault", ns=soap_namespaces.values(), error=False): try: detail = str(response.detail.Message) except: detail = "" if "ID3242" in detail: raise NotAuthorizedError() else: raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) else: raise error
def wsdl(self): "Generate Web Service Description v1.1" xml = """<?xml version="1.0"?> <wsdl:definitions name="%(name)s" targetNamespace="%(namespace)s" xmlns:tns="%(namespace)s" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">%(documentation)s</wsdl:documentation> <wsdl:types> <xsd:schema targetNamespace="%(namespace)s" elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> </xsd:schema> </wsdl:types> </wsdl:definitions> """ % {'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation} wsdl = SimpleXMLElement(xml) for method, (function, returns, args, doc) in self.methods.items(): # create elements: def parse_element(name, values, array=False, complex=False): if not complex: element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element') complex = element.add_child("xsd:complexType") else: complex = wsdl('wsdl:types')('xsd:schema').add_child('xsd:complexType') element = complex element['name'] = name if values: items = values elif values is None: items = [('value', None)] else: items = [] if not array and items: all = complex.add_child("xsd:all") elif items: all = complex.add_child("xsd:sequence") for k,v in items: e = all.add_child("xsd:element") e['name'] = k if array: e[:]={'minOccurs': "0", 'maxOccurs': "unbounded"} if v in TYPE_MAP.keys(): t='xsd:%s' % TYPE_MAP[v] elif v is None: t='xsd:anyType' elif isinstance(v, list): n="ArrayOf%s%s" % (name, k) l = [] for d in v: l.extend(d.items()) parse_element(n, l, array=True, complex=True) t = "tns:%s" % n elif isinstance(v, dict): n="%s%s" % (name, k) parse_element(n, v.items(), complex=True) t = "tns:%s" % n e.add_attribute('type', t) parse_element("%s" % method, args and args.items()) parse_element("%sResponse" % method, returns and returns.items()) # create messages: for m,e in ('Input',''), ('Output','Response'): message = wsdl.add_child('wsdl:message') message['name'] = "%s%s" % (method, m) part = message.add_child("wsdl:part") part[:] = {'name': 'parameters', 'element': 'tns:%s%s' % (method,e)} # create ports portType = wsdl.add_child('wsdl:portType') portType['name'] = "%sPortType" % self.name for method, (function, returns, args, doc) in self.methods.items(): op = portType.add_child('wsdl:operation') op['name'] = method if doc: op.add_child("wsdl:documentation", doc) input = op.add_child("wsdl:input") input['message'] = "tns:%sInput" % method output = op.add_child("wsdl:output") output['message'] = "tns:%sOutput" % method # create bindings binding = wsdl.add_child('wsdl:binding') binding['name'] = "%sBinding" % self.name binding['type'] = "tns:%sPortType" % self.name soapbinding = binding.add_child('soap:binding') soapbinding['style'] = "document" soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http" for method in self.methods.keys(): op = binding.add_child('wsdl:operation') op['name'] = method soapop = op.add_child('soap:operation') soapop['soapAction'] = self.action soapop['style'] = 'document' input = op.add_child("wsdl:input") ##input.add_attribute('name', "%sInput" % method) soapbody = input.add_child("soap:body") soapbody["use"] = "literal" output = op.add_child("wsdl:output") ##output.add_attribute('name', "%sOutput" % method) soapbody = output.add_child("soap:body") soapbody["use"] = "literal" service = wsdl.add_child('wsdl:service') service["name"] = "%sService" % self.name service.add_child('wsdl:documentation', text=self.documentation) port=service.add_child('wsdl:port') port["name"] = "%s" % self.name port["binding"] = "tns:%sBinding" % self.name soapaddress = port.add_child('soap:address') soapaddress["location"] = self.location return wsdl.as_xml(pretty=True)
def dispatch(self, xml, action=None): "Receive and proccess SOAP call" # default values: prefix = self.prefix ret = fault = None soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' name = None # namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] _ns_reversed = dict(((v,k) for k,v in self.namespaces.iteritems())) # Switch keys-values # _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'} try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ("http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env",): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value # If the value from attributes on Envelope is in additional namespaces elif v in self.namespaces.values(): _ns = request.attributes()[k].localName _uri = request.attributes()[k].value _ns_reversed[_uri] = _ns # update with received alias # Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod' # After that we know how the client has prefixed additional namespaces ns = NS_RX.findall(xml) for k, v in ns: if v in self.namespaces.values(): _ns_reversed[v] = k soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) if action: # method name = action name = action[len(self.action)+1:-1] prefix = self.prefix if not action or not name: # method name = input message name name = method.get_local_name() prefix = method.get_prefix() log.debug('dispatch method: %s', name) function, returns_types, args_types, doc = self.methods[name] log.debug('returns_types %s', returns_types) # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request': method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) log.debug('dispathed method returns: %s', ret) except Exception: # This shouldn't be one huge try/except import sys etype, evalue, etb = sys.exc_info() log.error(traceback.format_exc()) if self.debug: detail = ''.join(traceback.format_exception(etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': unicode(evalue), 'detail': detail} # build response message if not prefix: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" else: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(prefix)s="%(namespace)s"/>""" xml %= { # a %= {} is a shortcut for a = a % {} 'namespace': self.namespace, 'prefix': prefix, 'soap_ns': soap_ns, 'soap_uri': soap_uri } # Now we add extra namespaces xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed) # Change our namespace alias to that given by the client. # We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] # mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'} mapping = dict(((k, _ns_reversed[v]) for k,v in self.namespaces.iteritems())) # Switch keys-values and change value # and get {'model': u'mod', 'external': u'ext'} response = SimpleXMLElement(xml, namespace=self.namespace, namespaces_map = mapping, prefix=prefix) response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" body = response.add_child("%s:Body" % soap_ns, ns=False) if fault: # generate a Soap Fault (with the python exception) body.marshall("%s:Fault" % soap_ns, fault, ns=False) else: # return normal value res = body.add_child("%sResponse" % name, ns=prefix) if not prefix: res['xmlns'] = self.namespace # add target namespace # serialize returned values (response) if type definition available if returns_types: if not isinstance(ret, dict): res.marshall(returns_types.keys()[0], ret, ) else: for k,v in ret.items(): res.marshall(k, v) elif returns_types is None: # merge xmlelement returned res.import_node(ret) elif returns_types == {}: log.warning('Given returns_types is an empty dict.') return response.as_xml(pretty=self.pretty)
def wsdl_parse(self, url, cache=False): "Parse Web Service Description v1.1" log.debug("wsdl url: %s" % url) # Try to load a previously parsed wsdl: force_download = False if cache: # make md5 hash of the url for caching... filename_pkl = "%s.pkl" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename_pkl = os.path.join(cache, filename_pkl) if os.path.exists(filename_pkl): log.debug("Unpickle file %s" % (filename_pkl, )) f = open(filename_pkl, "r") pkl = pickle.load(f) f.close() # sanity check: if pkl['version'][:-1] != __version__.split( " ")[0][:-1] or pkl['url'] != url: import warnings warnings.warn( 'version or url mismatch! discarding cached wsdl', RuntimeWarning) log.debug('Version: %s %s' % (pkl['version'], __version__)) log.debug('URL: %s %s' % (pkl['url'], url)) force_download = True else: self.namespace = pkl['namespace'] self.documentation = pkl['documentation'] return pkl['services'] soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: s and str( (':' in s) and s.split(':')[1] or s) get_namespace_prefix = lambda s: s and str( (':' in s) and s.split(':')[0] or None) # always return an unicode object: REVERSE_TYPE_MAP[u'string'] = unicode # Open uri and read xml: xml = fetch(url, self.http, cache, force_download, self.wsdl_basedir) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service['name'] if not service_name: continue # empty service? log.debug("Processing service %s" % service_name) serv = services.setdefault(service_name, {'ports': {}}) serv['documentation'] = service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) operations[binding_name] = {} address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { 'name': binding_name, 'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) if port_type_name not in port_type_bindings: port_type_bindings[port_type_name] = [] port_type_bindings[port_type_name].append(bindings[binding_name]) for operation in binding.operation: op_name = operation['name'] op = operation('operation', ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations[binding_name].setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) d['parts'] = {} # input and/or ouput can be not present! input = operation('input', error=False) body = input and input( 'body', ns=soap_uris.values(), error=False) d['parts']['input_body'] = body and body['parts'] or None output = operation('output', error=False) body = output and output( 'body', ns=soap_uris.values(), error=False) d['parts']['output_body'] = body and body['parts'] or None header = input and input( 'header', ns=soap_uris.values(), error=False) d['parts']['input_header'] = header and { 'message': header['message'], 'part': header['part'] } or None header = output and output( 'header', ns=soap_uris.values(), error=False) d['parts']['output_header'] = header and { 'message': header['message'], 'part': header['part'] } or None if action: d["action"] = action # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get( 'targetNamespace', self.namespace) imported_schemas = {} # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema(schema, imported_schemas, elements, xsd_uri, self.__soap_server, self.http, cache, force_download, self.wsdl_basedir) postprocess_element(elements) for message in wsdl.message: log.debug("Processing message %s" % message['name']) for part in message('part', error=False) or []: element = {} element_name = part['element'] if not element_name: # some implementations (axis) uses type instead element_name = part['type'] type_ns = get_namespace_prefix(element_name) type_uri = wsdl.get_namespace_uri(type_ns) if type_uri == xsd_uri: element_name = get_local_name(element_name) fn = REVERSE_TYPE_MAP.get(unicode(element_name), None) element = {part['name']: fn} # emulate a true Element (complexType) messages.setdefault((message['name'], None), { message['name']: OrderedDict() }).values()[0].update(element) else: element_name = get_local_name(element_name) fn = elements.get(make_key(element_name, 'element')) if not fn: # some axis servers uses complexType for part messages fn = elements.get(make_key(element_name, 'complexType')) element = {message['name']: {part['name']: fn}} else: element = {element_name: fn} messages[(message['name'], part['name'])] = element for port_type in wsdl.portType: port_type_name = port_type['name'] log.debug("Processing port type %s" % port_type_name) for binding in port_type_bindings[port_type_name]: for operation in port_type.operation: op_name = operation['name'] op = operations[str(binding['name'])][op_name] op['documentation'] = unicode( operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) if operation("input", error=False): input_msg = get_local_name( operation.input['message']) input_header = op['parts'].get('input_header') if input_header: header_msg = get_local_name( input_header.get('message')) header_part = get_local_name( input_header.get('part')) # warning: some implementations use a separate message! header = get_message(messages, header_msg or input_msg, header_part) else: header = None # not enought info to search the header message: op['input'] = get_message( messages, input_msg, op['parts'].get('input_body')) op['header'] = header else: op['input'] = None op['header'] = None if operation("output", error=False): output_msg = get_local_name( operation.output['message']) op['output'] = get_message( messages, output_msg, op['parts'].get('output_body')) else: op['output'] = None # dump the full service/port/operation map #log.debug(pprint.pformat(services)) # Save parsed wsdl (cache) if cache: f = open(filename_pkl, "wb") pkl = { 'version': __version__.split(" ")[0], 'url': url, 'namespace': self.namespace, 'documentation': self.documentation, 'services': services, } pickle.dump(pkl, f) f.close() return services
def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns]) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request, method).import_node(param) elif parameters: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) elif not self.__soap_server in ('oracle', ) or self.__soap_server in ( 'jbossas6', ): # JBossAS-6 requires no empty method parameters! delattr(request( "Body", ns=soap_namespaces.values(), ), method) # construct header and parameters (if not wsdl given) except wsse if self.__headers and not self.services: self.__call_headers = dict([(k, v) for k, v in self.__headers.items() if not k.startswith("wsse:")]) # always extract WS Security header and send it if 'wsse:Security' in self.__headers: #TODO: namespaces too hardwired, clean-up... header = request( 'Header', ns=soap_namespaces.values(), ) k = 'wsse:Security' v = self.__headers[k] header.marshall(k, v, ns=False, add_children_ns=False) header( k )['xmlns:wsse'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' #<wsse:UsernameToken xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> if self.__call_headers: header = request( 'Header', ns=soap_namespaces.values(), ) for k, v in self.__call_headers.items(): ##if not self.__ns: ## header['xmlns'] header.marshall(k, v, ns=self.__ns, add_children_ns=False) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response( "Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response
def wsdl(self, url, debug=False, cache=False): "Parse Web Service Description v1.1" soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": "soap11", "http://schemas.xmlsoap.org/wsdl/soap12/": "soap12", } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: str((":" in s) and s.split(":")[1] or s) REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()]) def fetch(url): "Fetch a document from a URL, save it locally if cache enabled" import os, hashlib # make md5 hash of the url for caching... filename = "%s.xml" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename = os.path.join(cache, filename) if cache and os.path.exists(filename): if debug: print "Reading file %s" % (filename,) f = open(filename, "r") xml = f.read() f.close() else: if debug: print "Fetching url %s" % (url,) f = urllib.urlopen(url) xml = f.read() if cache: if debug: print "Writing file %s" % (filename,) f = open(filename, "w") f.write(xml) f.close() return xml # Open uri and read xml: xml = fetch(url) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl["targetNamespace"] self.documentation = unicode(wsdl("documentation", error=False) or "") services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service["name"] if not service_name: continue # empty service? if debug: print "Processing service", service_name serv = services.setdefault(service_name, {"ports": {}}) serv["documentation"] = service["documentation"] or "" for port in service.port: binding_name = get_local_name(port["binding"]) address = port("address", ns=soap_uris.values(), error=False) location = address and address["location"] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { "service_name": service_name, "location": location, "soap_uri": soap_uri, "soap_ver": soap_ver, } serv["ports"][port["name"]] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding["name"] if debug: print "Processing binding", service_name soap_binding = binding("binding", ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding["transport"] or None port_type_name = get_local_name(binding["type"]) bindings[binding_name].update({"port_type_name": port_type_name, "transport": transport, "operations": {}}) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation["name"] op = operation("operation", ns=soap_uris.values(), error=False) action = op and op["soapAction"] d = operations.setdefault(op_name, {}) bindings[binding_name]["operations"][op_name] = d d.update({"name": op_name}) # if action: #TODO: separe operation_binding from operation if action: d["action"] = action # TODO: cleanup element/schema/types parsing: def process_element(element_name, node): "Parse and define simple element types" if debug: print "Processing element", element_name for tag in node: if tag.get_local_name() in ("annotation", "documentation"): continue elif tag.get_local_name() in ("element", "restriction"): if debug: print element_name, "has not children!", tag children = tag # element "alias"? alias = True elif tag.children(): children = tag.children() alias = False else: if debug: print element_name, "has not children!", tag continue # TODO: abstract? d = OrderedDict() for e in children: t = e["type"] if not t: t = e["base"] # complexContent (extension)! if not t: t = "anyType" # no type given! t = t.split(":") if len(t) > 1: ns, type_name = t else: ns, type_name = None, t[0] if element_name == type_name: continue # prevent infinite recursion uri = ns and e.get_namespace_uri(ns) or xsd_uri if uri == xsd_uri: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # complex type, postprocess later fn = elements.setdefault(unicode(type_name), OrderedDict()) if e["name"] is not None and not alias: e_name = unicode(e["name"]) d[e_name] = fn else: if debug: print "complexConent/simpleType/element", element_name, "=", type_name d[None] = fn if e["maxOccurs"] == "unbounded": # it's an array... TODO: compound arrays? d.array = True if e is not None and e.get_local_name() == "extension" and e.children(): # extend base element: process_element(element_name, e.children()) elements.setdefault(element_name, OrderedDict()).update(d) # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get("targetNamespace", self.namespace) imported_schemas = {} def preprocess_schema(schema): "Find schema elements and complex types" for element in schema.children(): if element.get_local_name() in ("import",): schema_namespace = element["namespace"] schema_location = element["schemaLocation"] if schema_location is None: if debug: print "Schema location not provided for %s!" % (schema_namespace,) continue if schema_location in imported_schemas: if debug: print "Schema %s already imported!" % (schema_location,) continue imported_schemas[schema_location] = schema_namespace if debug: print "Importing schema %s from %s" % (schema_namespace, schema_location) # Open uri and read xml: xml = fetch(schema_location) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema) if element.get_local_name() in ("element", "complexType", "simpleType"): element_name = unicode(element["name"]) if debug: print "Parsing Element %s: %s" % (element.get_local_name(), element_name) if element.get_local_name() == "complexType": children = element.children() elif element.get_local_name() == "simpleType": children = element("restriction", ns=xsd_uri) elif element.get_local_name() == "element" and element["type"]: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == "element": children = element if children: process_element(element_name, children) def postprocess_element(elements): "Fix unresolved references (elements referenced before its definition, thanks .net)" for k, v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v != elements: # TODO: fix recursive elements postprocess_element(v) if None in v and v[None]: # extension base? if isinstance(v[None], dict): for i, kk in enumerate(v[None]): # extend base -keep orginal order- elements[k].insert(kk, v[None][kk], i) del v[None] else: # "alias", just replace if debug: print "Replacing ", k, " = ", v[None] elements[k] = v[None] # break if isinstance(v, list): for n in v: # recurse list postprocess_element(n) # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema(schema) postprocess_element(elements) for message in wsdl.message: if debug: print "Processing message", message["name"] part = message("part", error=False) element = {} if part: element_name = part["element"] if not element_name: element_name = part["type"] # some uses type instead element_name = get_local_name(element_name) element = {element_name: elements.get(element_name)} messages[message["name"]] = element for port_type in wsdl.portType: port_type_name = port_type["name"] if debug: print "Processing port type", port_type_name binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation["name"] op = operations[op_name] op["documentation"] = unicode(operation("documentation", error=False) or "") if binding["soap_ver"]: # TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input["message"]) output = get_local_name(operation.output["message"]) op["input"] = messages[input] op["output"] = messages[output] if debug: import pprint pprint.pprint(services) return services
def dispatch(self, xml, action=None): """Receive and process SOAP call""" # default values: prefix = self.prefix ret = fault = None soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' name = None # namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] _ns_reversed = dict( ((v, k) for k, v in self.namespaces.iteritems())) # Switch keys-values # _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'} try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ( "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env", ): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value # If the value from attributes on Envelope is in additional namespaces elif v in self.namespaces.values(): _ns = request.attributes()[k].localName _uri = request.attributes()[k].value _ns_reversed[_uri] = _ns # update with received alias # Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod' # After that we know how the client has prefixed additional namespaces ns = NS_RX.findall(xml) for k, v in ns: if v in self.namespaces.values(): _ns_reversed[v] = k soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) if action: # method name = action name = action[len(self.action) + 1:-1] prefix = self.prefix if not action or not name: # method name = input message name name = method.get_local_name() prefix = method.get_prefix() log.debug('dispatch method: %s', name) function, returns_types, args_types, doc = self.methods[name] log.debug('returns_types %s', returns_types) # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request': method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) log.debug('dispathed method returns: %s', ret) except Exception: # This shouldn't be one huge try/except import sys etype, evalue, etb = sys.exc_info() log.error(traceback.format_exc()) if self.debug: detail = ''.join(traceback.format_exception( etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault = { 'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': unicode(evalue), 'detail': detail } # build response message if not prefix: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" else: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(prefix)s="%(namespace)s"/>""" xml %= { # a %= {} is a shortcut for a = a % {} 'namespace': self.namespace, 'prefix': prefix, 'soap_ns': soap_ns, 'soap_uri': soap_uri } # Now we add extra namespaces xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed) # Change our namespace alias to that given by the client. # We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] # mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'} mapping = dict( ((k, _ns_reversed[v]) for k, v in self.namespaces.iteritems() )) # Switch keys-values and change value # and get {'model': u'mod', 'external': u'ext'} response = SimpleXMLElement(xml, namespace=self.namespace, namespaces_map=mapping, prefix=prefix) response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" body = response.add_child("%s:Body" % soap_ns, ns=False) if fault: # generate a Soap Fault (with the python exception) body.marshall("%s:Fault" % soap_ns, fault, ns=False) else: # return normal value res = body.add_child("%sResponse" % name, ns=prefix) if not prefix: res['xmlns'] = self.namespace # add target namespace # serialize returned values (response) if type definition available if returns_types: if not isinstance(ret, dict): res.marshall( returns_types.keys()[0], ret, ) else: for k, v in ret.items(): res.marshall(k, v) elif returns_types is None: # merge xmlelement returned res.import_node(ret) elif returns_types == {}: log.warning('Given returns_types is an empty dict.') return response.as_xml(pretty=self.pretty)
def wsdl_parse(self, url, cache=False): "Parse Web Service Description v1.1" log.debug("wsdl url: %s" % url) # Try to load a previously parsed wsdl: force_download = False if cache: # make md5 hash of the url for caching... filename_pkl = "%s.pkl" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename_pkl = os.path.join(cache, filename_pkl) if os.path.exists(filename_pkl): log.debug("Unpickle file %s" % (filename_pkl,)) f = open(filename_pkl, "r") pkl = pickle.load(f) f.close() # sanity check: if pkl["version"][:-1] != __version__.split(" ")[0][:-1] or pkl["url"] != url: import warnings warnings.warn("version or url mismatch! discarding cached wsdl", RuntimeWarning) log.debug("Version: %s %s" % (pkl["version"], __version__)) log.debug("URL: %s %s" % (pkl["url"], url)) force_download = True else: self.namespace = pkl["namespace"] self.documentation = pkl["documentation"] return pkl["services"] soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": "soap11", "http://schemas.xmlsoap.org/wsdl/soap12/": "soap12", } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: s and str((":" in s) and s.split(":")[1] or s) get_namespace_prefix = lambda s: s and str((":" in s) and s.split(":")[0] or None) # always return an unicode object: REVERSE_TYPE_MAP[u"string"] = unicode # Open uri and read xml: xml = fetch(url, self.http, cache, force_download, self.wsdl_basedir) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl["targetNamespace"] self.documentation = unicode(wsdl("documentation", error=False) or "") services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service["name"] if not service_name: continue # empty service? log.debug("Processing service %s" % service_name) serv = services.setdefault(service_name, {"ports": {}}) serv["documentation"] = service["documentation"] or "" for port in service.port: binding_name = get_local_name(port["binding"]) operations[binding_name] = {} address = port("address", ns=soap_uris.values(), error=False) location = address and address["location"] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { "name": binding_name, "service_name": service_name, "location": location, "soap_uri": soap_uri, "soap_ver": soap_ver, } serv["ports"][port["name"]] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding["name"] soap_binding = binding("binding", ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding["transport"] or None port_type_name = get_local_name(binding["type"]) bindings[binding_name].update({"port_type_name": port_type_name, "transport": transport, "operations": {}}) if port_type_name not in port_type_bindings: port_type_bindings[port_type_name] = [] port_type_bindings[port_type_name].append(bindings[binding_name]) for operation in binding.operation: op_name = operation["name"] op = operation("operation", ns=soap_uris.values(), error=False) action = op and op["soapAction"] d = operations[binding_name].setdefault(op_name, {}) bindings[binding_name]["operations"][op_name] = d d.update({"name": op_name}) d["parts"] = {} # input and/or ouput can be not present! input = operation("input", error=False) body = input and input("body", ns=soap_uris.values(), error=False) d["parts"]["input_body"] = body and body["parts"] or None output = operation("output", error=False) body = output and output("body", ns=soap_uris.values(), error=False) d["parts"]["output_body"] = body and body["parts"] or None header = input and input("header", ns=soap_uris.values(), error=False) d["parts"]["input_header"] = header and {"message": header["message"], "part": header["part"]} or None header = output and output("header", ns=soap_uris.values(), error=False) d["parts"]["output_header"] = header and {"message": header["message"], "part": header["part"]} or None if action: d["action"] = action # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get("targetNamespace", self.namespace) imported_schemas = {} # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema( schema, imported_schemas, elements, xsd_uri, self.__soap_server, self.http, cache, force_download, self.wsdl_basedir, ) postprocess_element(elements) for message in wsdl.message: log.debug("Processing message %s" % message["name"]) for part in message("part", error=False) or []: element = {} element_name = part["element"] if not element_name: # some implementations (axis) uses type instead element_name = part["type"] type_ns = get_namespace_prefix(element_name) type_uri = wsdl.get_namespace_uri(type_ns) if type_uri == xsd_uri: element_name = get_local_name(element_name) fn = REVERSE_TYPE_MAP.get(unicode(element_name), None) element = {part["name"]: fn} # emulate a true Element (complexType) messages.setdefault((message["name"], None), {message["name"]: OrderedDict()}).values()[0].update( element ) else: element_name = get_local_name(element_name) fn = elements.get(make_key(element_name, "element")) if not fn: # some axis servers uses complexType for part messages fn = elements.get(make_key(element_name, "complexType")) element = {message["name"]: {part["name"]: fn}} else: element = {element_name: fn} messages[(message["name"], part["name"])] = element for port_type in wsdl.portType: port_type_name = port_type["name"] log.debug("Processing port type %s" % port_type_name) for binding in port_type_bindings[port_type_name]: for operation in port_type.operation: op_name = operation["name"] op = operations[str(binding["name"])][op_name] op["documentation"] = unicode(operation("documentation", error=False) or "") if binding["soap_ver"]: # TODO: separe operation_binding from operation (non SOAP?) if operation("input", error=False): input_msg = get_local_name(operation.input["message"]) input_header = op["parts"].get("input_header") if input_header: header_msg = get_local_name(input_header.get("message")) header_part = get_local_name(input_header.get("part")) # warning: some implementations use a separate message! header = get_message(messages, header_msg or input_msg, header_part) else: header = None # not enought info to search the header message: op["input"] = get_message(messages, input_msg, op["parts"].get("input_body")) op["header"] = header else: op["input"] = None op["header"] = None if operation("output", error=False): output_msg = get_local_name(operation.output["message"]) op["output"] = get_message(messages, output_msg, op["parts"].get("output_body")) else: op["output"] = None # dump the full service/port/operation map # log.debug(pprint.pformat(services)) # Save parsed wsdl (cache) if cache: f = open(filename_pkl, "wb") pkl = { "version": __version__.split(" ")[0], "url": url, "namespace": self.namespace, "documentation": self.documentation, "services": services, } pickle.dump(pkl, f) f.close() return services
def wsdl(self): "Generate Web Service Description v1.1" xml = """<?xml version="1.0"?> <wsdl:definitions name="%(name)s" targetNamespace="%(namespace)s" xmlns:tns="%(namespace)s" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">%(documentation)s</wsdl:documentation> <wsdl:types> <xsd:schema targetNamespace="%(namespace)s" elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> </xsd:schema> </wsdl:types> </wsdl:definitions> """ % { 'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation } wsdl = SimpleXMLElement(xml) for method, (function, returns, args, doc) in self.methods.items(): # create elements: def parse_element(name, values, array=False, complex=False): if not complex: element = wsdl('wsdl:types')('xsd:schema').add_child( 'xsd:element') complex = element.add_child("xsd:complexType") else: complex = wsdl('wsdl:types')('xsd:schema').add_child( 'xsd:complexType') element = complex element['name'] = name if values: items = values elif values is None: items = [('value', None)] else: items = [] if not array and items: all = complex.add_child("xsd:all") elif items: all = complex.add_child("xsd:sequence") for k, v in items: e = all.add_child("xsd:element") e['name'] = k if array: e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"} if v in TYPE_MAP.keys(): t = 'xsd:%s' % TYPE_MAP[v] elif v is None: t = 'xsd:anyType' elif isinstance(v, list): n = "ArrayOf%s%s" % (name, k) l = [] for d in v: l.extend(d.items()) parse_element(n, l, array=True, complex=True) t = "tns:%s" % n elif isinstance(v, dict): n = "%s%s" % (name, k) parse_element(n, v.items(), complex=True) t = "tns:%s" % n e.add_attribute('type', t) parse_element("%s" % method, args and args.items()) parse_element("%sResponse" % method, returns and returns.items()) # create messages: for m, e in ('Input', ''), ('Output', 'Response'): message = wsdl.add_child('wsdl:message') message['name'] = "%s%s" % (method, m) part = message.add_child("wsdl:part") part[:] = { 'name': 'parameters', 'element': 'tns:%s%s' % (method, e) } # create ports portType = wsdl.add_child('wsdl:portType') portType['name'] = "%sPortType" % self.name for method, (function, returns, args, doc) in self.methods.items(): op = portType.add_child('wsdl:operation') op['name'] = method if doc: op.add_child("wsdl:documentation", doc) input = op.add_child("wsdl:input") input['message'] = "tns:%sInput" % method output = op.add_child("wsdl:output") output['message'] = "tns:%sOutput" % method # create bindings binding = wsdl.add_child('wsdl:binding') binding['name'] = "%sBinding" % self.name binding['type'] = "tns:%sPortType" % self.name soapbinding = binding.add_child('soap:binding') soapbinding['style'] = "document" soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http" for method in self.methods.keys(): op = binding.add_child('wsdl:operation') op['name'] = method soapop = op.add_child('soap:operation') soapop['soapAction'] = self.action + method soapop['style'] = 'document' input = op.add_child("wsdl:input") ##input.add_attribute('name', "%sInput" % method) soapbody = input.add_child("soap:body") soapbody["use"] = "literal" output = op.add_child("wsdl:output") ##output.add_attribute('name', "%sOutput" % method) soapbody = output.add_child("soap:body") soapbody["use"] = "literal" service = wsdl.add_child('wsdl:service') service["name"] = "%sService" % self.name service.add_child('wsdl:documentation', text=self.documentation) port = service.add_child('wsdl:port') port["name"] = "%s" % self.name port["binding"] = "tns:%sBinding" % self.name soapaddress = port.add_child('soap:address') soapaddress["location"] = self.location return wsdl.as_xml(pretty=True)
def wsdl(self, url, debug=False): "Parse Web Service Description v1.1" soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s) REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()]) # Open uri and read xml: f = urllib.urlopen(url) xml = f.read() # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service['name'] if debug: print "Processing service", service_name serv = services.setdefault(service_name, {'ports': {}}) serv['documentation'] = service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { 'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] if debug: print "Processing binding", service_name soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation['name'] op = operation('operation', ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations.setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) #if action: #TODO: separe operation_binding from operation d["action"] = action def process_element(element_name, children): if debug: print "Processing element", element_name for tag in children: d = OrderedDict() for e in tag.children(): t = e['type'].split(":") if len(t) > 1: ns, type_name = t else: ns, type_name = xsd_ns, t[0] if ns == xsd_ns: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # complex type, postprocess later fn = elements.setdefault(unicode(type_name), OrderedDict()) e_name = unicode(e['name']) d[e_name] = fn if e['maxOccurs'] == "unbounded": # it's an array... TODO: compound arrays? d.array = True elements.setdefault(element_name, OrderedDict()).update(d) for element in wsdl.types("schema", ns=xsd_uri).children(): if element.get_local_name() in ('element', 'complexType'): element_name = unicode(element['name']) if debug: print "Parsing Element %s: %s" % (element.get_local_name(), element_name) if element.get_local_name() == 'complexType': children = element.children() else: children = element.children() if children: children = children.children() if children: process_element(element_name, children) def postprocess_element(elements): for k, v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v != elements: #TODO: fix recursive elements postprocess_element(v) postprocess_element(elements) for message in wsdl.message: if debug: print "Processing message", message['name'] part = message('part', error=False) element = {} if part: element_name = get_local_name(part['element']) element = {element_name: elements.get(element_name)} messages[message['name']] = element for port_type in wsdl.portType: port_type_name = port_type['name'] if debug: print "Processing port type", port_type_name binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation['name'] op = operations[op_name] op['documentation'] = unicode( operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input['message']) output = get_local_name(operation.output['message']) op['input'] = messages[input] op['output'] = messages[output] if debug: import pprint pprint.pprint(services) return services
class SoapClient(object): "Simple SOAP Client (simil PHP)" def __init__( self, location=None, action=None, namespace=None, cert=None, trace=False, exceptions=True, proxy=None, ns=False, soap_ns=None, wsdl=None, cache=False, cacert=None, sessions=False, soap_server=None, ): self.certssl = cert self.keyssl = None self.location = location # server location (url) self.action = action # SOAP base action self.namespace = namespace # message self.trace = trace # show debug messages self.exceptions = exceptions # lanzar execpiones? (Soap Faults) self.xml_request = self.xml_response = '' if not soap_ns and not ns: self.__soap_ns = 'soap' # 1.1 elif not soap_ns and ns: self.__soap_ns = 'soapenv' # 1.2 else: self.__soap_ns = soap_ns # SOAP Server (special cases like oracle or jbossas6) self.__soap_server = soap_server # SOAP Header support self.__headers = {} # general headers self.__call_headers = None # OrderedDict to be marshalled for RPC Call # check if the Certification Authority Cert is a string and store it if cacert and cacert.startswith("-----BEGIN CERTIFICATE-----"): fd, filename = tempfile.mkstemp() f = os.fdopen(fd, 'w+b', -1) if self.trace: log.info(u"Saving CA certificate to %s" % filename) f.write(cacert) cacert = filename f.close() self.cacert = cacert # Create HTTP wrapper Http = get_Http() self.http = Http(timeout=TIMEOUT, cacert=cacert, proxy=proxy, sessions=sessions) # parse wsdl url self.services = wsdl and self.wsdl_parse( wsdl, debug=trace, cache=cache) self.service_port = None # service port for late binding self.__ns = ns # namespace prefix or False to not use it if not ns: self.__xml = """<?xml version="1.0" encoding="UTF-8"?> <%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:%(soap_ns)s="%(soap_uri)s"> <%(soap_ns)s:Header/> <%(soap_ns)s:Body> <%(method)s xmlns="%(namespace)s"> </%(method)s> </%(soap_ns)s:Body> </%(soap_ns)s:Envelope>""" else: self.__xml = """<?xml version="1.0" encoding="UTF-8"?> <%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(ns)s="%(namespace)s"> <%(soap_ns)s:Header/> <%(soap_ns)s:Body> <%(ns)s:%(method)s> </%(ns)s:%(method)s> </%(soap_ns)s:Body> </%(soap_ns)s:Envelope>""" # parse wsdl url self.services = wsdl and self.wsdl_parse( wsdl, debug=trace, cache=cache) self.service_port = None # service port for late binding def __getattr__(self, attr): "Return a pseudo-method that can be called" if not self.services: # not using WSDL? return lambda self=self, *args, **kwargs: self.call( attr, *args, **kwargs) else: # using WSDL: return lambda *args, **kwargs: self.wsdl_call( attr, *args, **kwargs) def call(self, method, *args, **kwargs): "Prepare xml request and make SOAP call, returning a SimpleXMLElement" #TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns]) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) for param in parameters[0].children(): getattr(request, method).import_node(param) elif parameters: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) elif not self.__soap_server in ('oracle', ) or self.__soap_server in ( 'jbossas6', ): # JBossAS-6 requires no empty method parameters! delattr(request( "Body", ns=soap_namespaces.values(), ), method) # construct header and parameters (if not wsdl given) except wsse if self.__headers and not self.services: self.__call_headers = dict([(k, v) for k, v in self.__headers.items() if not k.startswith("wsse:")]) # always extract WS Security header and send it if 'wsse:Security' in self.__headers: #TODO: namespaces too hardwired, clean-up... header = request( 'Header', ns=soap_namespaces.values(), ) k = 'wsse:Security' v = self.__headers[k] header.marshall(k, v, ns=False, add_children_ns=False) header( k )['xmlns:wsse'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' #<wsse:UsernameToken xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> if self.__call_headers: header = request( 'Header', ns=soap_namespaces.values(), ) for k, v in self.__call_headers.items(): ##if not self.__ns: ## header['xmlns'] header.marshall(k, v, ns=self.__ns, add_children_ns=False) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace) if self.exceptions and response( "Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response def send(self, method, xml): "Send SOAP request using HTTP" if self.location == 'test': return location = "%s" % self.location #?op=%s" % (self.location, method) if self.services: soap_action = self.action else: soap_action = self.action + method headers = { 'Content-type': 'text/xml; charset="UTF-8"', 'Content-length': str(len(xml)), "SOAPAction": "\"%s\"" % (soap_action) } log.info("POST %s" % location) if self.trace: print "-" * 80 print "POST %s" % location print '\n'.join(["%s: %s" % (k, v) for k, v in headers.items()]) print u"\n%s" % xml.decode("utf8", "ignore") response, content = self.http.request(location, "POST", body=xml, headers=headers) self.response = response self.content = content if self.trace: print '\n'.join(["%s: %s" % (k, v) for k, v in response.items()]) print content #.decode("utf8","ignore") print "=" * 80 return content def get_operation(self, method): # try to find operation in wsdl file soap_ver = self.__soap_ns == 'soap12' and 'soap12' or 'soap11' if not self.service_port: for service_name, service in self.services.items(): for port_name, port in [ port for port in service['ports'].items() ]: if port['soap_ver'] == soap_ver: self.service_port = service_name, port_name break else: raise RuntimeError("Cannot determine service in WSDL: " "SOAP version: %s" % soap_ver) else: port = self.services[self.service_port[0]]['ports'][ self.service_port[1]] self.location = port['location'] operation = port['operations'].get(unicode(method)) if not operation: raise RuntimeError("Operation %s not found in WSDL: " "Service/Port Type: %s" % (method, self.service_port)) return operation def wsdl_call(self, method, *args, **kwargs): "Pre and post process SOAP call, input and output parameters using WSDL" soap_uri = soap_namespaces[self.__soap_ns] operation = self.get_operation(method) # get i/o type declarations: input = operation['input'] output = operation['output'] header = operation.get('header') if 'action' in operation: self.action = operation['action'] # sort parameters (same order as xsd:sequence) def sort_dict(od, d): if isinstance(od, dict): ret = OrderedDict() for k in od.keys(): v = d.get(k) # don't append null tags! if v is not None: if isinstance(v, dict): v = sort_dict(od[k], v) elif isinstance(v, list): v = [sort_dict(od[k][0], v1) for v1 in v] ret[str(k)] = v return ret else: return d # construct header and parameters if header: self.__call_headers = sort_dict(header, self.__headers) if input and args: # convert positional parameters to named parameters: d = [(k, arg) for k, arg in zip(input.values()[0].keys(), args)] kwargs.update(dict(d)) if input and kwargs: params = sort_dict(input.values()[0], kwargs).items() if self.__soap_server == "axis": # use the operation name method = method else: # use the message (element) name method = input.keys()[0] #elif not input: #TODO: no message! (see wsmtxca.dummy) else: params = kwargs and kwargs.items() # call remote procedure response = self.call(method, *params) # parse results: resp = response('Body', ns=soap_uri).children().unmarshall(output) return resp and resp.values()[0] # pass Response tag children def help(self, method): "Return operation documentation and invocation/returned value example" operation = self.get_operation(method) input = operation['input'] input = input and input.values() and input.values()[0] if isinstance(input, dict): input = ", ".join("%s=%s" % (k, repr(v)) for k, v in input.items()) elif isinstance(input, list): input = repr(input) output = operation['output'].values()[0] headers = operation.get('headers') or None return u"%s(%s)\n -> %s:\n\n%s\nHeaders: %s" % ( method, input or "", output and output or "", operation.get("documentation", ""), headers, ) def wsdl_parse(self, url, debug=False, cache=False): "Parse Web Service Description v1.1" log.debug("wsdl url: %s" % url) # Try to load a previously parsed wsdl: force_download = False if cache: # make md5 hash of the url for caching... filename_pkl = "%s.pkl" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename_pkl = os.path.join(cache, filename_pkl) if os.path.exists(filename_pkl): log.debug("Unpickle file %s" % (filename_pkl, )) f = open(filename_pkl, "r") pkl = pickle.load(f) f.close() # sanity check: if pkl['version'][:-1] != __version__.split( " ")[0][:-1] or pkl['url'] != url: import warnings warnings.warn( 'version or url mismatch! discarding cached wsdl', RuntimeWarning) if debug: log.debug('Version: %s %s' % (pkl['version'], __version__)) log.debug('URL: %s %s' % (pkl['url'], url)) force_download = True else: self.namespace = pkl['namespace'] self.documentation = pkl['documentation'] return pkl['services'] soap_ns = { "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', } wsdl_uri = "http://schemas.xmlsoap.org/wsdl/" xsd_uri = "http://www.w3.org/2001/XMLSchema" xsi_uri = "http://www.w3.org/2001/XMLSchema-instance" get_local_name = lambda s: s and str( (':' in s) and s.split(':')[1] or s) get_namespace_prefix = lambda s: s and str( (':' in s) and s.split(':')[0] or None) # always return an unicode object: REVERSE_TYPE_MAP[u'string'] = unicode def fetch(url): "Download a document from a URL, save it locally if cache enabled" # check / append a valid schema if not given: o = urlparse(url) if not o.scheme in ('http', 'https', 'file'): for scheme in ('http', 'https', 'file'): try: if not url.startswith("/") and scheme in ('http', 'https'): tmp_url = "%s://%s" % (scheme, url) else: tmp_url = "%s:%s" % (scheme, url) if debug: log.debug("Scheme not found, trying %s" % scheme) return fetch(tmp_url) except Exception, e: log.error(e) raise RuntimeError("No scheme given for url: %s" % url) # make md5 hash of the url for caching... filename = "%s.xml" % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename = os.path.join(cache, filename) if cache and os.path.exists(filename) and not force_download: log.info("Reading file %s" % (filename, )) f = open(filename, "r") xml = f.read() f.close() else: if o.scheme == 'file': log.info("Fetching url %s using urllib2" % (url, )) f = urllib2.urlopen(url) xml = f.read() else: log.info("GET %s using %s" % (url, self.http._wrapper_version)) response, xml = self.http.request(url, "GET", None, {}) if cache: log.info("Writing file %s" % (filename, )) if not os.path.isdir(cache): os.makedirs(cache) f = open(filename, "w") f.write(xml) f.close() return xml # Open uri and read xml: xml = fetch(url) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) # detect soap prefix and uri (xmlns attributes of <definitions>) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in soap_ns and k.startswith("xmlns:"): soap_uris[get_local_name(k)] = v if v == xsd_uri and k.startswith("xmlns:"): xsd_ns = get_local_name(k) # Extract useful data: self.namespace = wsdl['targetNamespace'] self.documentation = unicode(wsdl('documentation', error=False) or '') services = {} bindings = {} # binding_name: binding operations = {} # operation_name: operation port_type_bindings = {} # port_type_name: binding messages = {} # message: element elements = {} # element: type def for service in wsdl.service: service_name = service['name'] if not service_name: continue # empty service? if debug: log.debug("Processing service %s" % service_name) serv = services.setdefault(service_name, {'ports': {}}) serv['documentation'] = service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) address = port('address', ns=soap_uris.values(), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and soap_ns.get(soap_uri) bindings[binding_name] = { 'service_name': service_name, 'location': location, 'soap_uri': soap_uri, 'soap_ver': soap_ver, } serv['ports'][port['name']] = bindings[binding_name] for binding in wsdl.binding: binding_name = binding['name'] if debug: log.debug("Processing binding %s" % service_name) soap_binding = binding('binding', ns=soap_uris.values(), error=False) transport = soap_binding and soap_binding['transport'] or None port_type_name = get_local_name(binding['type']) bindings[binding_name].update({ 'port_type_name': port_type_name, 'transport': transport, 'operations': {}, }) port_type_bindings[port_type_name] = bindings[binding_name] for operation in binding.operation: op_name = operation['name'] op = operation('operation', ns=soap_uris.values(), error=False) action = op and op['soapAction'] d = operations.setdefault(op_name, {}) bindings[binding_name]['operations'][op_name] = d d.update({'name': op_name}) d['parts'] = {} body = operation.input('body', ns=soap_uris.values(), error=False) d['parts']['input_body'] = body and body['parts'] or None body = operation.output('body', ns=soap_uris.values(), error=False) d['parts']['output_body'] = body and body['parts'] or None header = operation.input('header', ns=soap_uris.values(), error=False) d['parts']['input_header'] = header and header['part'] or None headers = operation.output('header', ns=soap_uris.values(), error=False) d['parts']['output_header'] = header and header['part'] or None #if action: #TODO: separe operation_binding from operation if action: d["action"] = action def make_key(element_name, element_type): "return a suitable key for elements" # only distinguish 'element' vs other types if element_type in ('complexType', 'simpleType'): eltype = 'complexType' else: eltype = element_type if eltype not in ('element', 'complexType', 'simpleType'): raise RuntimeError("Unknown element type %s = %s" % (unicode(element_name), eltype)) return (unicode(element_name), eltype) #TODO: cleanup element/schema/types parsing: def process_element(element_name, node, element_type): "Parse and define simple element types" if debug: log.debug("Processing element %s %s" % (element_name, element_type)) for tag in node: if tag.get_local_name() in ("annotation", "documentation"): continue elif tag.get_local_name() in ('element', 'restriction'): if debug: log.debug("%s has not children! %s" % (element_name, tag)) children = tag # element "alias"? alias = True elif tag.children(): children = tag.children() alias = False else: if debug: log.debug("%s has not children! %s" % (element_name, tag)) continue #TODO: abstract? d = OrderedDict() for e in children: t = e['type'] if not t: t = e['base'] # complexContent (extension)! if not t: t = 'anyType' # no type given! t = t.split(":") if len(t) > 1: ns, type_name = t else: ns, type_name = None, t[0] if element_name == type_name: pass ## warning with infinite recursion uri = ns and e.get_namespace_uri(ns) or xsd_uri if uri == xsd_uri: # look for the type, None == any fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) else: # simple / complex type, postprocess later fn = elements.setdefault( make_key(type_name, "complexType"), OrderedDict()) if e['name'] is not None and not alias: e_name = unicode(e['name']) d[e_name] = fn else: if debug: log.debug( "complexConent/simpleType/element %s = %s" % (element_name, type_name)) d[None] = fn if e['maxOccurs'] == "unbounded" or (ns == 'SOAP-ENC' and type_name == 'Array'): # it's an array... TODO: compound arrays? d.array = True if e is not None and e.get_local_name( ) == 'extension' and e.children(): # extend base element: process_element(element_name, e.children(), element_type) elements.setdefault(make_key(element_name, element_type), OrderedDict()).update(d) # check axis2 namespace at schema types attributes self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get( 'targetNamespace', self.namespace) imported_schemas = {} def preprocess_schema(schema): "Find schema elements and complex types" for element in schema.children(): if element.get_local_name() in ('import', ): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: if debug: log.debug("Schema location not provided for %s!" % (schema_namespace, )) continue if schema_location in imported_schemas: if debug: log.debug("Schema %s already imported!" % (schema_location, )) continue imported_schemas[schema_location] = schema_namespace if debug: print "Importing schema %s from %s" % ( schema_namespace, schema_location) # Open uri and read xml: xml = fetch(schema_location) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema) element_type = element.get_local_name() if element_type in ('element', 'complexType', "simpleType"): element_name = unicode(element['name']) if debug: log.debug("Parsing Element %s: %s" % (element_type, element_name)) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element("restriction", ns=xsd_uri) elif element.get_local_name( ) == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(element_name, children, element_type) def postprocess_element(elements): "Fix unresolved references (elements referenced before its definition, thanks .net)" for k, v in elements.items(): if isinstance(v, OrderedDict): if v.array: elements[k] = [v] # convert arrays to python lists if v != elements: #TODO: fix recursive elements postprocess_element(v) if None in v and v[None]: # extension base? if isinstance(v[None], dict): for i, kk in enumerate(v[None]): # extend base -keep orginal order- elements[k].insert(kk, v[None][kk], i) del v[None] else: # "alias", just replace if debug: log.debug("Replacing %s = %s" % (k, v[None])) elements[k] = v[None] #break if isinstance(v, list): for n in v: # recurse list postprocess_element(n) # process current wsdl schema: for schema in wsdl.types("schema", ns=xsd_uri): preprocess_schema(schema) postprocess_element(elements) for message in wsdl.message: if debug: log.debug("Processing message %s" % message['name']) for part in message('part', error=False) or []: element = {} element_name = part['element'] if not element_name: # some implementations (axis) uses type instead element_name = part['type'] type_ns = get_namespace_prefix(element_name) type_uri = wsdl.get_namespace_uri(type_ns) if type_uri == xsd_uri: element_name = get_local_name(element_name) fn = REVERSE_TYPE_MAP.get(unicode(element_name), None) element = {part['name']: fn} # emulate a true Element (complexType) messages.setdefault((message['name'], None), { message['name']: OrderedDict() }).values()[0].update(element) else: element_name = get_local_name(element_name) fn = elements.get(make_key(element_name, 'element')) if not fn: # some axis servers uses complexType for part messages fn = elements.get(make_key(element_name, 'complexType')) element = {message['name']: {part['name']: fn}} else: element = {element_name: fn} messages[(message['name'], part['name'])] = element def get_message(message_name, part_name): if part_name: # get the specific part of the message: return messages.get((message_name, part_name)) else: # get the first part for the specified message: for (message_name_key, part_name_key), message in messages.items(): if message_name_key == message_name: return message for port_type in wsdl.portType: port_type_name = port_type['name'] if debug: log.debug("Processing port type %s" % port_type_name) binding = port_type_bindings[port_type_name] for operation in port_type.operation: op_name = operation['name'] op = operations[op_name] op['documentation'] = unicode( operation('documentation', error=False) or '') if binding['soap_ver']: #TODO: separe operation_binding from operation (non SOAP?) input = get_local_name(operation.input['message']) output = get_local_name(operation.output['message']) header = get_local_name(op['parts'].get('input_header')) op['input'] = get_message(input, op['parts'].get('input_body')) op['output'] = get_message(output, op['parts'].get('output_body')) op['header'] = get_message(input, op['parts'].get('input_header')) if debug: import pprint log.debug(pprint.pformat(services)) # Save parsed wsdl (cache) if cache: f = open(filename_pkl, "wb") pkl = { 'version': __version__.split(" ")[0], 'url': url, 'namespace': self.namespace, 'documentation': self.documentation, 'services': services, } pickle.dump(pkl, f) f.close() return services
class SoapDispatcher(object): "Simple Dispatcher for SOAP Server" def __init__(self, name, documentation='', action='', location='', namespace=None, prefix=False, soap_uri="http://schemas.xmlsoap.org/soap/envelope/", soap_ns='soap', **kwargs): self.methods = {} self.name = name self.documentation = documentation self.action = action # base SoapAction self.location = location self.namespace = namespace # targetNamespace self.prefix = prefix self.soap_ns = soap_ns self.soap_uri = soap_uri def register_function(self, name, fn, returns=None, args=None, doc=None): self.methods[name] = fn, returns, args, doc or getattr( fn, "__doc__", "") def dispatch(self, xml, action=None): "Receive and proccess SOAP call" # default values: prefix = self.prefix ret = fault = None soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ( "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env", ): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) if action: # method name = action name = action[len(self.action) + 1:-1] prefix = self.prefix if not action or not name: # method name = input message name name = method.get_local_name() prefix = method.get_prefix() if DEBUG: print "dispatch method", name function, returns_types, args_types, doc = self.methods[name] # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request': method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) if DEBUG: print ret except Exception, e: import sys etype, evalue, etb = sys.exc_info() if DEBUG: import traceback detail = ''.join(traceback.format_exception( etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault = { 'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': unicode(evalue), 'detail': detail } # build response message if not prefix: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" else: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(prefix)s="%(namespace)s"/>""" xml = xml % { 'namespace': self.namespace, 'prefix': prefix, 'soap_ns': soap_ns, 'soap_uri': soap_uri } response = SimpleXMLElement(xml, namespace=self.namespace, prefix=prefix) response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" body = response.add_child("%s:Body" % soap_ns, ns=False) if fault: # generate a Soap Fault (with the python exception) body.marshall("%s:Fault" % soap_ns, fault, ns=False) else: # return normal value res = body.add_child("%sResponse" % name, ns=prefix) if not prefix: res['xmlns'] = self.namespace # add target namespace # serialize returned values (response) if type definition available if returns_types: if not isinstance(ret, dict): res.marshall( returns_types.keys()[0], ret, ) else: for k, v in ret.items(): res.marshall(k, v) elif returns_types is None: # merge xmlelement returned res.import_node(ret) return response.as_xml()
def call(self, method, *args, **kwargs): """Prepare xml request and make SOAP call, returning a SimpleXMLElement. If a keyword argument called "headers" is passed with a value of a SimpleXMLElement object, then these headers will be inserted into the request. """ # TODO: method != input_message # Basic SOAP request: xml = self.__xml % dict( method=method, namespace=self.namespace, ns=self.__ns, soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns], ) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) try: request_headers = kwargs.pop("headers") except KeyError: request_headers = None # serialize parameters if kwargs: parameters = kwargs.items() else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): # merge xmlelement parameter ("raw" - already marshalled) if parameters[0].children() is not None: for param in parameters[0].children(): getattr(request, method).import_node(param) elif parameters: # marshall parameters: for k, v in parameters: # dict: tag=valor getattr(request, method).marshall(k, v) elif not self.__soap_server in ("oracle",) or self.__soap_server in ("jbossas6",): # JBossAS-6 requires no empty method parameters! delattr(request("Body", ns=soap_namespaces.values()), method) # construct header and parameters (if not wsdl given) except wsse if self.__headers and not self.services: self.__call_headers = dict([(k, v) for k, v in self.__headers.items() if not k.startswith("wsse:")]) # always extract WS Security header and send it if "wsse:Security" in self.__headers: # TODO: namespaces too hardwired, clean-up... header = request("Header", ns=soap_namespaces.values()) k = "wsse:Security" v = self.__headers[k] header.marshall(k, v, ns=False, add_children_ns=False) header(k)[ "xmlns:wsse" ] = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" # <wsse:UsernameToken xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> if self.__call_headers: header = request("Header", ns=soap_namespaces.values()) for k, v in self.__call_headers.items(): ##if not self.__ns: ## header['xmlns'] if isinstance(v, SimpleXMLElement): # allows a SimpleXMLElement to be constructed and inserted # rather than a dictionary. marshall doesn't allow ns: prefixes # in dict key names header.import_node(v) else: header.marshall(k, v, ns=self.__ns, add_children_ns=False) if request_headers: header = request("Header", ns=soap_namespaces.values()) for subheader in request_headers.children(): header.import_node(subheader) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace, jetty=self.__soap_server in ("jetty",)) if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) return response