django-ddp/dddp/views.py

230 lines
7.8 KiB
Python

"""Django DDP Server views."""
from __future__ import absolute_import, unicode_literals
import io
import logging
import mimetypes
import os.path
from ejson import dumps, loads
from django.conf import settings
from django.http import HttpResponse
from django.views.generic import View
import pybars
def read(path, default=None, encoding='utf8'):
"""Read encoded contents from specified path or return default."""
if not path:
return default
try:
with io.open(path, mode='r', encoding=encoding) as contents:
return contents.read()
except IOError:
if default is not None:
return default
raise
def read_json(path):
"""Read JSON encoded contents from specified path."""
with open(path, mode='r') as json_file:
return loads(json_file.read())
class MeteorView(View):
"""Django DDP Meteor server view."""
logger = logging.getLogger(__name__)
http_method_names = ['get', 'head']
json_path = None
runtime_config = None
manifest = None
program_json = None
program_json_path = None
star_json = None # top level layout
url_map = None
internal_map = None
server_load_map = None
template_path = None # web server HTML template path
client_map = None # web.browser (client) URL to path map
html = '<!DOCTYPE html>\n<html><head><title>DDP App</title></head></html>'
root_url_path_prefix = ''
bundled_js_css_prefix = '/'
def __init__(self, **kwargs):
"""Initialisation for Django DDP server view."""
self.runtime_config = {}
# super(...).__init__ assigns kwargs to instance.
super(MeteorView, self).__init__(**kwargs)
# read and process /etc/mime.types
mimetypes.init()
self.url_map = {}
# process `star_json`
self.star_json = read_json(self.json_path)
star_format = self.star_json['format']
if star_format != 'site-archive-pre1':
raise ValueError(
'Unknown Meteor star format: %r' % star_format,
)
programs = {
program['name']: program
for program in self.star_json['programs']
}
# process `bundle/programs/server/program.json` from build dir
server_json_path = os.path.join(
os.path.dirname(self.json_path),
os.path.dirname(programs['server']['path']),
'program.json',
)
server_json = read_json(server_json_path)
server_format = server_json['format']
if server_format != 'javascript-image-pre1':
raise ValueError(
'Unknown Meteor server format: %r' % server_format,
)
self.server_load_map = {}
for item in server_json['load']:
item['path_full'] = os.path.join(
os.path.dirname(server_json_path),
item['path'],
)
self.server_load_map[item['path']] = item
self.url_map[item['path']] = (
item['path_full'], 'text/javascript'
)
try:
item['source_map_full'] = os.path.join(
os.path.dirname(server_json_path),
item['sourceMap'],
)
self.url_map[item['sourceMap']] = (
item['source_map_full'], 'text/plain'
)
except KeyError:
pass
self.template_path = os.path.join(
os.path.dirname(server_json_path),
self.server_load_map[
'packages/boilerplate-generator.js'
][
'assets'
][
'boilerplate_web.browser.html'
],
)
# process `bundle/programs/web.browser/program.json` from build dir
web_browser_json_path = os.path.join(
os.path.dirname(self.json_path),
programs['web.browser']['path'],
)
web_browser_json = read_json(web_browser_json_path)
web_browser_format = web_browser_json['format']
if web_browser_format != 'web-program-pre1':
raise ValueError(
'Unknown Meteor web.browser format: %r' % (
web_browser_format,
),
)
self.client_map = {}
self.internal_map = {}
for item in web_browser_json['manifest']:
item['path_full'] = os.path.join(
os.path.dirname(web_browser_json_path),
item['path'],
)
if item['where'] == 'client':
if '?' in item['url']:
item['url'] = item['url'].split('?', 1)[0]
if item['url'].startswith('/'):
item['url'] = item['url'][1:]
self.client_map[item['url']] = item
self.url_map[item['url']] = (
item['path_full'],
mimetypes.guess_type(
item['path_full'],
)[0] or 'application/octet-stream',
)
elif item['where'] == 'internal':
self.internal_map[item['type']] = item
config = {
'css': [
{'url': item['path']}
for item in web_browser_json['manifest']
if item['type'] == 'css' and item['where'] == 'client'
],
'js': [
{'url': item['path']}
for item in web_browser_json['manifest']
if item['type'] == 'js' and item['where'] == 'client'
],
'meteorRuntimeConfig': '"%s"' % (
dumps(self.runtime_config)
),
'rootUrlPathPrefix': self.root_url_path_prefix,
'bundledJsCssPrefix': self.bundled_js_css_prefix,
'inlineScriptsAllowed': False,
'inline': None,
'head': read(
self.internal_map.get('head', {}).get('path_full', None),
default=u'',
),
'body': read(
self.internal_map.get('body', {}).get('path_full', None),
default=u'',
),
}
tmpl_raw = read(self.template_path, encoding='utf8')
compiler = pybars.Compiler()
tmpl = compiler.compile(tmpl_raw)
self.html = '<!DOCTYPE html>\n%s' % tmpl(config)
def get(self, request, path):
"""Return HTML (or other related content) for Meteor."""
self.logger.debug(
'[%s:%s] %s %s %s',
request.META['REMOTE_ADDR'], request.META['REMOTE_PORT'],
request.method, request.path,
request.META['SERVER_PROTOCOL'],
)
if path == 'meteor_runtime_config.js':
config = {
'DDP_DEFAULT_CONNECTION_URL': request.build_absolute_uri('/'),
'ROOT_URL': request.build_absolute_uri(
'%s/' % self.runtime_config.get('ROOT_URL_PATH_PREFIX', ''),
),
'ROOT_URL_PATH_PREFIX': '',
}
# Use HTTPS instead of HTTP if SECURE_SSL_REDIRECT is set
if config['DDP_DEFAULT_CONNECTION_URL'].startswith('http:') \
and settings.SECURE_SSL_REDIRECT:
config['DDP_DEFAULT_CONNECTION_URL'] = 'https:%s' % (
config['DDP_DEFAULT_CONNECTION_URL'].split(':', 1)[1],
)
config.update(self.runtime_config)
return HttpResponse(
'__meteor_runtime_config__ = %s;' % dumps(config),
content_type='text/javascript',
)
try:
file_path, content_type = self.url_map[path]
with open(file_path, 'r') as content:
return HttpResponse(
content.read(),
content_type=content_type,
)
except KeyError:
return HttpResponse(self.html)