Michael Merickel
2017-03-30 3e489b740b1836c81af240cba579245ab18177da
commit | author | age
0e0cb7 1 # (c) 2005 Ian Bicking and contributors; written for Paste
CM 2 # (http://pythonpaste.org) Licensed under the MIT license:
eb0432 3 # http://www.opensource.org/licenses/mit-license.php
CM 4 #
5 # For discussion of daemonizing:
6 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
7 #
8 # Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
9 # lib/site.py
0e0cb7 10
6e14f6 11 import argparse
0e0cb7 12 import os
2e6d63 13 import re
CM 14 import sys
d58614 15 import textwrap
38fc72 16 import threading
MM 17 import time
18 import webbrowser
2e6d63 19
1b7d85 20 import hupper
160865 21
bc37a5 22 from pyramid.compat import PY2
dfdc25 23
3e489b 24 from pyramid.scripts.common import get_config_loader
49fb77 25 from pyramid.scripts.common import parse_vars
9cab0e 26 from pyramid.path import AssetResolver
3f1309 27 from pyramid.settings import aslist
0e0cb7 28
9845f1 29
eac060 30 def main(argv=sys.argv, quiet=False):
JPG 31     command = PServeCommand(argv, quiet=quiet)
d58614 32     return command.run()
9845f1 33
0e0cb7 34
2e6d63 35 class PServeCommand(object):
0e0cb7 36
CM 37     description = """\
442c1e 38     This command serves a web application that uses a PasteDeploy
0e0cb7 39     configuration file for the server and application.
CM 40
41     You can also include variable assignments like 'http_port=8080'
42     and then use %(http_port)s in your config files.
43     """
a4c67f 44     default_verbosity = 1
0e0cb7 45
6e14f6 46     parser = argparse.ArgumentParser(
df57ec 47         description=textwrap.dedent(description),
c9b2fa 48         formatter_class=argparse.RawDescriptionHelpFormatter,
d58614 49         )
6e14f6 50     parser.add_argument(
0e0cb7 51         '-n', '--app-name',
CM 52         dest='app_name',
53         metavar='NAME',
54         help="Load the named application (default main)")
6e14f6 55     parser.add_argument(
0e0cb7 56         '-s', '--server',
CM 57         dest='server',
58         metavar='SERVER_TYPE',
59         help="Use the named server.")
6e14f6 60     parser.add_argument(
0e0cb7 61         '--server-name',
CM 62         dest='server_name',
63         metavar='SECTION_NAME',
2e6d63 64         help=("Use the named server as defined in the configuration file "
CM 65               "(default: main)"))
6e14f6 66     parser.add_argument(
0e0cb7 67         '--reload',
CM 68         dest='reload',
69         action='store_true',
70         help="Use auto-restart file monitor")
6e14f6 71     parser.add_argument(
0e0cb7 72         '--reload-interval',
CM 73         dest='reload_interval',
74         default=1,
2e6d63 75         help=("Seconds between checking files (low number can cause "
CM 76               "significant CPU usage)"))
6e14f6 77     parser.add_argument(
8dd970 78         '-b', '--browser',
MA 79         dest='browser',
80         action='store_true',
50cebd 81         help=("Open a web browser to the server url. The server url is "
MM 82               "determined from the 'open_url' setting in the 'pserve' "
83               "section of the configuration file."))
6e14f6 84     parser.add_argument(
a4c67f 85         '-v', '--verbose',
JPG 86         default=default_verbosity,
87         dest='verbose',
abed94 88         action='count',
25c64c 89         help="Set verbose level (default " + str(default_verbosity) + ")")
6e14f6 90     parser.add_argument(
abed94 91         '-q', '--quiet',
JPG 92         action='store_const',
93         const=0,
94         dest='verbose',
95         help="Suppress verbose output")
6e14f6 96     parser.add_argument(
SP 97         'config_uri',
9de2cd 98         nargs='?',
SP 99         default=None,
6e14f6 100         help='The URI to the configuration file.',
SP 101         )
9de2cd 102     parser.add_argument(
a86675 103         'config_vars',
9de2cd 104         nargs='*',
SP 105         default=(),
b5faf2 106         help="Variables required by the config file. For example, "
SP 107              "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
108              "passed here.",
109         )
9de2cd 110
3e489b 111     _get_config_loader = staticmethod(get_config_loader)  # for testing
3f1309 112
50cebd 113     open_url = None
MM 114
0e0cb7 115     _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
CM 116
eac060 117     def __init__(self, argv, quiet=False):
6e14f6 118         self.args = self.parser.parse_args(argv[1:])
eac060 119         if quiet:
6e14f6 120             self.args.verbose = 0
d64e8d 121         if self.args.reload:
RH 122             self.worker_kwargs = {'argv': argv, "quiet": quiet}
50cebd 123         self.watch_files = set()
0e0cb7 124
9845f1 125     def out(self, msg):  # pragma: no cover
6e14f6 126         if self.args.verbose > 0:
eac060 127             print(msg)
2e6d63 128
3e489b 129     def get_config_path(self, loader):
MM 130         return os.path.abspath(loader.uri.path)
3f1309 131
3e489b 132     def pserve_file_config(self, loader, global_conf=None):
MM 133         settings = loader.get_settings('pserve', global_conf)
134         config_path = self.get_config_path(loader)
135         here = os.path.dirname(config_path)
136         watch_files = aslist(settings.get('watch_files', ''), flatten=False)
3f1309 137
MM 138         # track file paths relative to the ini file
9cab0e 139         resolver = AssetResolver(package=None)
3f1309 140         for file in watch_files:
9cab0e 141             if ':' in file:
MM 142                 file = resolver.resolve(file).abspath()
143             elif not os.path.isabs(file):
144                 file = os.path.join(here, file)
50cebd 145             self.watch_files.add(os.path.abspath(file))
MM 146
248669 147         # attempt to determine the url of the server
3e489b 148         open_url = settings.get('open_url')
50cebd 149         if open_url:
MM 150             self.open_url = open_url
248669 151
3e489b 152     def guess_server_url(self, loader, server_name, global_conf=None):
248669 153         server_name = server_name or 'main'
3e489b 154         settings = loader.get_settings('server:' + server_name, global_conf)
MM 155         if 'port' in settings:
156             return 'http://127.0.0.1:{port}'.format(**settings)
49fb77 157
50a8a0 158     def run(self):  # pragma: no cover
9de2cd 159         if not self.args.config_uri:
442c1e 160             self.out('You must give a config file')
d58614 161             return 2
3e489b 162         config_uri = self.args.config_uri
MM 163         config_vars = parse_vars(self.args.config_vars)
9de2cd 164         app_spec = self.args.config_uri
6e14f6 165         app_name = self.args.app_name
49fb77 166
3e489b 167         loader = self._get_config_loader(config_uri)
MM 168         loader.setup_logging(config_vars)
169
170         self.pserve_file_config(loader, global_conf=config_vars)
171
6e14f6 172         server_name = self.args.server_name
SP 173         if self.args.server:
0e0cb7 174             server_spec = 'egg:pyramid'
CM 175             assert server_name is None
6e14f6 176             server_name = self.args.server
0e0cb7 177         else:
CM 178             server_spec = app_spec
38fc72 179
3e489b 180         server_loader = loader
MM 181         if server_spec != app_spec:
182             server_loader = self.get_config_loader(server_spec)
183
38fc72 184         # do not open the browser on each reload so check hupper first
6e14f6 185         if self.args.browser and not hupper.is_active():
50cebd 186             url = self.open_url
248669 187
3e489b 188             if not url:
MM 189                 url = self.guess_server_url(
190                     server_loader, server_name, config_vars)
248669 191
50cebd 192             if not url:
MM 193                 self.out('WARNING: could not determine the server\'s url to '
194                          'open the browser. To fix this set the "open_url" '
195                          'setting in the [pserve] section of the '
196                          'configuration file.')
197
198             else:
199                 def open_browser():
200                     time.sleep(1)
201                     webbrowser.open(url)
202                 t = threading.Thread(target=open_browser)
203                 t.setDaemon(True)
204                 t.start()
38fc72 205
6e14f6 206         if self.args.reload and not hupper.is_active():
SP 207             if self.args.verbose > 1:
38fc72 208                 self.out('Running reloading file monitor')
MM 209             hupper.start_reloader(
210                 'pyramid.scripts.pserve.main',
6e14f6 211                 reload_interval=int(self.args.reload_interval),
SP 212                 verbose=self.args.verbose,
d64e8d 213                 worker_kwargs=self.worker_kwargs
38fc72 214             )
MM 215             return 0
216
3e489b 217         config_path = self.get_config_path(loader)
MM 218         self.watch_files.add(config_path)
219
220         server_path = self.get_config_path(server_loader)
221         self.watch_files.add(server_path)
3f1309 222
38fc72 223         if hupper.is_active():
MM 224             reloader = hupper.get_reloader()
50cebd 225             reloader.watch_files(list(self.watch_files))
0e0cb7 226
3e489b 227         server = server_loader.get_wsgi_server(server_name, config_vars)
49fb77 228
3e489b 229         app = loader.get_wsgi_app(app_name, config_vars)
0e0cb7 230
6e14f6 231         if self.args.verbose > 0:
0e0cb7 232             if hasattr(os, 'getpid'):
CM 233                 msg = 'Starting server in PID %i.' % os.getpid()
234             else:
235                 msg = 'Starting server.'
2e6d63 236             self.out(msg)
0e0cb7 237
9cab0e 238         try:
MM 239             server(app)
240         except (SystemExit, KeyboardInterrupt) as e:
6e14f6 241             if self.args.verbose > 1:
9cab0e 242                 raise
MM 243             if str(e):
244                 msg = ' ' + str(e)
245             else:
246                 msg = ''
247             self.out('Exiting%s (-v to see traceback)' % msg)
2e6d63 248
9845f1 249
0e0cb7 250 # For paste.deploy server instantiation (egg:pyramid#wsgiref)
9845f1 251 def wsgiref_server_runner(wsgi_app, global_conf, **kw):  # pragma: no cover
0e0cb7 252     from wsgiref.simple_server import make_server
CM 253     host = kw.get('host', '0.0.0.0')
254     port = int(kw.get('port', 8080))
255     server = make_server(host, port, wsgi_app)
256     print('Starting HTTP server on http://%s:%s' % (host, port))
257     server.serve_forever()
a0e3cb 258
9845f1 259
a0e3cb 260 # For paste.deploy server instantiation (egg:pyramid#cherrypy)
CM 261 def cherrypy_server_runner(
262         app, global_conf=None, host='127.0.0.1', port=None,
263         ssl_pem=None, protocol_version=None, numthreads=None,
264         server_name=None, max=None, request_queue_size=None,
265         timeout=None
9845f1 266         ):  # pragma: no cover
a0e3cb 267     """
CM 268     Entry point for CherryPy's WSGI server
269
270     Serves the specified WSGI app via CherryPyWSGIServer.
271
272     ``app``
273
274         The WSGI 'application callable'; multiple WSGI applications
275         may be passed as (script_name, callable) pairs.
276
277     ``host``
278
279         This is the ipaddress to bind to (or a hostname if your
280         nameserver is properly configured).  This defaults to
281         127.0.0.1, which is not a public interface.
282
283     ``port``
284
285         The port to run on, defaults to 8080 for HTTP, or 4443 for
286         HTTPS. This can be a string or an integer value.
287
288     ``ssl_pem``
289
290         This an optional SSL certificate file (via OpenSSL) You can
291         generate a self-signed test PEM certificate file as follows:
292
293             $ openssl genrsa 1024 > host.key
294             $ chmod 400 host.key
295             $ openssl req -new -x509 -nodes -sha1 -days 365  \\
296                           -key host.key > host.cert
297             $ cat host.cert host.key > host.pem
298             $ chmod 400 host.pem
299
300     ``protocol_version``
301
302         The protocol used by the server, by default ``HTTP/1.1``.
303
304     ``numthreads``
305
306         The number of worker threads to create.
307
308     ``server_name``
309
310         The string to set for WSGI's SERVER_NAME environ entry.
311
312     ``max``
313
314         The maximum number of queued requests. (defaults to -1 = no
315         limit).
316
317     ``request_queue_size``
318
319         The 'backlog' argument to socket.listen(); specifies the
320         maximum number of queued connections.
321
322     ``timeout``
323
324         The timeout in seconds for accepted connections.
325     """
326     is_ssl = False
327     if ssl_pem:
328         port = port or 4443
329         is_ssl = True
330
331     if not port:
332         if ':' in host:
333             host, port = host.split(':', 1)
334         else:
335             port = 8080
336     bind_addr = (host, int(port))
337
338     kwargs = {}
339     for var_name in ('numthreads', 'max', 'request_queue_size', 'timeout'):
340         var = locals()[var_name]
341         if var is not None:
342             kwargs[var_name] = int(var)
343
344     from cherrypy import wsgiserver
345
346     server = wsgiserver.CherryPyWSGIServer(bind_addr, app,
347                                            server_name=server_name, **kwargs)
d6e8b8 348     if ssl_pem is not None:
bc37a5 349         if PY2:
d6e8b8 350             server.ssl_certificate = server.ssl_private_key = ssl_pem
TL 351         else:
7319ab 352             # creates wsgiserver.ssl_builtin as side-effect
MM 353             wsgiserver.get_ssl_adapter_class()
354             server.ssl_adapter = wsgiserver.ssl_builtin.BuiltinSSLAdapter(
355                 ssl_pem, ssl_pem)
d6e8b8 356
a0e3cb 357     if protocol_version:
CM 358         server.protocol = protocol_version
359
360     try:
361         protocol = is_ssl and 'https' or 'http'
362         if host == '0.0.0.0':
d20324 363             print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' %
a0e3cb 364                   (port, protocol, port))
CM 365         else:
366             print('serving on %s://%s:%s' % (protocol, host, port))
367         server.start()
368     except (KeyboardInterrupt, SystemExit):
369         server.stop()
370
371     return server
40d54e 372
9845f1 373
RH 374 if __name__ == '__main__':  # pragma: no cover
40d54e 375     sys.exit(main() or 0)