Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar_network/toolkit/router.py
diff options
context:
space:
mode:
Diffstat (limited to 'sugar_network/toolkit/router.py')
-rw-r--r--sugar_network/toolkit/router.py351
1 files changed, 161 insertions, 190 deletions
diff --git a/sugar_network/toolkit/router.py b/sugar_network/toolkit/router.py
index df57ff3..b37eee4 100644
--- a/sugar_network/toolkit/router.py
+++ b/sugar_network/toolkit/router.py
@@ -20,16 +20,16 @@ import time
import types
import logging
import calendar
-import mimetypes
from base64 import b64decode
from bisect import bisect_left
from urllib import urlencode
from urlparse import parse_qsl, urlsplit
from email.utils import parsedate, formatdate
-from os.path import isfile, split, splitext
+from os.path import isfile
from sugar_network import toolkit
-from sugar_network.toolkit import http, coroutine, enforce
+from sugar_network.toolkit.coroutine import this
+from sugar_network.toolkit import i18n, http, coroutine, enforce
_SIGNATURE_LIFETIME = 600
@@ -84,14 +84,15 @@ class ACL(object):
DELETE = 1 << 5
INSERT = 1 << 6
REMOVE = 1 << 7
+ REPLACE = 1 << 8
PUBLIC = CREATE | WRITE | READ | DELETE | INSERT | REMOVE
- AUTH = 1 << 8
- AUTHOR = 1 << 9
- SUPERUSER = 1 << 10
+ AUTH = 1 << 10
+ AUTHOR = 1 << 11
+ SUPERUSER = 1 << 12
- LOCAL = 1 << 11
- CALC = 1 << 12
+ LOCAL = 1 << 13
+ CALC = 1 << 14
NAMES = {
CREATE: 'Create',
@@ -100,6 +101,7 @@ class ACL(object):
DELETE: 'Delete',
INSERT: 'Insert',
REMOVE: 'Remove',
+ REPLACE: 'Replace',
}
@@ -114,18 +116,16 @@ class Unauthorized(http.Unauthorized):
class Request(dict):
- principal = None
- subcall = lambda *args: enforce(False)
-
def __init__(self, environ=None, method=None, path=None, cmd=None,
content=None, content_stream=None, content_type=None, session=None,
- **kwargs):
+ principal=None, **kwargs):
dict.__init__(self)
self.path = []
self.cmd = None
self.environ = {}
self.session = session or {}
+ self.principal = principal
self._content = _NOT_SET
self._dirty_query = False
@@ -252,6 +252,11 @@ class Request(dict):
return self.path[2]
@property
+ def key(self):
+ if len(self.path) > 3:
+ return self.path[3]
+
+ @property
def static_prefix(self):
http_host = self.environ.get('HTTP_HOST')
if http_host:
@@ -326,23 +331,6 @@ class Request(dict):
else:
existing_value = self[key] = [existing_value, value]
- def call(self, response=None, **kwargs):
- environ = {}
- for key in ('HTTP_HOST',
- 'HTTP_ACCEPT_LANGUAGE',
- 'HTTP_ACCEPT_ENCODING',
- 'HTTP_IF_MODIFIED_SINCE',
- 'HTTP_AUTHORIZATION',
- ):
- if key in self.environ:
- environ[key] = self.environ[key]
- request = Request(environ, **kwargs)
- if response is None:
- response = Response()
- request.principal = self.principal
- request.subcall = self.subcall
- return self.subcall(request, response)
-
def ensure_content(self):
if self._content is not _NOT_SET:
return
@@ -400,9 +388,9 @@ class Response(dict):
for key, value in dict.items(self):
if type(value) in (list, tuple):
for i in value:
- result.append((_to_ascii(key), _to_ascii(i)))
+ result.append((toolkit.ascii(key), toolkit.ascii(i)))
else:
- result.append((_to_ascii(key), _to_ascii(value)))
+ result.append((toolkit.ascii(key), toolkit.ascii(value)))
return result
def __repr__(self):
@@ -428,10 +416,6 @@ class Response(dict):
dict.__delitem__(self, key)
-class Blob(dict):
- pass
-
-
class Router(object):
def __init__(self, routes_model, allow_spawn=False):
@@ -441,8 +425,8 @@ class Router(object):
self._invalid_origins = set()
self._host = None
self._routes = _Routes()
- self._preroutes = set()
- self._postroutes = set()
+ self._preroutes = []
+ self._postroutes = []
processed = set()
cls = type(routes_model)
@@ -452,10 +436,14 @@ class Router(object):
if name in processed:
continue
if hasattr(attr, 'is_preroute'):
- self._preroutes.add(getattr(routes_model, name))
+ route_ = getattr(routes_model, name)
+ if route_ not in self._preroutes:
+ self._preroutes.append(route_)
continue
elif hasattr(attr, 'is_postroute'):
- self._postroutes.add(getattr(routes_model, name))
+ route_ = getattr(routes_model, name)
+ if route_ not in self._postroutes:
+ self._postroutes.append(route_)
continue
elif not hasattr(attr, 'route'):
continue
@@ -481,44 +469,75 @@ class Router(object):
processed.add(name)
cls = cls.__base__
- def call(self, request, response):
- request.subcall = self.call
- result = self._call_route(request, response)
-
- if isinstance(result, Blob):
- if 'url' in result:
- raise http.Redirect(result['url'])
-
- path = result['blob']
- enforce(isfile(path), 'No such file')
-
- mtime = result.get('mtime') or int(os.stat(path).st_mtime)
- if request.if_modified_since and mtime and \
- mtime <= request.if_modified_since:
- raise http.NotModified()
- response.last_modified = mtime
-
- response.content_type = result.get('mime_type') or \
- 'application/octet-stream'
-
- filename = result.get('filename')
- if not filename:
- filename = _filename(result.get('name') or
- splitext(split(path)[-1])[0],
- response.content_type)
- response['Content-Disposition'] = \
- 'attachment; filename="%s"' % filename
-
- result = file(path, 'rb')
-
- if hasattr(result, 'read'):
- if hasattr(result, 'fileno'):
- response.content_length = os.fstat(result.fileno()).st_size
- elif hasattr(result, 'seek'):
- result.seek(0, 2)
- response.content_length = result.tell()
- result.seek(0)
- result = _stream_reader(result)
+ this.call = self.call
+
+ def call(self, request=None, response=None, environ=None, principal=None,
+ **kwargs):
+ if request is None:
+ if this.request is not None:
+ if not environ:
+ environ = {}
+ for key in ('HTTP_HOST',
+ 'HTTP_ACCEPT_LANGUAGE',
+ 'HTTP_ACCEPT_ENCODING',
+ 'HTTP_IF_MODIFIED_SINCE',
+ 'HTTP_AUTHORIZATION',
+ ):
+ if key in this.request.environ:
+ environ[key] = this.request.environ[key]
+ if not principal:
+ principal = this.request.principal
+ request = Request(environ=environ, principal=principal, **kwargs)
+ if response is None:
+ response = Response()
+
+ route_ = self._resolve_route(request)
+
+ for arg, cast in route_.arguments.items():
+ value = request.get(arg)
+ if value is None:
+ if not hasattr(cast, '__call__'):
+ request[arg] = cast
+ continue
+ if not hasattr(cast, '__call__'):
+ cast = type(cast)
+ try:
+ request[arg] = _typecast(cast, value)
+ except Exception, error:
+ raise http.BadRequest(
+ 'Cannot typecast %r argument: %s' % (arg, error))
+ kwargs = {}
+ for arg in route_.kwarg_names:
+ if arg == 'request':
+ kwargs[arg] = request
+ elif arg == 'response':
+ kwargs[arg] = response
+ elif arg not in kwargs:
+ kwargs[arg] = request.get(arg)
+
+ for i in self._preroutes:
+ i(route_, request, response)
+ result = None
+ exception = None
+ try:
+ result = route_.callback(**kwargs)
+ if route_.mime_type == 'text/event-stream' and \
+ self._allow_spawn and 'spawn' in request:
+ _logger.debug('Spawn event stream for %r', request)
+ request.ensure_content()
+ coroutine.spawn(self._event_stream, request, result)
+ result = None
+ except Exception, exception:
+ raise
+ else:
+ if not response.content_type:
+ if isinstance(result, toolkit.File):
+ response.content_type = result.get('mime_type')
+ if not response.content_type:
+ response.content_type = route_.mime_type
+ finally:
+ for i in self._postroutes:
+ i(request, response, result, exception)
return result
@@ -533,7 +552,7 @@ class Router(object):
if 'callback' in request:
js_callback = request.pop('callback')
- result = None
+ content = None
try:
if 'HTTP_ORIGIN' in request.environ:
enforce(self._assert_origin(request.environ), http.Forbidden,
@@ -541,7 +560,34 @@ class Router(object):
request.environ['HTTP_ORIGIN'])
response['Access-Control-Allow-Origin'] = \
request.environ['HTTP_ORIGIN']
+
result = self.call(request, response)
+
+ if isinstance(result, toolkit.File):
+ if 'url' in result:
+ raise http.Redirect(result['url'])
+ enforce(isfile(result.path), 'No such file')
+ if request.if_modified_since and result.mtime and \
+ result.mtime <= request.if_modified_since:
+ raise http.NotModified()
+ response.last_modified = result.mtime
+ response.content_type = result.get('mime_type') or \
+ 'application/octet-stream'
+ response['Content-Disposition'] = \
+ 'attachment; filename="%s"' % result.name
+ result = file(result.path, 'rb')
+
+ if not hasattr(result, 'read'):
+ content = result
+ else:
+ if hasattr(result, 'fileno'):
+ response.content_length = os.fstat(result.fileno()).st_size
+ elif hasattr(result, 'seek'):
+ result.seek(0, 2)
+ response.content_length = result.tell()
+ result.seek(0)
+ content = _stream_reader(result)
+
except http.StatusPass, error:
response.status = error.status
if error.headers:
@@ -557,100 +603,46 @@ class Router(object):
if request.method == 'HEAD':
response.meta['error'] = str(error)
else:
- result = {'error': str(error),
- 'request': request.url,
- }
+ content = {'error': str(error), 'request': request.url}
response.content_type = 'application/json'
- result_streamed = isinstance(result, types.GeneratorType)
+ streamed_content = isinstance(content, types.GeneratorType)
if request.method == 'HEAD':
- result_streamed = False
- result = None
+ streamed_content = False
+ content = None
elif js_callback:
- if result_streamed:
- result = ''.join(result)
- result_streamed = False
- result = '%s(%s);' % (js_callback, json.dumps(result))
- response.content_length = len(result)
- elif not result_streamed:
+ if streamed_content:
+ content = ''.join(content)
+ streamed_content = False
+ content = '%s(%s);' % (js_callback, json.dumps(content))
+ response.content_length = len(content)
+ elif not streamed_content:
if response.content_type == 'application/json':
- result = json.dumps(result)
+ content = json.dumps(content)
if 'content-length' not in response:
- response.content_length = len(result) if result else 0
+ response.content_length = len(content) if content else 0
for key, value in response.meta.items():
- response.set('X-SN-%s' % _to_ascii(key), json.dumps(value))
+ response.set('X-SN-%s' % toolkit.ascii(key), json.dumps(value))
- if request.method == 'HEAD' and result is not None:
+ if request.method == 'HEAD' and content is not None:
_logger.warning('Content from HEAD response is ignored')
- result = None
+ content = None
- _logger.trace('%s call: request=%s response=%r result=%r',
- self, request.environ, response, repr(result)[:256])
+ _logger.trace('%s call: request=%s response=%r content=%r',
+ self, request.environ, response, repr(content)[:256])
start_response(response.status, response.items())
- if result_streamed:
+ if streamed_content:
if response.content_type == 'text/event-stream':
- for event in _event_stream(request, result):
+ for event in _event_stream(request, content):
yield 'data: %s\n\n' % json.dumps(event)
else:
- for i in result:
+ for i in content:
yield i
- elif result is not None:
- yield result
-
- def _call_route(self, request, response):
- route_ = self._resolve_route(request)
- request.routes = self._routes_model
-
- for arg, cast in route_.arguments.items():
- value = request.get(arg)
- if value is None:
- if not hasattr(cast, '__call__'):
- request[arg] = cast
- continue
- if not hasattr(cast, '__call__'):
- cast = type(cast)
- try:
- request[arg] = _typecast(cast, value)
- except Exception, error:
- raise http.BadRequest(
- 'Cannot typecast %r argument: %s' % (arg, error))
- kwargs = {}
- for arg in route_.kwarg_names:
- if arg == 'request':
- kwargs[arg] = request
- elif arg == 'response':
- kwargs[arg] = response
- elif arg not in kwargs:
- kwargs[arg] = request.get(arg)
-
- for i in self._preroutes:
- i(route_, request, response)
- result = None
- exception = None
- try:
- result = route_.callback(**kwargs)
- if route_.mime_type == 'text/event-stream' and \
- self._allow_spawn and 'spawn' in request:
- _logger.debug('Spawn event stream for %r', request)
- request.ensure_content()
- coroutine.spawn(self._event_stream, request, result)
- result = None
- except Exception, exception:
- raise
- else:
- if not response.content_type:
- if isinstance(result, Blob):
- response.content_type = result.get('mime_type')
- if not response.content_type:
- response.content_type = route_.mime_type
- finally:
- for i in self._postroutes:
- i(request, response, result, exception)
-
- return result
+ elif content is not None:
+ yield content
def _resolve_route(self, request):
found_path = [False]
@@ -695,9 +687,19 @@ class Router(object):
commons['guid'] = request.guid
if request.prop:
commons['prop'] = request.prop
- for event in _event_stream(request, stream):
+ try:
+ for event in _event_stream(request, stream):
+ event.update(commons)
+ this.localcast(event)
+ except Exception, error:
+ _logger.exception('Event stream %r failed', request)
+ event = {'event': 'failure',
+ 'exception': type(error).__name__,
+ 'error': str(error),
+ }
+ event.update(request.session)
event.update(commons)
- self._routes_model.broadcast(event)
+ this.localcast(event)
def _assert_origin(self, environ):
origin = environ['HTTP_ORIGIN']
@@ -747,22 +749,6 @@ class _ContentStream(object):
return result
-def _filename(names, mime_type):
- if type(names) not in (list, tuple):
- names = [names]
- parts = []
- for name in names:
- if isinstance(name, dict):
- name = toolkit.gettext(name)
- parts.append(''.join([i.capitalize() for i in name.split()]))
- result = '-'.join(parts)
- if mime_type:
- if not mimetypes.inited:
- mimetypes.init()
- result += mimetypes.guess_extension(mime_type) or ''
- return result.replace(os.sep, '')
-
-
def _stream_reader(stream):
try:
while True:
@@ -783,15 +769,8 @@ def _event_stream(request, stream):
event[0].update(i)
event = event[0]
yield event
- except Exception, error:
- _logger.exception('Event stream %r failed', request)
- event = {'event': 'failure',
- 'exception': type(error).__name__,
- 'error': str(error),
- }
- event.update(request.session)
- yield event
- _logger.debug('Event stream %r exited', request)
+ finally:
+ _logger.debug('Event stream %r exited', request)
def _typecast(cast, value):
@@ -817,7 +796,7 @@ def _typecast(cast, value):
def _parse_accept_language(value):
if not value:
- return [toolkit.default_lang()]
+ return [i18n.default_lang()]
langs = []
qualities = []
for chunk in value.split(','):
@@ -836,14 +815,6 @@ def _parse_accept_language(value):
return langs
-def _to_ascii(value):
- if not isinstance(value, basestring):
- return str(value)
- if isinstance(value, unicode):
- return value.encode('utf8')
- return value
-
-
class _Routes(dict):
def __init__(self, parent=None):