/silverlining/devel-runner.py

https://bitbucket.org/ianb/silverlining/ · Python · 231 lines · 183 code · 21 blank · 27 comment · 30 complexity · 80a56fa13359025c042c00d29be09916 MD5 · raw file

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