"""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 = '\nDDP App' 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 = '\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)