summaryrefslogtreecommitdiff
path: root/indra/lib/python/indra/ipc
diff options
context:
space:
mode:
authorAaron Brashears <aaronb@lindenlab.com>2007-07-16 20:29:28 +0000
committerAaron Brashears <aaronb@lindenlab.com>2007-07-16 20:29:28 +0000
commit7964c6f7a5b622d698f7d471690b29122966b1b2 (patch)
tree78a708bc316db2e2ff3047089a3edf8020c4d7cb /indra/lib/python/indra/ipc
parent88d7d45316186af931285b7f03d2a78cd04f888f (diff)
Result of svn merge -r65183:65337 svn+ssh://svn/svn/linden/branches/python-shuffle into release. Also includes untabification of many python files.
Diffstat (limited to 'indra/lib/python/indra/ipc')
-rw-r--r--indra/lib/python/indra/ipc/__init__.py7
-rw-r--r--indra/lib/python/indra/ipc/compatibility.py103
-rw-r--r--indra/lib/python/indra/ipc/httputil.py94
-rw-r--r--indra/lib/python/indra/ipc/llmessage.py353
-rw-r--r--indra/lib/python/indra/ipc/llsdhttp.py257
-rw-r--r--indra/lib/python/indra/ipc/russ.py123
-rw-r--r--indra/lib/python/indra/ipc/servicebuilder.py52
-rw-r--r--indra/lib/python/indra/ipc/tokenstream.py134
-rw-r--r--indra/lib/python/indra/ipc/xml_rpc.py253
9 files changed, 1376 insertions, 0 deletions
diff --git a/indra/lib/python/indra/ipc/__init__.py b/indra/lib/python/indra/ipc/__init__.py
new file mode 100644
index 0000000000..6576513f9d
--- /dev/null
+++ b/indra/lib/python/indra/ipc/__init__.py
@@ -0,0 +1,7 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra ipc module.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
diff --git a/indra/lib/python/indra/ipc/compatibility.py b/indra/lib/python/indra/ipc/compatibility.py
new file mode 100644
index 0000000000..a430e46ab4
--- /dev/null
+++ b/indra/lib/python/indra/ipc/compatibility.py
@@ -0,0 +1,103 @@
+"""\
+@file compatibility.py
+@brief Classes that manage compatibility states.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+
+"""Compatibility combination table:
+
+ I M O N S
+ -- -- -- -- --
+I: I I I I I
+M: I M M M M
+O: I M O M O
+N: I M M N N
+S: I M O N S
+
+"""
+
+class _Compatibility(object):
+ def __init__(self, reason):
+ self.reasons = [ ]
+ if reason:
+ self.reasons.append(reason)
+
+ def combine(self, other):
+ if self._level() <= other._level():
+ return self._buildclone(other)
+ else:
+ return other._buildclone(self)
+
+ def prefix(self, leadin):
+ self.reasons = [ leadin + r for r in self.reasons ]
+
+ def same(self): return self._level() >= 1
+ def deployable(self): return self._level() > 0
+ def resolved(self): return self._level() > -1
+ def compatible(self): return self._level() > -2
+
+ def explain(self):
+ return self.__class__.__name__ + "\n" + "\n".join(self.reasons) + "\n"
+
+ def _buildclone(self, other=None):
+ c = self._buildinstance()
+ c.reasons = self.reasons
+ if other:
+ c.reasons = c.reasons + other.reasons
+ return c
+
+ def _buildinstance(self):
+ return self.__class__(None)
+
+# def _level(self):
+# raise RuntimeError('implement in subclass')
+
+
+class Incompatible(_Compatibility):
+ def _level(self):
+ return -2
+
+class Mixed(_Compatibility):
+ def __init__(self, *inputs):
+ _Compatibility.__init__(self, None)
+ for i in inputs:
+ self.reasons += i.reasons
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return -1
+
+class _Aged(_Compatibility):
+ def combine(self, other):
+ if self._level() == other._level():
+ return self._buildclone(other)
+ if int(self._level()) == int(other._level()):
+ return Mixed(self, other)
+ return _Compatibility.combine(self, other)
+
+class Older(_Aged):
+ def _level(self):
+ return -0.25
+
+class Newer(_Aged):
+ def _level(self):
+ return 0.25
+
+class Same(_Compatibility):
+ def __init__(self):
+ _Compatibility.__init__(self, None)
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return 1
+
+
+
+
diff --git a/indra/lib/python/indra/ipc/httputil.py b/indra/lib/python/indra/ipc/httputil.py
new file mode 100644
index 0000000000..85e4a645bc
--- /dev/null
+++ b/indra/lib/python/indra/ipc/httputil.py
@@ -0,0 +1,94 @@
+"""\
+@file httputil.py
+@brief HTTP utilities. HTTP date conversion and non-blocking HTTP
+client support.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+
+import os
+import time
+import urlparse
+
+import httplib
+try:
+ from mx.DateTime import Parser
+
+ parse_date = Parser.DateTimeFromString
+except ImportError:
+ from dateutil import parser
+
+ parse_date = parser.parse
+
+
+HTTP_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
+
+
+to_http_time = lambda t: time.strftime(HTTP_TIME_FORMAT, time.gmtime(t))
+from_http_time = lambda t: int(parse_date(t).gmticks())
+
+def host_and_port_from_url(url):
+ """@breif Simple function to get host and port from an http url.
+ @return Returns host, port and port may be None.
+ """
+ host = None
+ port = None
+ parsed_url = urlparse.urlparse(url)
+ try:
+ host, port = parsed_url[1].split(':')
+ except ValueError:
+ host = parsed_url[1].split(':')
+ return host, port
+
+
+def better_putrequest(self, method, url, skip_host=0):
+ self.method = method
+ self.path = url
+ self.old_putrequest(method, url, skip_host)
+
+
+class HttpClient(httplib.HTTPConnection):
+ """A subclass of httplib.HTTPConnection which works around a bug
+ in the interaction between eventlet sockets and httplib. httplib relies
+ on gc to close the socket, causing the socket to be closed too early.
+
+ This is an awful hack and the bug should be fixed properly ASAP.
+ """
+ def __init__(self, host, port=None, strict=None):
+ httplib.HTTPConnection.__init__(self, host, port, strict)
+
+ def close(self):
+ pass
+
+ old_putrequest = httplib.HTTPConnection.putrequest
+ putrequest = better_putrequest
+
+
+class HttpsClient(httplib.HTTPSConnection):
+ def close(self):
+ pass
+ old_putrequest = httplib.HTTPSConnection.putrequest
+ putrequest = better_putrequest
+
+
+
+scheme_to_factory_map = {
+ 'http': HttpClient,
+ 'https': HttpsClient,
+}
+
+
+def makeConnection(scheme, location, use_proxy):
+ if use_proxy:
+ if "http_proxy" in os.environ:
+ location = os.environ["http_proxy"]
+ elif "ALL_PROXY" in os.environ:
+ location = os.environ["ALL_PROXY"]
+ else:
+ location = "localhost:3128" #default to local squid
+ if location.startswith("http://"):
+ location = location[len("http://"):]
+ return scheme_to_factory_map[scheme](location)
+
diff --git a/indra/lib/python/indra/ipc/llmessage.py b/indra/lib/python/indra/ipc/llmessage.py
new file mode 100644
index 0000000000..5a9e867ecd
--- /dev/null
+++ b/indra/lib/python/indra/ipc/llmessage.py
@@ -0,0 +1,353 @@
+"""\
+@file llmessage.py
+@brief Message template parsing and compatiblity
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+from sets import Set, ImmutableSet
+
+from compatibility import Incompatible, Older, Newer, Same
+from tokenstream import TokenStream
+
+###
+### Message Template
+###
+
+class Template:
+ def __init__(self):
+ self.messages = { }
+
+ def addMessage(self, m):
+ self.messages[m.name] = m
+
+ def compatibleWithBase(self, base):
+ messagenames = (
+ ImmutableSet(self.messages.keys())
+ | ImmutableSet(base.messages.keys())
+ )
+
+ compatibility = Same()
+ for name in messagenames:
+ selfmessage = self.messages.get(name, None)
+ basemessage = base.messages.get(name, None)
+
+ if not selfmessage:
+ c = Older("missing message %s, did you mean to deprecate?" % name)
+ elif not basemessage:
+ c = Newer("added message %s" % name)
+ else:
+ c = selfmessage.compatibleWithBase(basemessage)
+ c.prefix("in message %s: " % name)
+
+ compatibility = compatibility.combine(c)
+
+ return compatibility
+
+
+
+class Message:
+ HIGH = "High"
+ MEDIUM = "Medium"
+ LOW = "Low"
+ FIXED = "Fixed"
+ priorities = [ HIGH, MEDIUM, LOW, FIXED ]
+ prioritieswithnumber = [ FIXED ]
+
+ TRUSTED = "Trusted"
+ NOTTRUSTED = "NotTrusted"
+ trusts = [ TRUSTED, NOTTRUSTED ]
+
+ UNENCODED = "Unencoded"
+ ZEROCODED = "Zerocoded"
+ encodings = [ UNENCODED, ZEROCODED ]
+
+ NOTDEPRECATED = "NotDeprecated"
+ DEPRECATED = "Deprecated"
+ UDPDEPRECATED = "UDPDeprecated"
+ deprecations = [ NOTDEPRECATED, UDPDEPRECATED, DEPRECATED ]
+ # in order of increasing deprecation
+
+ def __init__(self, name, number, priority, trust, coding):
+ self.name = name
+ self.number = number
+ self.priority = priority
+ self.trust = trust
+ self.coding = coding
+ self.deprecateLevel = 0
+ self.blocks = [ ]
+
+ def deprecated(self):
+ return self.deprecateLevel != 0
+
+ def deprecate(self, deprecation):
+ self.deprecateLevel = self.deprecations.index(deprecation)
+
+ def addBlock(self, block):
+ self.blocks.append(block)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ # this should never happen in real life because of the
+ # way Template matches up messages by name
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.priority != base.priority:
+ return Incompatible("has different priority: %s vs. %s in base"
+ % (self.priority, base.priority))
+ if self.trust != base.trust:
+ return Incompatible("has different trust: %s vs. %s in base"
+ % (self.trust, base.trust))
+ if self.coding != base.coding:
+ return Incompatible("has different coding: %s vs. %s in base"
+ % (self.coding, base.coding))
+ if self.number != base.number:
+ return Incompatible("has different number: %s vs. %s in base"
+ % (self.number, base.number))
+
+ compatibility = Same()
+
+ if self.deprecateLevel != base.deprecateLevel:
+ if self.deprecateLevel < base.deprecateLevel:
+ c = Older("is less deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ else:
+ c = Newer("is more deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ compatibility = compatibility.combine(c)
+
+ selflen = len(self.blocks)
+ baselen = len(base.blocks)
+ samelen = min(selflen, baselen)
+
+ for i in xrange(0, samelen):
+ selfblock = self.blocks[i]
+ baseblock = base.blocks[i]
+
+ c = selfblock.compatibleWithBase(baseblock)
+ if not c.same():
+ c = Incompatible("block %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra blocks" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra blocks" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Block(object):
+ SINGLE = "Single"
+ MULTIPLE = "Multiple"
+ VARIABLE = "Variable"
+ repeats = [ SINGLE, MULTIPLE, VARIABLE ]
+ repeatswithcount = [ MULTIPLE ]
+
+ def __init__(self, name, repeat, count=None):
+ self.name = name
+ self.repeat = repeat
+ self.count = count
+ self.variables = [ ]
+
+ def addVariable(self, variable):
+ self.variables.append(variable)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.repeat != base.repeat:
+ return Incompatible("has different repeat: %s vs. %s in base"
+ % (self.repeat, base.repeat))
+ if self.repeat in Block.repeatswithcount:
+ if self.count != base.count:
+ return Incompatible("has different count: %s vs. %s in base"
+ % (self.count, base.count))
+
+ compatibility = Same()
+
+ selflen = len(self.variables)
+ baselen = len(base.variables)
+
+ for i in xrange(0, min(selflen, baselen)):
+ selfvar = self.variables[i]
+ basevar = base.variables[i]
+
+ c = selfvar.compatibleWithBase(basevar)
+ if not c.same():
+ c = Incompatible("variable %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra variables" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra variables" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Variable:
+ U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
+ S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
+ F32 = "F32"; F64 = "F64"
+ LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
+ LLQUATERNION = "LLQuaternion"
+ LLUUID = "LLUUID"
+ BOOL = "BOOL"
+ IPADDR = "IPADDR"; IPPORT = "IPPORT"
+ FIXED = "Fixed"
+ VARIABLE = "Variable"
+ types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
+ LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
+ LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
+ typeswithsize = [ FIXED, VARIABLE ]
+
+ def __init__(self, name, type, size):
+ self.name = name
+ self.type = type
+ self.size = size
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.type != base.type:
+ return Incompatible("has different type: %s vs. %s in base"
+ % (self.type, base.type))
+ if self.type in Variable.typeswithsize:
+ if self.size != base.size:
+ return Incompatible("has different size: %s vs. %s in base"
+ % (self.size, base.size))
+ return Same()
+
+
+
+###
+### Parsing Message Templates
+###
+
+class TemplateParser:
+ def __init__(self, tokens):
+ self._tokens = tokens
+ self._version = 0
+ self._numbers = { }
+ for p in Message.priorities:
+ self._numbers[p] = 0
+
+ def parseTemplate(self):
+ tokens = self._tokens
+ t = Template()
+ while True:
+ if tokens.want("version"):
+ v = float(tokens.require(tokens.wantFloat()))
+ self._version = v
+ t.version = v
+ continue
+
+ m = self.parseMessage()
+ if m:
+ t.addMessage(m)
+ continue
+
+ if self._version >= 2.0:
+ tokens.require(tokens.wantEOF())
+ break
+ else:
+ if tokens.wantEOF():
+ break
+
+ tokens.consume()
+ # just assume (gulp) that this is a comment
+ # line 468: "sim -> dataserver"
+ return t
+
+
+ def parseMessage(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+
+ name = tokens.require(tokens.wantSymbol())
+ priority = tokens.require(tokens.wantOneOf(Message.priorities))
+
+ if self._version >= 2.0 or priority in Message.prioritieswithnumber:
+ number = int("+" + tokens.require(tokens.wantInteger()), 0)
+ else:
+ self._numbers[priority] += 1
+ number = self._numbers[priority]
+
+ trust = tokens.require(tokens.wantOneOf(Message.trusts))
+ coding = tokens.require(tokens.wantOneOf(Message.encodings))
+
+ m = Message(name, number, priority, trust, coding)
+
+ if self._version >= 2.0:
+ d = tokens.wantOneOf(Message.deprecations)
+ if d:
+ m.deprecate(d)
+
+ while True:
+ b = self.parseBlock()
+ if not b:
+ break
+ m.addBlock(b)
+
+ tokens.require(tokens.want("}"))
+
+ return m
+
+
+ def parseBlock(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ repeat = tokens.require(tokens.wantOneOf(Block.repeats))
+ if repeat in Block.repeatswithcount:
+ count = int(tokens.require(tokens.wantInteger()))
+ else:
+ count = None
+
+ b = Block(name, repeat, count)
+
+ while True:
+ v = self.parseVariable()
+ if not v:
+ break
+ b.addVariable(v)
+
+ tokens.require(tokens.want("}"))
+ return b
+
+
+ def parseVariable(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ type = tokens.require(tokens.wantOneOf(Variable.types))
+ if type in Variable.typeswithsize:
+ size = tokens.require(tokens.wantInteger())
+ else:
+ tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }"
+ size = None
+ tokens.require(tokens.want("}"))
+ return Variable(name, type, size)
+
+def parseTemplateString(s):
+ return TemplateParser(TokenStream().fromString(s)).parseTemplate()
+
+def parseTemplateFile(f):
+ return TemplateParser(TokenStream().fromFile(f)).parseTemplate()
diff --git a/indra/lib/python/indra/ipc/llsdhttp.py b/indra/lib/python/indra/ipc/llsdhttp.py
new file mode 100644
index 0000000000..a2a889742a
--- /dev/null
+++ b/indra/lib/python/indra/ipc/llsdhttp.py
@@ -0,0 +1,257 @@
+"""\
+@file llsdhttp.py
+@brief Functions to ease moving llsd over http
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+import os.path
+import os
+import os
+import urlparse
+
+from indra.base import llsd
+from indra.ipc import httputil
+LLSD = llsd.LLSD()
+
+
+class ConnectionError(Exception):
+ def __init__(self, method, host, port, path, status, reason, body):
+ self.method = method
+ self.host = host
+ self.port = port
+ self.path = path
+ self.status = status
+ self.reason = reason
+ self.body = body
+ Exception.__init__(self)
+
+ def __str__(self):
+ return "%s(%r, %r, %r, %r, %r, %r, %r)" % (
+ type(self).__name__,
+ self.method, self.host, self.port,
+ self.path, self.status, self.reason, self.body)
+
+
+class NotFound(ConnectionError):
+ pass
+
+class Forbidden(ConnectionError):
+ pass
+
+class MalformedResponse(ConnectionError):
+ pass
+
+class NotImplemented(ConnectionError):
+ pass
+
+def runConnection(connection, raise_parse_errors=True):
+ response = connection.getresponse()
+ if (response.status not in [200, 201, 204]):
+ klass = {404:NotFound,
+ 403:Forbidden,
+ 501:NotImplemented}.get(response.status, ConnectionError)
+ raise klass(
+ connection.method, connection.host, connection.port,
+ connection.path, response.status, response.reason, response.read())
+ content_length = response.getheader('content-length')
+
+ if content_length: # Check to see whether it is not None
+ content_length = int(content_length)
+
+ if content_length: # Check to see whether the length is not 0
+ body = response.read(content_length)
+ else:
+ body = ''
+
+ if not body.strip():
+ #print "No body:", `body`
+ return None
+ try:
+ return LLSD.parse(body)
+ except Exception, e:
+ if raise_parse_errors:
+ print "Exception: %s, Could not parse LLSD: " % (e), body
+ raise MalformedResponse(
+ connection.method, connection.host, connection.port,
+ connection.path, response.status, response.reason, body)
+ else:
+ return None
+
+class FileScheme(object):
+ """Retarded scheme to local file wrapper."""
+ host = '<file>'
+ port = '<file>'
+ reason = '<none>'
+
+ def __init__(self, location):
+ pass
+
+ def request(self, method, fullpath, body='', headers=None):
+ self.status = 200
+ self.path = fullpath.split('?')[0]
+ self.method = method = method.lower()
+ assert method in ('get', 'put', 'delete')
+ if method == 'delete':
+ try:
+ os.remove(self.path)
+ except OSError:
+ pass # don't complain if already deleted
+ elif method == 'put':
+ try:
+ f = file(self.path, 'w')
+ f.write(body)
+ f.close()
+ except IOError, e:
+ self.status = 500
+ self.raise_connection_error()
+ elif method == 'get':
+ if not os.path.exists(self.path):
+ self.status = 404
+ self.raise_connection_error(NotFound)
+
+ def connect(self):
+ pass
+
+ def getresponse(self):
+ return self
+
+ def getheader(self, header):
+ if header == 'content-length':
+ try:
+ return os.path.getsize(self.path)
+ except OSError:
+ return 0
+
+ def read(self, howmuch=None):
+ if self.method == 'get':
+ try:
+ return file(self.path, 'r').read(howmuch)
+ except IOError:
+ self.status = 500
+ self.raise_connection_error()
+ return ''
+
+ def raise_connection_error(self, klass=ConnectionError):
+ raise klass(
+ self.method, self.host, self.port,
+ self.path, self.status, self.reason, '')
+
+scheme_to_factory_map = {
+ 'http': httputil.HttpClient,
+ 'https': httputil.HttpsClient,
+ 'file': FileScheme,
+}
+
+def makeConnection(scheme, location, use_proxy):
+ if use_proxy:
+ if "http_proxy" in os.environ:
+ location = os.environ["http_proxy"]
+ elif "ALL_PROXY" in os.environ:
+ location = os.environ["ALL_PROXY"]
+ else:
+ location = "localhost:3128" #default to local squid
+ if location.startswith("http://"):
+ location = location[len("http://"):]
+ return scheme_to_factory_map[scheme](location)
+
+
+def get(url, headers=None, use_proxy=False):
+ if headers is None:
+ headers = {}
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=use_proxy)
+ fullpath = path
+ if query:
+ fullpath += "?"+query
+ connection.connect()
+ if headers is None:
+ headers=dict()
+ if use_proxy:
+ headers.update({ "Host" : location })
+ connection.request("GET", url, headers=headers)
+ else:
+ connection.request("GET", fullpath, headers=headers)
+
+ return runConnection(connection)
+
+def put(url, data, headers=None):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ if headers is None:
+ headers = {}
+ headers.update({'Content-Type': 'application/xml'})
+ fullpath = path
+ if query:
+ fullpath += "?"+query
+ connection.request("PUT", fullpath, body, headers)
+ return runConnection(connection, raise_parse_errors=False)
+
+def delete(url):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("DELETE", path+"?"+query)
+ return runConnection(connection, raise_parse_errors=False)
+
+def post(url, data=None):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/xml'})
+ return runConnection(connection)
+
+def postFile(url, filename):
+ f = open(filename)
+ body = f.read()
+ f.close()
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'})
+ return runConnection(connection)
+
+def getStatus(url, use_proxy=False):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=use_proxy)
+ if use_proxy:
+ connection.request("GET", url, headers={ "Host" : location })
+ else:
+ connection.request("GET", path+"?"+query)
+ return connection.getresponse().status
+
+def putStatus(url, data):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("PUT", path+"?"+query, body, {'Content-Type': 'application/xml'})
+ return connection.getresponse().status
+
+def deleteStatus(url):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("DELETE", path+"?"+query)
+ return connection.getresponse().status
+
+def postStatus(url, data):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body)
+ return connection.getresponse().status
+
+def postFileStatus(url, filename):
+ f = open(filename)
+ body = f.read()
+ f.close()
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'})
+ response = connection.getresponse()
+ return response.status, response.read()
+
+def getFromSimulator(path, use_proxy=False):
+ return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy)
+
+def postToSimulator(path, data=None):
+ return post('http://' + simulatorHostAndPort + path, data)
diff --git a/indra/lib/python/indra/ipc/russ.py b/indra/lib/python/indra/ipc/russ.py
new file mode 100644
index 0000000000..f6f67d4314
--- /dev/null
+++ b/indra/lib/python/indra/ipc/russ.py
@@ -0,0 +1,123 @@
+"""\
+@file russ.py
+@brief Recursive URL Substitution Syntax helpers
+@author Phoenix
+
+Many details on how this should work is available on the wiki:
+https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
+
+Adding features to this should be reflected in that page in the
+implementations section.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import urllib
+from indra.ipc import llsdhttp
+
+class UnbalancedBraces(Exception):
+ pass
+
+class UnknownDirective(Exception):
+ pass
+
+class BadDirective(Exception):
+ pass
+
+def format(format_str, context):
+ """@brief Format format string according to rules for RUSS.
+@see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax
+@param format_str The input string to format.
+@param context A map used for string substitutions.
+@return Returns the formatted string. If no match, the braces remain intact.
+"""
+ while True:
+ #print "format_str:", format_str
+ all_matches = _find_sub_matches(format_str)
+ if not all_matches:
+ break
+ substitutions = 0
+ while True:
+ matches = all_matches.pop()
+ # we work from right to left to make sure we do not
+ # invalidate positions earlier in format_str
+ matches.reverse()
+ for pos in matches:
+ # Use index since _find_sub_matches should have raised
+ # an exception, and failure to find now is an exception.
+ end = format_str.index('}', pos)
+ #print "directive:", format_str[pos+1:pos+5]
+ if format_str[pos + 1] == '$':
+ value = context.get(format_str[pos + 2:end])
+ elif format_str[pos + 1] == '%':
+ value = _build_query_string(
+ context.get(format_str[pos + 2:end]))
+ elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file':
+ value = _fetch_url_directive(format_str[pos + 1:end])
+ else:
+ raise UnknownDirective, format_str[pos:end + 1]
+ if not value == None:
+ format_str = format_str[:pos]+str(value)+format_str[end+1:]
+ substitutions += 1
+
+ # If there were any substitutions at this depth, re-parse
+ # since this may have revealed new things to substitute
+ if substitutions:
+ break
+ if not all_matches:
+ break
+
+ # If there were no substitutions at all, and we have exhausted
+ # the possible matches, bail.
+ if not substitutions:
+ break
+ return format_str
+
+def _find_sub_matches(format_str):
+ """@brief Find all of the substitution matches.
+@param format_str the RUSS conformant format string.
+@return Returns an array of depths of arrays of positional matches in input.
+"""
+ depth = 0
+ matches = []
+ for pos in range(len(format_str)):
+ if format_str[pos] == '{':
+ depth += 1
+ if not len(matches) == depth:
+ matches.append([])
+ matches[depth - 1].append(pos)
+ continue
+ if format_str[pos] == '}':
+ depth -= 1
+ continue
+ if not depth == 0:
+ raise UnbalancedBraces, format_str
+ return matches
+
+def _build_query_string(query_dict):
+ """\
+ @breif given a dict, return a query string. utility wrapper for urllib.
+ @param query_dict input query dict
+ @returns Returns an urlencoded query string including leading '?'.
+ """
+ if query_dict:
+ return '?' + urllib.urlencode(query_dict)
+ else:
+ return ''
+
+def _fetch_url_directive(directive):
+ "*FIX: This only supports GET"
+ commands = directive.split('|')
+ resource = llsdhttp.get(commands[0])
+ if len(commands) == 3:
+ resource = _walk_resource(resource, commands[2])
+ return resource
+
+def _walk_resource(resource, path):
+ path = path.split('/')
+ for child in path:
+ if not child:
+ continue
+ resource = resource[child]
+ return resource
diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py
new file mode 100644
index 0000000000..d422b2ed42
--- /dev/null
+++ b/indra/lib/python/indra/ipc/servicebuilder.py
@@ -0,0 +1,52 @@
+"""\
+@file servicebuilder.py
+@author Phoenix
+@brief Class which will generate service urls.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+from indra.base import config
+from indra.ipc import llsdhttp
+from indra.ipc import russ
+
+services_config = {}
+try:
+ services_config = llsdhttp.get(config.get('services-config'))
+except:
+ pass
+
+class ServiceBuilder(object):
+ def __init__(self, services_definition = services_config):
+ """\
+ @brief
+ @brief Create a ServiceBuilder.
+ @param services_definition Complete services definition, services.xml.
+ """
+ # no need to keep a copy of the services section of the
+ # complete services definition, but it doesn't hurt much.
+ self.services = services_definition['services']
+ self.builders = {}
+ for service in self.services:
+ service_builder = service.get('service-builder')
+ if not service_builder:
+ continue
+ if isinstance(service_builder, dict):
+ # We will be constructing several builders
+ for name, builder in service_builder.items():
+ full_builder_name = service['name'] + '-' + name
+ self.builders[full_builder_name] = builder
+ else:
+ self.builders[service['name']] = service_builder
+
+ def buildServiceURL(self, name, context):
+ """\
+ @brief given the environment on construction, return a service URL.
+ @param name The name of the service.
+ @param context A dict of name value lookups for the service.
+ @returns Returns the
+ """
+ base_url = config.get('services-base-url')
+ svc_path = russ.format(self.builders[name], context)
+ return base_url + svc_path
diff --git a/indra/lib/python/indra/ipc/tokenstream.py b/indra/lib/python/indra/ipc/tokenstream.py
new file mode 100644
index 0000000000..f757f2f8af
--- /dev/null
+++ b/indra/lib/python/indra/ipc/tokenstream.py
@@ -0,0 +1,134 @@
+"""\
+@file tokenstream.py
+@brief Message template parsing utility class
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import re
+
+class _EOF(object):
+ pass
+
+EOF = _EOF()
+
+class _LineMarker(int):
+ pass
+
+_commentRE = re.compile(r'//.*')
+_symbolRE = re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
+_integerRE = re.compile(r'(0x[0-9A-Fa-f]+|0\d*|[1-9]\d*)')
+_floatRE = re.compile(r'\d+(\.\d*)?')
+
+
+class ParseError(Exception):
+ def __init__(self, stream, reason):
+ self.line = stream.line
+ self.context = stream._context()
+ self.reason = reason
+
+ def _contextString(self):
+ c = [ ]
+ for t in self.context:
+ if isinstance(t, _LineMarker):
+ break
+ c.append(t)
+ return " ".join(c)
+
+ def __str__(self):
+ return "line %d: %s @ ... %s" % (
+ self.line, self.reason, self._contextString())
+
+ def __nonzero__(self):
+ return False
+
+
+def _optionText(options):
+ n = len(options)
+ if n == 1:
+ return '"%s"' % options[0]
+ return '"' + '", "'.join(options[0:(n-1)]) + '" or "' + options[-1] + '"'
+
+
+class TokenStream(object):
+ def __init__(self):
+ self.line = 0
+ self.tokens = [ ]
+
+ def fromString(self, string):
+ return self.fromLines(string.split('\n'))
+
+ def fromFile(self, file):
+ return self.fromLines(file)
+
+ def fromLines(self, lines):
+ i = 0
+ for line in lines:
+ i += 1
+ self.tokens.append(_LineMarker(i))
+ self.tokens.extend(_commentRE.sub(" ", line).split())
+ self._consumeLines()
+ return self
+
+ def consume(self):
+ if not self.tokens:
+ return EOF
+ t = self.tokens.pop(0)
+ self._consumeLines()
+ return t
+
+ def _consumeLines(self):
+ while self.tokens and isinstance(self.tokens[0], _LineMarker):
+ self.line = self.tokens.pop(0)
+
+ def peek(self):
+ if not self.tokens:
+ return EOF
+ return self.tokens[0]
+
+ def want(self, t):
+ if t == self.peek():
+ return self.consume()
+ return ParseError(self, 'expected "%s"' % t)
+
+ def wantOneOf(self, options):
+ assert len(options)
+ if self.peek() in options:
+ return self.consume()
+ return ParseError(self, 'expected one of %s' % _optionText(options))
+
+ def wantEOF(self):
+ return self.want(EOF)
+
+ def wantRE(self, re, message=None):
+ t = self.peek()
+ if t != EOF:
+ m = re.match(t)
+ if m and m.end() == len(t):
+ return self.consume()
+ if not message:
+ message = "expected match for r'%s'" % re.pattern
+ return ParseError(self, message)
+
+ def wantSymbol(self):
+ return self.wantRE(_symbolRE, "expected symbol")
+
+ def wantInteger(self):
+ return self.wantRE(_integerRE, "expected integer")
+
+ def wantFloat(self):
+ return self.wantRE(_floatRE, "expected float")
+
+ def _context(self):
+ n = min(5, len(self.tokens))
+ return self.tokens[0:n]
+
+ def require(self, t):
+ if t:
+ return t
+ if isinstance(t, ParseError):
+ raise t
+ else:
+ raise ParseError(self, "unmet requirement")
+
diff --git a/indra/lib/python/indra/ipc/xml_rpc.py b/indra/lib/python/indra/ipc/xml_rpc.py
new file mode 100644
index 0000000000..66375b4ac3
--- /dev/null
+++ b/indra/lib/python/indra/ipc/xml_rpc.py
@@ -0,0 +1,253 @@
+"""\
+@file xml_rpc.py
+@brief An implementation of a parser/generator for the XML-RPC xml format.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+
+from greenlet import greenlet
+
+from mulib import mu
+
+from xml.sax import handler
+from xml.sax import parseString
+
+
+# States
+class Expected(object):
+ def __init__(self, tag):
+ self.tag = tag
+
+ def __getattr__(self, name):
+ return type(self)(name)
+
+ def __repr__(self):
+ return '%s(%r)' % (
+ type(self).__name__, self.tag)
+
+
+class START(Expected):
+ pass
+
+
+class END(Expected):
+ pass
+
+
+class STR(object):
+ tag = ''
+
+
+START = START('')
+END = END('')
+
+
+class Malformed(Exception):
+ pass
+
+
+class XMLParser(handler.ContentHandler):
+ def __init__(self, state_machine, next_states):
+ handler.ContentHandler.__init__(self)
+ self.state_machine = state_machine
+ if not isinstance(next_states, tuple):
+ next_states = (next_states, )
+ self.next_states = next_states
+ self._character_buffer = ''
+
+ def assertState(self, state, name, *rest):
+ if not isinstance(self.next_states, tuple):
+ self.next_states = (self.next_states, )
+ for next in self.next_states:
+ if type(state) == type(next):
+ if next.tag and next.tag != name:
+ raise Malformed(
+ "Expected %s, got %s %s %s" % (
+ next, state, name, rest))
+ break
+ else:
+ raise Malformed(
+ "Expected %s, got %s %s %s" % (
+ self.next_states, state, name, rest))
+
+ def startElement(self, name, attrs):
+ self.assertState(START, name.lower(), attrs)
+ self.next_states = self.state_machine.switch(START, (name.lower(), dict(attrs)))
+
+ def endElement(self, name):
+ if self._character_buffer.strip():
+ characters = self._character_buffer.strip()
+ self._character_buffer = ''
+ self.assertState(STR, characters)
+ self.next_states = self.state_machine.switch(characters)
+ self.assertState(END, name.lower())
+ self.next_states = self.state_machine.switch(END, name.lower())
+
+ def error(self, exc):
+ self.bozo = 1
+ self.exc = exc
+
+ def fatalError(self, exc):
+ self.error(exc)
+ raise exc
+
+ def characters(self, characters):
+ self._character_buffer += characters
+
+
+def parse(what):
+ child = greenlet(xml_rpc)
+ me = greenlet.getcurrent()
+ startup_states = child.switch(me)
+ parser = XMLParser(child, startup_states)
+ try:
+ parseString(what, parser)
+ except Malformed:
+ print what
+ raise
+ return child.switch()
+
+
+def xml_rpc(yielder):
+ yielder.switch(START.methodcall)
+ yielder.switch(START.methodname)
+ methodName = yielder.switch(STR)
+ yielder.switch(END.methodname)
+
+ yielder.switch(START.params)
+
+ root = None
+ params = []
+ while True:
+ state, _ = yielder.switch(START.param, END.params)
+ if state == END:
+ break
+
+ yielder.switch(START.value)
+
+ params.append(
+ handle(yielder))
+
+ yielder.switch(END.value)
+ yielder.switch(END.param)
+
+ yielder.switch(END.methodcall)
+ ## Resume parse
+ yielder.switch()
+ ## Return result to parse
+ return methodName.strip(), params
+
+
+def handle(yielder):
+ _, (tag, attrs) = yielder.switch(START)
+ if tag in ['int', 'i4']:
+ result = int(yielder.switch(STR))
+ elif tag == 'boolean':
+ result = bool(int(yielder.switch(STR)))
+ elif tag == 'string':
+ result = yielder.switch(STR)
+ elif tag == 'double':
+ result = float(yielder.switch(STR))
+ elif tag == 'datetime.iso8601':
+ result = yielder.switch(STR)
+ elif tag == 'base64':
+ result = base64.b64decode(yielder.switch(STR))
+ elif tag == 'struct':
+ result = {}
+ while True:
+ state, _ = yielder.switch(START.member, END.struct)
+ if state == END:
+ break
+
+ yielder.switch(START.name)
+ key = yielder.switch(STR)
+ yielder.switch(END.name)
+
+ yielder.switch(START.value)
+ result[key] = handle(yielder)
+ yielder.switch(END.value)
+
+ yielder.switch(END.member)
+ ## We already handled </struct> above, don't want to handle it below
+ return result
+ elif tag == 'array':
+ result = []
+ yielder.switch(START.data)
+ while True:
+ state, _ = yielder.switch(START.value, END.data)
+ if state == END:
+ break
+
+ result.append(handle(yielder))
+
+ yielder.switch(END.value)
+
+ yielder.switch(getattr(END, tag))
+
+ return result
+
+
+VALUE = mu.tagFactory('value')
+BOOLEAN = mu.tagFactory('boolean')
+INT = mu.tagFactory('int')
+STRUCT = mu.tagFactory('struct')
+MEMBER = mu.tagFactory('member')
+NAME = mu.tagFactory('name')
+ARRAY = mu.tagFactory('array')
+DATA = mu.tagFactory('data')
+STRING = mu.tagFactory('string')
+DOUBLE = mu.tagFactory('double')
+METHODRESPONSE = mu.tagFactory('methodResponse')
+PARAMS = mu.tagFactory('params')
+PARAM = mu.tagFactory('param')
+
+mu.inline_elements['string'] = True
+mu.inline_elements['boolean'] = True
+mu.inline_elements['name'] = True
+
+
+def _generate(something):
+ if isinstance(something, dict):
+ result = STRUCT()
+ for key, value in something.items():
+ result[
+ MEMBER[
+ NAME[key], _generate(value)]]
+ return VALUE[result]
+ elif isinstance(something, list):
+ result = DATA()
+ for item in something:
+ result[_generate(item)]
+ return VALUE[ARRAY[[result]]]
+ elif isinstance(something, basestring):
+ return VALUE[STRING[something]]
+ elif isinstance(something, bool):
+ if something:
+ return VALUE[BOOLEAN['1']]
+ return VALUE[BOOLEAN['0']]
+ elif isinstance(something, int):
+ return VALUE[INT[something]]
+ elif isinstance(something, float):
+ return VALUE[DOUBLE[something]]
+
+def generate(*args):
+ params = PARAMS()
+ for arg in args:
+ params[PARAM[_generate(arg)]]
+ return METHODRESPONSE[params]
+
+
+if __name__ == '__main__':
+ print parse("""<?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
+""")
+
+
+
+
+
+
+
+
+