summaryrefslogtreecommitdiff
path: root/indra/lib/python/indra/ipc
diff options
context:
space:
mode:
authorAaron Brashears <aaronb@lindenlab.com>2009-05-18 23:38:35 +0000
committerAaron Brashears <aaronb@lindenlab.com>2009-05-18 23:38:35 +0000
commit6df2755ba6b24d0cefd52ce175b0212dd46c9b10 (patch)
tree833bc29e7bd5438eb89f34119ae157efe6258b2c /indra/lib/python/indra/ipc
parent0257214763203708e8e29d09346e777b95cdfce6 (diff)
Result of svn merge -r119432:120464 svn+ssh://svn/svn/linden/branches/http_database/merge-03 into trunk. QAR-1462
Diffstat (limited to 'indra/lib/python/indra/ipc')
-rw-r--r--indra/lib/python/indra/ipc/servicebuilder.py22
-rw-r--r--indra/lib/python/indra/ipc/siesta.py132
2 files changed, 120 insertions, 34 deletions
diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py
index cb43bcb26f..0a0ce2b4e2 100644
--- a/indra/lib/python/indra/ipc/servicebuilder.py
+++ b/indra/lib/python/indra/ipc/servicebuilder.py
@@ -39,6 +39,12 @@ except:
pass
_g_builder = None
+def _builder():
+ global _g_builder
+ if _g_builder is None:
+ _g_builder = ServiceBuilder()
+ return _g_builder
+
def build(name, context={}, **kwargs):
""" Convenience method for using a global, singleton, service builder. Pass arguments either via a dict or via python keyword arguments, or both!
@@ -56,6 +62,11 @@ def build(name, context={}, **kwargs):
_g_builder = ServiceBuilder()
return _g_builder.buildServiceURL(name, context, **kwargs)
+def build_path(name, context={}, **kwargs):
+ context = context.copy() # shouldn't modify the caller's dictionary
+ context.update(kwargs)
+ return _builder().buildPath(name, context)
+
class ServiceBuilder(object):
def __init__(self, services_definition = services_config):
"""\
@@ -73,12 +84,21 @@ class ServiceBuilder(object):
continue
if isinstance(service_builder, dict):
# We will be constructing several builders
- for name, builder in service_builder.items():
+ for name, builder in service_builder.iteritems():
full_builder_name = service['name'] + '-' + name
self.builders[full_builder_name] = builder
else:
self.builders[service['name']] = service_builder
+ def buildPath(self, name, context):
+ """\
+ @brief given the environment on construction, return a service path.
+ @param name The name of the service.
+ @param context A dict of name value lookups for the service.
+ @returns Returns the
+ """
+ return russ.format(self.builders[name], context)
+
def buildServiceURL(self, name, context={}, **kwargs):
"""\
@brief given the environment on construction, return a service URL.
diff --git a/indra/lib/python/indra/ipc/siesta.py b/indra/lib/python/indra/ipc/siesta.py
index b206f181c4..d867e71537 100644
--- a/indra/lib/python/indra/ipc/siesta.py
+++ b/indra/lib/python/indra/ipc/siesta.py
@@ -1,3 +1,32 @@
+"""\
+@file siesta.py
+@brief A tiny llsd based RESTful web services framework
+
+$LicenseInfo:firstyear=2008&license=mit$
+
+Copyright (c) 2008, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+from indra.base import config
from indra.base import llsd
from webob import exc
import webob
@@ -37,11 +66,11 @@ def mime_type(content_type):
return content_type.split(';', 1)[0].strip().lower()
class BodyLLSD(object):
- '''Give a webob Request or Response an llsd property.
+ '''Give a webob Request or Response an llsd based "content" property.
- Getting the llsd property parses the body, and caches the result.
+ Getting the content property parses the body, and caches the result.
- Setting the llsd property formats a payload, and the body property
+ Setting the content property formats a payload, and the body property
is set.'''
def _llsd__get(self):
@@ -80,7 +109,7 @@ class BodyLLSD(object):
if hasattr(self, '_llsd'):
del self._llsd
- llsd = property(_llsd__get, _llsd__set, _llsd__del)
+ content = property(_llsd__get, _llsd__set, _llsd__del)
class Response(webob.Response, BodyLLSD):
@@ -114,10 +143,10 @@ class Request(webob.Request, BodyLLSD):
Sensible content type and accept headers are used by default.
- Setting the llsd property also sets the body. Getting the llsd
+ Setting the content property also sets the body. Getting the content
property parses the body if necessary.
- If you set the body property directly, the llsd property will be
+ If you set the body property directly, the content property will be
deleted.'''
default_content_type = 'application/llsd+xml'
@@ -149,11 +178,11 @@ class Request(webob.Request, BodyLLSD):
body = property(webob.Request._body__get, _body__set,
webob.Request._body__del, webob.Request._body__get.__doc__)
- def create_response(self, llsd=None, status='200 OK',
+ def create_response(self, content=None, status='200 OK',
conditional_response=webob.NoDefault):
resp = self.ResponseClass(status=status, request=self,
conditional_response=conditional_response)
- resp.llsd = llsd
+ resp.content = content
return resp
def curl(self):
@@ -196,12 +225,18 @@ llsd_formatters = {
'application/xml': llsd.format_xml,
}
+formatter_qualities = (
+ ('application/llsd+xml', 1.0),
+ ('application/llsd+notation', 0.5),
+ ('application/llsd+binary', 0.4),
+ ('application/xml', 0.3),
+ ('application/json', 0.2),
+ )
def formatter_for_mime_type(mime_type):
'''Return a formatter that encodes to the given MIME type.
The result is a pair of function and MIME type.'''
-
try:
return llsd_formatters[mime_type], mime_type
except KeyError:
@@ -214,21 +249,19 @@ def formatter_for_request(req):
'''Return a formatter that encodes to the preferred type of the client.
The result is a pair of function and actual MIME type.'''
-
- for ctype in req.accept.best_matches('application/llsd+xml'):
- try:
- return llsd_formatters[ctype], ctype
- except KeyError:
- pass
- else:
+ ctype = req.accept.best_match(formatter_qualities)
+ try:
+ return llsd_formatters[ctype], ctype
+ except KeyError:
raise exc.HTTPNotAcceptable().exception
def wsgi_adapter(func, environ, start_response):
'''Adapt a Siesta callable to act as a WSGI application.'''
-
+ # Process the request as appropriate.
try:
req = Request(environ)
+ #print req.urlvars
resp = func(req, **req.urlvars)
if not isinstance(resp, webob.Response):
try:
@@ -281,7 +314,8 @@ def llsd_class(cls):
allowed = [m for m in http11_methods
if hasattr(instance, 'handle_' + m.lower())]
raise exc.HTTPMethodNotAllowed(
- headers={'Allowed': ', '.join(allowed)}).exception
+ headers={'Allow': ', '.join(allowed)}).exception
+ #print "kwargs: ", kwargs
return handler(req, **kwargs)
def replacement(environ, start_response):
@@ -336,7 +370,7 @@ def curl(reqs):
route_re = re.compile(r'''
\{ # exact character "{"
- (\w+) # variable name (restricted to a-z, 0-9, _)
+ (\w*) # "config" or variable (restricted to a-z, 0-9, _)
(?:([:~])([^}]+))? # optional :type or ~regex part
\} # exact character "}"
''', re.VERBOSE)
@@ -344,27 +378,37 @@ route_re = re.compile(r'''
predefined_regexps = {
'uuid': r'[a-f0-9][a-f0-9-]{31,35}',
'int': r'\d+',
+ 'host': r'[a-z0-9][a-z0-9\-\.]*',
}
def compile_route(route):
fp = StringIO()
last_pos = 0
for match in route_re.finditer(route):
+ #print "matches: ", match.groups()
fp.write(re.escape(route[last_pos:match.start()]))
var_name = match.group(1)
sep = match.group(2)
expr = match.group(3)
- if expr:
- if sep == ':':
- expr = predefined_regexps[expr]
- # otherwise, treat what follows '~' as a regexp
+ if var_name == 'config':
+ expr = re.escape(str(config.get(var_name)))
else:
- expr = '[^/]+'
- expr = '(?P<%s>%s)' % (var_name, expr)
+ if expr:
+ if sep == ':':
+ expr = predefined_regexps[expr]
+ # otherwise, treat what follows '~' as a regexp
+ else:
+ expr = '[^/]+'
+ if var_name != '':
+ expr = '(?P<%s>%s)' % (var_name, expr)
+ else:
+ expr = '(%s)' % (expr,)
fp.write(expr)
last_pos = match.end()
fp.write(re.escape(route[last_pos:]))
- return '^%s$' % fp.getvalue()
+ compiled_route = '^%s$' % fp.getvalue()
+ #print route, "->", compiled_route
+ return compiled_route
class Router(object):
'''WSGI routing class. Parses a URL and hands off a request to
@@ -372,21 +416,43 @@ class Router(object):
responds with a 404.'''
def __init__(self):
- self.routes = []
- self.paths = []
+ self._new_routes = []
+ self._routes = []
+ self._paths = []
def add(self, route, app, methods=None):
- self.paths.append(route)
- self.routes.append((re.compile(compile_route(route)), app,
- methods and dict.fromkeys(methods)))
+ self._new_routes.append((route, app, methods))
+
+ def _create_routes(self):
+ for route, app, methods in self._new_routes:
+ self._paths.append(route)
+ self._routes.append(
+ (re.compile(compile_route(route)),
+ app,
+ methods and dict.fromkeys(methods)))
+ self._new_routes = []
def __call__(self, environ, start_response):
+ # load up the config from the config file. Only needs to be
+ # done once per interpreter. This is the entry point of all
+ # siesta applications, so this is where we trap it.
+ _conf = config.get_config()
+ if _conf is None:
+ import os.path
+ fname = os.path.join(
+ environ.get('ll.config_dir', '/local/linden/etc'),
+ 'indra.xml')
+ config.load(fname)
+
+ # proceed with handling the request
+ self._create_routes()
path_info = environ['PATH_INFO']
request_method = environ['REQUEST_METHOD']
allowed = []
- for regex, app, methods in self.routes:
+ for regex, app, methods in self._routes:
m = regex.match(path_info)
if m:
+ #print "groupdict:",m.groupdict()
if not methods or request_method in methods:
environ['paste.urlvars'] = m.groupdict()
return app(environ, start_response)
@@ -396,7 +462,7 @@ class Router(object):
allowed = dict.fromkeys(allows).keys()
allowed.sort()
resp = exc.HTTPMethodNotAllowed(
- headers={'Allowed': ', '.join(allowed)})
+ headers={'Allow': ', '.join(allowed)})
else:
resp = exc.HTTPNotFound()
return resp(environ, start_response)