PageRenderTime 145ms CodeModel.GetById 60ms app.highlight 45ms RepoModel.GetById 35ms app.codeStats 0ms

/silverlining/devel-runner.py

https://bitbucket.org/ianb/silverlining/
Python | 231 lines | 189 code | 18 blank | 24 comment | 31 complexity | 80a56fa13359025c042c00d29be09916 MD5 | raw file
  1"""This is the server that is run for silver serve.
  2
  3Note that this is a script, and is run as a script, it is not a
  4module.  This allows a server to be run without silverlining itself on
  5sys.path.
  6"""
  7
  8# Setup sys.path to include silversupport:
  9import sys
 10import os
 11path = os.path.dirname(os.path.dirname(__file__))
 12sys.path.insert(0, path)
 13# The parent directory ends up on sys.path, even though we don't want
 14# it to; here we remove it:
 15here = os.path.abspath(os.path.dirname(__file__))
 16for path in list(sys.path):
 17    if os.path.abspath(path) == here:
 18        sys.path.remove(path)
 19import shlex
 20import mimetypes
 21from silversupport.requests import internal_request
 22from silversupport.appconfig import AppConfig
 23from silversupport.shell import run
 24
 25silverlining_conf = os.path.join(os.environ['HOME'], '.silverlining.conf')
 26
 27
 28def load_paste_reloader():
 29    try:
 30        from paste import reloader
 31        return reloader
 32    except ImportError:
 33        import new
 34        ## FIXME: not sure if this'll work well if sys.path is fixed
 35        ## up later with a Paste:
 36        init_mod = new.module('paste')
 37        init_mod.__path__ = []
 38        sys.modules['paste'] = init_mod
 39        mod = new.module('paste.reloader')
 40        init_mod.reloader = mod
 41        execfile(os.path.join(os.path.dirname(__file__),
 42                              'paste-reloader.py'),
 43                 mod.__dict__)
 44        sys.modules['paste.reloader'] = mod
 45        return mod
 46
 47reloader = load_paste_reloader()
 48reloader.install()
 49
 50def load_paste_httpserver():
 51    try:
 52        from paste import httpserver
 53        return httpserver
 54    except ImportError:
 55        pass
 56    import new
 57    import sys
 58    fn = os.environ['SILVER_PASTE_LOCATION']
 59    if fn.endswith('.pyc'):
 60        fn = fn[:-1]
 61    util_init = os.path.join(os.path.dirname(fn), 'util', '__init__.py')
 62    mod = new.module('paste.util')
 63    mod.__file__ = util_init
 64    execfile(mod.__file__, mod.__dict__)
 65    mod.__path__ = os.path.dirname(util_init)
 66    sys.modules['paste.util'] = mod
 67    util_converters = os.path.join(os.path.dirname(fn), 'util', 'converters.py')
 68    cmod = new.module('paste.util.converters')
 69    cmod.__file__ = util_converters
 70    execfile(cmod.__file__, cmod.__dict__)
 71    sys.modules['paste.util.converters'] = cmod
 72    mod.converters = cmod
 73    mod = new.module('paste.httpserver')
 74    mod.__file__ = fn
 75    execfile(mod.__file__, mod.__dict__)
 76    return mod
 77
 78
 79def get_app(base_path):
 80    ## FIXME: is this a reasonable instance_name default?
 81    app_config = AppConfig(
 82        os.path.join(base_path, 'app.ini'),
 83        local_config=os.environ.get('SILVER_APP_CONFIG') or None)
 84    instance_name = 'localhost'
 85    os.environ['SILVER_INSTANCE_NAME'] = instance_name
 86    os.environ['SILVER_CANONICAL_HOSTNAME'] = 'localhost'
 87    app_config.activate_services(os.environ)
 88    app_config.activate_path()
 89    reloader.watch_file(app_config.config_file)
 90    reloader.watch_file(app_config.runner.split('#')[0])
 91    found_app = app_config.get_app_from_runner()
 92
 93    # This calls the update_fetch URL on every reload/restart, which
 94    # is... questionable.
 95    update_fetch = app_config.update_fetch
 96    for url in update_fetch:
 97        if url.startswith('script:'):
 98            script = url[len('script:'):]
 99            print 'Running update script %s' % script
100            call_script(app_config, script)
101        else:
102            print 'Fetching update URL %s' % url
103            status, headers, body = internal_request(
104                app_config, 'localhost',
105                url, environ={'silverlining.update': True})
106            if not status.startswith('200'):
107                sys.stdout.write(status + '\n')
108                sys.stdout.flush()
109            if body:
110                sys.stdout.write(body)
111                if not body.endswith('\n'):
112                    sys.stdout.write('\n')
113                sys.stdout.flush()
114
115    if app_config.writable_root_location:
116        writable_root = os.environ['CONFIG_WRITABLE_ROOT']
117    else:
118        writable_root = None
119
120    return found_app, writable_root
121
122
123def call_script(app_config, script):
124    run([sys.executable, os.path.join(os.path.dirname(__file__), 'mgr-scripts', 'call-script.py'),
125         app_config.app_dir] + shlex.split(script))
126
127
128class CompoundApp(object):
129    """Application that simulates the Apache configuration of silverlining
130
131    This basically serves up the normal WSGI application, plus the
132    static files.
133    """
134
135    def __init__(self, base_path, writable_root=None):
136        self.base_path = base_path
137        self._app = self.writable_root = None
138        self.app
139
140    @property
141    def app(self):
142        if self._app is not None:
143            return self._app
144        prev_modules = sys.modules.keys()
145        try:
146            self._app, self.writable_root = get_app(self.base_path)
147            return self._app
148        except:
149            self._app = None
150            # Make sure any stale/SyntaxError modules are removed:
151            for mod_name in sys.modules.keys():
152                if mod_name not in prev_modules:
153                    del sys.modules[mod_name]
154            return self.make_error_app()
155
156    def make_error_app(self):
157        import traceback
158        exc_info = sys.exc_info()
159        error = ''.join(traceback.format_exception(*exc_info))
160        print >> sys.stderr, error
161
162        def app(environ, start_response):
163            start_response('500 Server Error',
164                           [('content-type', 'text/plain')])
165            return ['Error loading app:\n', error]
166        app.is_error = True
167        return app
168
169    def __call__(self, environ, start_response):
170        app = self.app
171        if getattr(app, 'is_error', False):
172            return app(environ, start_response)
173        environ['silverlining.devel'] = True
174        path_info = environ.get('PATH_INFO', '')
175        path_info = os.path.normpath(path_info)
176        path_info = path_info.replace('\\', '/').lstrip('/')
177        paths = [os.path.join(self.base_path, 'static', path_info)]
178        if self.writable_root:
179            paths.append(os.path.join(self.writable_root, path_info))
180        for path in paths:
181            if os.path.exists(path) and os.path.isdir(path) and os.path.exists(os.path.join(path, 'index.html')):
182                if not environ['PATH_INFO'].endswith('/'):
183                    start_response(
184                        '301 Moved Permanently',
185                        [('Location', environ.get('SCRIPT_NAME', '') + environ['PATH_INFO'] + '/')])
186                    return ['']
187                return self.serve_file(os.path.join(path, 'index.html'), environ, start_response)
188            if os.path.exists(path) and not os.path.isdir(path):
189                return self.serve_file(path, environ, start_response)
190        return app(environ, start_response)
191
192    def serve_file(self, path, environ, start_response):
193        """Serve a file.
194
195        This does not use any library because we want this server to
196        be library-agnostic.  It's not a great server (e.g., no cache
197        handling), but since this is only for development it should be
198        okay.
199        """
200        length = os.path.getsize(path)
201        type, encoding = mimetypes.guess_type(path)
202        if not type:
203            type = 'application/octet-stream'
204
205        def iterator():
206            fp = open(path, 'rb')
207            while 1:
208                chunk = fp.read(4096)
209                if not chunk:
210                    break
211                yield chunk
212            fp.close()
213        start_response('200 OK', [
214            ('Content-type', type),
215            ('Content-length', str(length))])
216        return iterator()
217
218
219def main(base_path):
220    app = CompoundApp(base_path)
221    httpserver = load_paste_httpserver()
222    host = os.environ.get('SILVER_SERVE_HOST', '127.0.0.1')
223    port = os.environ.get('SILVER_SERVE_PORT', '8080')
224    try:
225        httpserver.serve(app, host=host, port=port)
226    except KeyboardInterrupt:
227        pass
228
229if __name__ == '__main__':
230    base_path = sys.argv[1]
231    main(base_path)