1 from __future__ import nested_scopes
4 ################################################################################
6 # SOAPpy - Cayce Ullman (cayce@actzero.com)
7 # Brian Matthews (blm@actzero.com)
8 # Gregory Warnes (Gregory.R.Warnes@Pfizer.com)
9 # Christopher Blunck (blunck@gst.com)
11 ################################################################################
12 # Copyright (c) 2003, Pfizer
13 # Copyright (c) 2001, Cayce Ullman.
14 # Copyright (c) 2001, Brian Matthews.
16 # All rights reserved.
18 # Redistribution and use in source and binary forms, with or without
19 # modification, are permitted provided that the following conditions are met:
20 # Redistributions of source code must retain the above copyright notice, this
21 # list of conditions and the following disclaimer.
23 # Redistributions in binary form must reproduce the above copyright notice,
24 # this list of conditions and the following disclaimer in the documentation
25 # and/or other materials provided with the distribution.
27 # Neither the name of actzero, inc. nor the names of its contributors may
28 # be used to endorse or promote products derived from this software without
29 # specific prior written permission.
31 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
35 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 ################################################################################
45 ident = '$Id: Server.py 1468 2008-05-24 01:55:33Z warnes $'
46 from version import __version__
57 from Parser import parseSOAPRPC
58 from Config import Config
59 from Types import faultType, voidType, simplify
61 from SOAPBuilder import buildSOAP
62 from Utilities import debugHeader, debugFooter
64 try: from M2Crypto import SSL
67 ident = '$Id: Server.py 1468 2008-05-24 01:55:33Z warnes $'
69 from version import __version__
71 ################################################################################
72 # Call context dictionary
73 ################################################################################
79 return _contexts[thread.get_ident()]
81 ################################################################################
83 ################################################################################
85 # Method Signature class for adding extra info to registered funcs, right now
86 # used just to indicate it should be called with keywords, instead of ordered
89 def __init__(self, func, keywords=0, context=0):
91 self.keywords = keywords
92 self.context = context
93 self.__name__ = func.__name__
95 def __call__(self, *args, **kw):
96 return apply(self.func,args,kw)
99 def __init__(self, header, body, attrs, xmldata, connection, httpheaders,
105 self.xmldata = xmldata
106 self.connection = connection
107 self.httpheaders= httpheaders
108 self.soapaction = soapaction
110 # A class to describe how header messages are handled
112 # Initially fail out if there are any problems.
113 def __init__(self, header, attrs):
114 for i in header.__dict__.keys():
118 d = getattr(header, i)
121 fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')])
126 raise faultType, ("%s:MustUnderstand" % NS.ENV_T,
127 "Required Header Misunderstood",
130 ################################################################################
132 ################################################################################
133 class SOAPServerBase:
135 def get_request(self):
136 sock, addr = SocketServer.TCPServer.get_request(self)
139 sock = SSL.Connection(self.ssl_context, sock)
140 sock._setup_ssl(addr)
141 if sock.accept_ssl() != 1:
142 raise socket.error, "Couldn't accept SSL connection"
146 def registerObject(self, object, namespace = '', path = ''):
147 if namespace == '' and path == '': namespace = self.namespace
148 if namespace == '' and path != '':
149 namespace = path.replace("/", ":")
150 if namespace[0] == ":": namespace = namespace[1:]
151 self.objmap[namespace] = object
153 def registerFunction(self, function, namespace = '', funcName = None,
155 if not funcName : funcName = function.__name__
156 if namespace == '' and path == '': namespace = self.namespace
157 if namespace == '' and path != '':
158 namespace = path.replace("/", ":")
159 if namespace[0] == ":": namespace = namespace[1:]
160 if self.funcmap.has_key(namespace):
161 self.funcmap[namespace][funcName] = function
163 self.funcmap[namespace] = {funcName : function}
165 def registerKWObject(self, object, namespace = '', path = ''):
166 if namespace == '' and path == '': namespace = self.namespace
167 if namespace == '' and path != '':
168 namespace = path.replace("/", ":")
169 if namespace[0] == ":": namespace = namespace[1:]
170 for i in dir(object.__class__):
171 if i[0] != "_" and callable(getattr(object, i)):
172 self.registerKWFunction(getattr(object,i), namespace)
174 # convenience - wraps your func for you.
175 def registerKWFunction(self, function, namespace = '', funcName = None,
177 if namespace == '' and path == '': namespace = self.namespace
178 if namespace == '' and path != '':
179 namespace = path.replace("/", ":")
180 if namespace[0] == ":": namespace = namespace[1:]
181 self.registerFunction(MethodSig(function,keywords=1), namespace,
184 def unregisterObject(self, object, namespace = '', path = ''):
185 if namespace == '' and path == '': namespace = self.namespace
186 if namespace == '' and path != '':
187 namespace = path.replace("/", ":")
188 if namespace[0] == ":": namespace = namespace[1:]
190 del self.objmap[namespace]
192 class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
193 def version_string(self):
194 return '<a href="http://pywebsvcs.sf.net">' + \
195 'SOAPpy ' + __version__ + '</a> (Python ' + \
196 sys.version.split()[0] + ')'
198 def date_time_string(self):
199 self.__last_date_time_string = \
200 BaseHTTPServer.BaseHTTPRequestHandler.\
201 date_time_string(self)
203 return self.__last_date_time_string
210 if self.server.config.dumpHeadersIn:
211 s = 'Incoming HTTP headers'
213 print self.raw_requestline.strip()
214 print "\n".join(map (lambda x: x.strip(),
215 self.headers.headers))
218 data = self.rfile.read(int(self.headers["Content-length"]))
220 if self.server.config.dumpSOAPIn:
228 (r, header, body, attrs) = \
229 parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
235 if Config.simplify_objects:
236 args = simplify(args)
239 # Handle mixed named and unnamed arguments by assuming
240 # that all arguments with names of the form "v[0-9]+"
241 # are unnamed and should be passed in numeric order,
242 # other arguments are named and should be passed using
245 # This is a non-standard exension to the SOAP protocol,
246 # but is supported by Apache AXIS.
248 # It is enabled by default. To disable, set
249 # Config.specialArgs to False.
255 if Config.specialArgs:
257 for (k,v) in kw.items():
264 named_args[str(k)] = v
267 named_args[str(k)] = v
269 # We have to decide namespace precedence
270 # I'm happy with the following scenario
271 # if r._ns is specified use it, if not check for
272 # a path, if it's specified convert it and use it as the
273 # namespace. If both are specified, use r._ns.
277 if len(self.path) > 1 and not ns:
278 ns = self.path.replace("/", ":")
279 if ns[0] == ":": ns = ns[1:]
281 # authorization method
284 keylist = ordered_args.keys()
287 # create list in proper order w/o names
288 tmp = map( lambda x: ordered_args[x], keylist)
291 #print '<-> Argument Matching Yielded:'
292 #print '<-> Ordered Arguments:' + str(ordered_args)
293 #print '<-> Named Arguments :' + str(named_args)
299 nsmethod = "%s:%s" % (ns, method)
304 # First look for registered functions
305 if self.server.funcmap.has_key(ns) and \
306 self.server.funcmap[ns].has_key(method):
307 f = self.server.funcmap[ns][method]
309 # look for the authorization method
310 if self.server.config.authMethod != None:
311 authmethod = self.server.config.authMethod
312 if self.server.funcmap.has_key(ns) and \
313 self.server.funcmap[ns].has_key(authmethod):
314 a = self.server.funcmap[ns][authmethod]
316 # Now look at registered objects
317 # Check for nested attributes. This works even if
318 # there are none, because the split will return
320 f = self.server.objmap[ns]
322 # Look for the authorization method
323 if self.server.config.authMethod != None:
324 authmethod = self.server.config.authMethod
325 if hasattr(f, authmethod):
326 a = getattr(f, authmethod)
328 # then continue looking for the method
329 l = method.split(".")
333 info = sys.exc_info()
335 resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
337 "%s : %s %s %s" % (nsmethod,
341 encoding = self.server.encoding,
342 config = self.server.config)
349 x = HeaderHandler(header, attrs)
353 # call context book keeping
354 # We're stuffing the method into the soapaction if there
355 # isn't one, someday, we'll set that on the client
356 # and it won't be necessary here
357 # for now we're doing both
359 if "SOAPAction".lower() not in self.headers.keys() or \
360 self.headers["SOAPAction"] == "\"\"":
361 self.headers["SOAPAction"] = method
363 thread_id = thread.get_ident()
364 _contexts[thread_id] = SOAPContext(header, body,
368 self.headers["SOAPAction"])
370 # Do an authorization check
372 if not apply(a, (), {"_SOAPContext" :
373 _contexts[thread_id] }):
374 raise faultType("%s:Server" % NS.ENV_T,
375 "Authorization failed.",
378 # If it's wrapped, some special action may be needed
379 if isinstance(f, MethodSig):
382 if f.context: # retrieve context object
383 c = _contexts[thread_id]
385 if Config.specialArgs:
387 named_args["_SOAPContext"] = c
388 fr = apply(f, ordered_args, named_args)
390 # This is lame, but have to de-unicode
395 for (k, v) in kw.items():
398 strkw["_SOAPContext"] = c
399 fr = apply(f, (), strkw)
401 fr = apply(f, args, {'_SOAPContext':c})
403 fr = apply(f, args, {})
406 if Config.specialArgs:
407 fr = apply(f, ordered_args, named_args)
409 fr = apply(f, args, {})
412 if type(fr) == type(self) and \
413 isinstance(fr, voidType):
414 resp = buildSOAP(kw = {'%sResponse' % method: fr},
415 encoding = self.server.encoding,
416 config = self.server.config)
418 resp = buildSOAP(kw =
419 {'%sResponse' % method: {'Result': fr}},
420 encoding = self.server.encoding,
421 config = self.server.config)
424 if _contexts.has_key(thread_id):
425 del _contexts[thread_id]
429 info = sys.exc_info()
432 if self.server.config.dumpFaultInfo:
433 s = 'Method %s exception' % nsmethod
435 traceback.print_exception(info[0], info[1],
439 if isinstance(e, faultType):
442 f = faultType("%s:Server" % NS.ENV_T,
446 if self.server.config.returnFaultInfo:
447 f._setDetail("".join(traceback.format_exception(
448 info[0], info[1], info[2])))
449 elif not hasattr(f, 'detail'):
450 f._setDetail("%s %s" % (info[0], info[1]))
454 resp = buildSOAP(f, encoding = self.server.encoding,
455 config = self.server.config)
461 info = sys.exc_info()
463 if self.server.config.dumpFaultInfo:
464 s = 'Received fault exception'
466 traceback.print_exception(info[0], info[1],
470 if self.server.config.returnFaultInfo:
471 e._setDetail("".join(traceback.format_exception(
472 info[0], info[1], info[2])))
473 elif not hasattr(e, 'detail'):
474 e._setDetail("%s %s" % (info[0], info[1]))
478 resp = buildSOAP(e, encoding = self.server.encoding,
479 config = self.server.config)
482 # internal error, report as HTTP server error
484 if self.server.config.dumpFaultInfo:
485 s = 'Internal exception %s' % e
488 info = sys.exc_info()
490 traceback.print_exception(info[0], info[1], info[2])
496 self.send_response(500)
499 if self.server.config.dumpHeadersOut and \
500 self.request_version != 'HTTP/0.9':
501 s = 'Outgoing HTTP headers'
503 if self.responses.has_key(status):
504 s = ' ' + self.responses[status][0]
507 print "%s %d%s" % (self.protocol_version, 500, s)
508 print "Server:", self.version_string()
509 print "Date:", self.__last_date_time_string
512 # got a valid SOAP response
513 self.send_response(status)
516 if self.server.encoding != None:
517 t += '; charset=%s' % self.server.encoding
518 self.send_header("Content-type", t)
519 self.send_header("Content-length", str(len(resp)))
522 if self.server.config.dumpHeadersOut and \
523 self.request_version != 'HTTP/0.9':
524 s = 'Outgoing HTTP headers'
526 if self.responses.has_key(status):
527 s = ' ' + self.responses[status][0]
530 print "%s %d%s" % (self.protocol_version, status, s)
531 print "Server:", self.version_string()
532 print "Date:", self.__last_date_time_string
533 print "Content-type:", t
534 print "Content-length:", len(resp)
537 if self.server.config.dumpSOAPOut:
545 self.wfile.write(resp)
548 # We should be able to shut down both a regular and an SSL
549 # connection, but under Python 2.1, calling shutdown on an
550 # SSL connections drops the output, so this work-around.
551 # This should be investigated more someday.
553 if self.server.config.SSLserver and \
554 isinstance(self.connection, SSL.Connection):
555 self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN |
556 SSL.SSL_RECEIVED_SHUTDOWN)
558 self.connection.shutdown(1)
562 #print 'command ', self.command
563 #print 'path ', self.path
564 #print 'request_version', self.request_version
566 #print ' type ', self.headers.type
567 #print ' maintype', self.headers.maintype
568 #print ' subtype ', self.headers.subtype
569 #print ' params ', self.headers.plist
571 path = self.path.lower()
572 if path.endswith('wsdl'):
574 function = namespace = None
575 if self.server.funcmap.has_key(namespace) \
576 and self.server.funcmap[namespace].has_key(method):
577 function = self.server.funcmap[namespace][method]
579 if namespace in self.server.objmap.keys():
580 function = self.server.objmap[namespace]
581 l = method.split(".")
583 function = getattr(function, i)
586 self.send_response(200)
587 self.send_header("Content-type", 'text/plain')
589 response = apply(function, ())
590 self.wfile.write(str(response))
594 self.send_response(200)
595 self.send_header("Content-type", 'text/html')
597 self.wfile.write('''\
606 This server supports HTTP GET requests only for the the purpose of
607 obtaining Web Services Description Language (WSDL) for a specific
610 Either you requested an URL that does not end in "wsdl" or this
611 server does not implement a wsdl method.
618 def log_message(self, format, *args):
620 BaseHTTPServer.BaseHTTPRequestHandler.\
621 log_message (self, format, *args)
625 class SOAPServer(SOAPServerBase, SocketServer.TCPServer):
627 def __init__(self, addr = ('localhost', 8000),
628 RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
629 config = Config, namespace = None, ssl_context = None):
631 # Test the encoding, raising an exception if it's not known
635 if ssl_context != None and not config.SSLserver:
636 raise AttributeError, \
637 "SSL server not supported by this Python installation"
639 self.namespace = namespace
642 self.ssl_context = ssl_context
643 self.encoding = encoding
647 self.allow_reuse_address= 1
649 SocketServer.TCPServer.__init__(self, addr, RequestHandler)
652 class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer):
654 def __init__(self, addr = ('localhost', 8000),
655 RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
656 config = Config, namespace = None, ssl_context = None):
658 # Test the encoding, raising an exception if it's not known
662 if ssl_context != None and not config.SSLserver:
663 raise AttributeError, \
664 "SSL server not supported by this Python installation"
666 self.namespace = namespace
669 self.ssl_context = ssl_context
670 self.encoding = encoding
674 self.allow_reuse_address= 1
676 SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler)
678 # only define class if Unix domain sockets are available
679 if hasattr(socket, "AF_UNIX"):
681 class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer):
683 def __init__(self, addr = 8000,
684 RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
685 config = Config, namespace = None, ssl_context = None):
687 # Test the encoding, raising an exception if it's not known
691 if ssl_context != None and not config.SSLserver:
692 raise AttributeError, \
693 "SSL server not supported by this Python installation"
695 self.namespace = namespace
698 self.ssl_context = ssl_context
699 self.encoding = encoding
703 self.allow_reuse_address= 1
705 SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler)