import os.path
import re
import fnmatch
import logging
import os
from google.appengine.api.mail import InboundEmailMessage
from tic import loader
from tic.conf import settings
from tic.core import Component, ExtensionPoint, TicError, implements
from tic.exceptions import FileNotFoundException, ImproperlyConfigured
from tic.utils.importlib import import_module
from tic.web.api import HTTPNotFound, IAuthenticator, IEmailHandler, IRequestHandler, \
Request, RequestDone
from tic.web.rpc.json import dumps
from tic.development import closure
[docs]def dispatch_request(environ, start_response):
"""
Main entry point for the TIC web interface.
Args:
environ: the WSGI environment dict
start_response: the WSGI callback for starting the response
"""
try:
from boot import ENVIRONMENT
req = Request(environ, start_response)
try:
dispatcher = RequestDispatcher(ENVIRONMENT)
dispatcher.dispatch(req)
except HTTPNotFound:
req.send_response(404)
req.write("404")
except RequestDone:
pass
resp = req._response or []
return resp
except Exception, ex:
req = Request(environ, start_response)
from google.appengine.ext.webapp import template
from tic.web import browser
req.send_response(500)
mimetype = "text/html;charset=utf-8"
req.send_header('Content-Type', mimetype)
import traceback, sys
exc_type, exc_value, exc_tb = sys.exc_info()
tb = traceback.format_exception(exc_type, exc_value, exc_tb)
err = ''.join(tb)
logging.error('\n' + err)
if 'Development' in os.environ['SERVER_SOFTWARE']:
error = "function(){console.warn(%s);}()" % dumps("Python Error:\n%s" % err)
vars = {
'data': {
'js': error,
'text': '<br />'.join(tb)
}
}
req.write(template.render("%stic/web/templates/error.html" % loader.root_path(), vars))
else:
raise
[docs]class FavIconHanlder(Component):
"""
Default handler for the favicon to return nothing..
this is just to escape erroring out which cost alot in appengine
This can be overridden by configuring it in the app.yaml file. see
appengine docs for more details
"""
implements(IRequestHandler)
[docs] def match_request(self, req):
return req.path_info == '/favicon.ico'
[docs] def process_request(self, req):
pass
[docs]class MailHandler(Component):
'''
Router for all incomming mail
'''
implements(IRequestHandler)
handlers = ExtensionPoint(IEmailHandler)
[docs] def match_request(self, req):
return req.path_info.startswith('/_ah/mail/')
[docs] def process_request(self, req):
#TODO: construct EmailMessage object
emailMessate = InboundEmailMessage(req.read())
for handler in self.handlers:
if handler.match_email(emailMessate):
handler.process_email(emailMessate)
[docs]class DefaultHandler(Component):
'''
This is the default handler. It basically handles the entry, index.html
and converting any dojo files to cross domain,xd, files if needed
'''
implements(IRequestHandler)
templates_dir = "templates"
[docs] def match_request(self, req):
return "/client/" in req.path_info
[docs] def process_request(self, req):
self.templates_dir = "%stic/web/templates/" % loader.root_path()
dojo_template = os.path.join(self.templates_dir, "index.html")
closure_template = os.path.join(self.templates_dir, "closure.html")
request_path = req.path_info[1:]
file = os.path.join(loader.root_path(), request_path) #removes the first '/'
if self.match_request(req): # /client/
if file.endswith('.js'):
return self._render_js_file(file, req)
# TODO: Not sure whats the best way to make a default renderer for django templates
elif file.endswith('.django.html'):
return self._render_template(file, req)
elif file.endswith('_test/'): #closure test
file = file[:-1]
path, filename = file.rsplit('/', 1)
css_deps, js_deps = closure.calculate_test_deps(file)
js_test = js_deps.pop()
return self._render_template(
os.path.join(self.templates_dir, "closure_test.html"),
req,
{
'title': request_path[:-1].replace('/','.').replace('_test',''),
'js_deps': js_deps,
'js_test': js_test,
'css_deps': css_deps
})
elif file.endswith('_ep/'): #closure manual defined entry point
file = file[:-1] + ".js" #hack for _get_module_name => must have '.js' as an extension
logging.info(file)
css_deps, js_deps = closure.calculate_deps(file)
ep = loader._get_module_name(file)
return self._render_template(closure_template, req, {
'entrypoint': ep,
'js_deps': js_deps,
'css_deps': css_deps
})
elif file.endswith('/'): #dojo stuff
file = file[:-1]
path, filename = file.rsplit('/', 1)
return self._render_template(
os.path.join(self.templates_dir, "index_js.html"),
req,
{"js": file.replace(loader.root_path(), '').replace('/', '.'),
})
if not request_path:
files = []
for file in loader.locate("entrypoint.js"):
files.append(file)
if len(files) > 1:
raise Exception('More than one entry point defined\n%s' % '\n'.join(files))
if not len(files):
raise Exception('No entry point defined\n')
js_entrypoint = loader._get_module_name(files[0])
if self._is_dojo(files[0]):
return self._render_template(dojo_template, req, {
'entrypoint': js_entrypoint
})
elif self._is_closure(files[0]):
css_deps, js_deps = closure.calculate_deps(files[0])
return self._render_template(closure_template, req, {
'entrypoint': js_entrypoint,
'js_deps': js_deps,
'css_deps': css_deps
})
req.send_file(os.path.abspath(file))
def _is_dojo(self, file):
"""
returns true if the javascript file has dojo.provide()
"""
provide_matcher = re.compile(r'.*\s*dojo\.provide\([\'"](.*)[\'"]\)')
require_matcher = re.compile(r'.*\s*dojo\.require\([\'"](.*)[\'"]\)')
return provide_matcher.match(open(file, 'r').read())
def _is_closure(self, file):
"""
returns true if the javascript file has goog.provide()
"""
provide_matcher = re.compile(r'.*\s*goog\.provide\([\'"](.*)[\'"]\)')
require_matcher = re.compile(r'.*\s*goog\.require\([\'"](.*)[\'"]\)')
return provide_matcher.match(open(file, 'r').read())
def _render_template(self, file, req, data=None):
from google.appengine.ext.webapp import template
mimetype = "text/html;charset=utf-8"
req.send_header('Content-Type', mimetype)
logging.debug(self._get_dojo_modules())
req.write(template.render(file, data))
def _render_dojo_template(self, file, req, data=None):
vars = {
'modules': self._get_dojo_modules(),
'base': os.path.join(os.path.abspath(os.curdir), os.path.join(self.templates_dir, "base.html")),
'data': data,
}
self._render_template(file, req, vars)
def _render_js_file(self, file, req):
if file.endswith(".xd.js"): # Dojo Cross domain. we need to genereate the file
#get the basic file
file = file.replace(".xd.", ".")
self._assert_file_exists(file)
from tic.web.dojo import render_xd_classes
return render_xd_classes(file, req)
self._assert_file_exists(file)
req.send_file(os.path.abspath(file))
def _assert_file_exists(self, file):
if not os.path.isfile(file):
raise FileNotFoundException(file)
def _get_dojo_modules(self):
modules = []
for file in loader.locate('*.js'):
# logging.debug(file)
if '/client/' in file:
m = file.replace(loader.root_path(), '').split('/')[0]
modules.append(m)
return set(modules)
[docs]class RequestDispatcher(Component):
"""
Web request dispatcher.
This component dispatches incoming requests to registered handlers.
"""
required = True
authenticators = ExtensionPoint(IAuthenticator)
handlers = ExtensionPoint(IRequestHandler)
# Public API
[docs] def authenticate(self, req):
for authenticator in self.authenticators:
authname = authenticator.authenticate(req)
if authname:
return authname
else:
return 'anonymous'
[docs] def dispatch(self, req):
# Setup request callbacks for lazily-evaluated properties
req.callbacks.update({
'authname': self.authenticate,
'session': self._get_session,
# 'locale': self._get_locale,
# 'tz': self._get_timezone,
# 'form_token': self._get_form_token
})
# select handler
chosen_handler = None
try:
for handler in self.handlers:
if handler.match_request(req):
chosen_handler = handler
break
# choose the default one if no handler found
if not chosen_handler:
if not req.path_info or req.path_info == '/':
chosen_handler = self._load_default_handler()
except TicError, e:
raise HTTPInternalError(e)
if not chosen_handler:
if req.path_info.endswith('/'):
# Strip trailing / and redirect
target = req.path_info.rstrip('/').encode('utf-8')
if req.query_string:
target += '?' + req.query_string
req.redirect(req.href + target, permanent=True)
raise HTTPNotFound('No handler matched request to %s',
req.path_info)
# pre-process any incoming request, whether a handler
# was found or not
chosen_handler = self._pre_process_request(req, chosen_handler)
# process request
chosen_handler.process_request(req)
# TODO: post-process request
# Private methods
def _load_default_handler(self):
"""loads the default handler"""
module, attr = settings.DEFAULT_HANDLER.rsplit('.', 1)
try:
mod = import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing default handler module %s: "%s"' % (module, e))
except ValueError, e:
raise ImproperlyConfigured('Error importing default handler module. Is DEFAULT_HANDLER a correctly defined class')
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" default handler backend' % (module, attr))
return cls(self.compmgr)
def _pre_process_request(self, req, chosen_handler):
for filter_ in settings.REQUEST_FILTERS:
filter = self._load_filter(filter_)
chosen_handler = filter.pre_process_request(req, chosen_handler)
return chosen_handler
def _load_filter(self, filter):
"""loads the filter"""
module, attr = filter.rsplit('.', 1)
try:
mod = import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing filter module %s: "%s"' % (module, e))
except ValueError, e:
raise ImproperlyConfigured('Error importing filter module. Is FILTERS a correctly defined class')
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" filter backend' % (module, attr))
return cls(self.compmgr)
def _get_session(self, req):
from tic.web.sessions import Session
return Session()