123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- """Command line runner.
- """
- import getopt
- import logging
- import os
- import os.path
- import re
- import sys
- from waitress import serve
- from waitress.adjustments import Adjustments
- from waitress.utilities import logger
- HELP = """\
- Usage:
- {0} [OPTS] MODULE:OBJECT
- Standard options:
- --help
- Show this information.
- --call
- Call the given object to get the WSGI application.
- --host=ADDR
- Hostname or IP address on which to listen, default is '0.0.0.0',
- which means "all IP addresses on this host".
- Note: May not be used together with --listen
- --port=PORT
- TCP port on which to listen, default is '8080'
- Note: May not be used together with --listen
- --listen=ip:port
- Tell waitress to listen on an ip port combination.
- Example:
- --listen=127.0.0.1:8080
- --listen=[::1]:8080
- --listen=*:8080
- This option may be used multiple times to listen on multiple sockets.
- A wildcard for the hostname is also supported and will bind to both
- IPv4/IPv6 depending on whether they are enabled or disabled.
- --[no-]ipv4
- Toggle on/off IPv4 support.
- Example:
- --no-ipv4
- This will disable IPv4 socket support. This affects wildcard matching
- when generating the list of sockets.
- --[no-]ipv6
- Toggle on/off IPv6 support.
- Example:
- --no-ipv6
- This will turn on IPv6 socket support. This affects wildcard matching
- when generating a list of sockets.
- --unix-socket=PATH
- Path of Unix socket. If a socket path is specified, a Unix domain
- socket is made instead of the usual inet domain socket.
- Not available on Windows.
- --unix-socket-perms=PERMS
- Octal permissions to use for the Unix domain socket, default is
- '600'.
- --url-scheme=STR
- Default wsgi.url_scheme value, default is 'http'.
- --url-prefix=STR
- The ``SCRIPT_NAME`` WSGI environment value. Setting this to anything
- except the empty string will cause the WSGI ``SCRIPT_NAME`` value to be
- the value passed minus any trailing slashes you add, and it will cause
- the ``PATH_INFO`` of any request which is prefixed with this value to
- be stripped of the prefix. Default is the empty string.
- --ident=STR
- Server identity used in the 'Server' header in responses. Default
- is 'waitress'.
- Tuning options:
- --threads=INT
- Number of threads used to process application logic, default is 4.
- --backlog=INT
- Connection backlog for the server. Default is 1024.
- --recv-bytes=INT
- Number of bytes to request when calling socket.recv(). Default is
- 8192.
- --send-bytes=INT
- Number of bytes to send to socket.send(). Default is 18000.
- Multiples of 9000 should avoid partly-filled TCP packets.
- --outbuf-overflow=INT
- A temporary file should be created if the pending output is larger
- than this. Default is 1048576 (1MB).
- --outbuf-high-watermark=INT
- The app_iter will pause when pending output is larger than this value
- and will resume once enough data is written to the socket to fall below
- this threshold. Default is 16777216 (16MB).
- --inbuf-overflow=INT
- A temporary file should be created if the pending input is larger
- than this. Default is 524288 (512KB).
- --connection-limit=INT
- Stop creating new channels if too many are already active.
- Default is 100.
- --cleanup-interval=INT
- Minimum seconds between cleaning up inactive channels. Default
- is 30. See '--channel-timeout'.
- --channel-timeout=INT
- Maximum number of seconds to leave inactive connections open.
- Default is 120. 'Inactive' is defined as 'has received no data
- from the client and has sent no data to the client'.
- --[no-]log-socket-errors
- Toggle whether premature client disconnect tracebacks ought to be
- logged. On by default.
- --max-request-header-size=INT
- Maximum size of all request headers combined. Default is 262144
- (256KB).
- --max-request-body-size=INT
- Maximum size of request body. Default is 1073741824 (1GB).
- --[no-]expose-tracebacks
- Toggle whether to expose tracebacks of unhandled exceptions to the
- client. Off by default.
- --asyncore-loop-timeout=INT
- The timeout value in seconds passed to asyncore.loop(). Default is 1.
- --asyncore-use-poll
- The use_poll argument passed to ``asyncore.loop()``. Helps overcome
- open file descriptors limit. Default is False.
- --channel-request-lookahead=INT
- Allows channels to stay readable and buffer more requests up to the
- given maximum even if a request is already being processed. This allows
- detecting if a client closed the connection while its request is being
- processed. Default is 0.
- """
- RUNNER_PATTERN = re.compile(
- r"""
- ^
- (?P<module>
- [a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*
- )
- :
- (?P<object>
- [a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*
- )
- $
- """,
- re.I | re.X,
- )
- def match(obj_name):
- matches = RUNNER_PATTERN.match(obj_name)
- if not matches:
- raise ValueError(f"Malformed application '{obj_name}'")
- return matches.group("module"), matches.group("object")
- def resolve(module_name, object_name):
- """Resolve a named object in a module."""
-
-
-
-
-
-
-
-
-
-
- segments = [str(segment) for segment in object_name.split(".")]
- obj = __import__(module_name, fromlist=segments[:1])
- for segment in segments:
- obj = getattr(obj, segment)
- return obj
- def show_help(stream, name, error=None):
- if error is not None:
- print(f"Error: {error}\n", file=stream)
- print(HELP.format(name), file=stream)
- def show_exception(stream):
- exc_type, exc_value = sys.exc_info()[:2]
- args = getattr(exc_value, "args", None)
- print(
- ("There was an exception ({}) importing your module.\n").format(
- exc_type.__name__,
- ),
- file=stream,
- )
- if args:
- print("It had these arguments: ", file=stream)
- for idx, arg in enumerate(args, start=1):
- print(f"{idx}. {arg}\n", file=stream)
- else:
- print("It had no arguments.", file=stream)
- def run(argv=sys.argv, _serve=serve):
- """Command line runner."""
- name = os.path.basename(argv[0])
- try:
- kw, args = Adjustments.parse_args(argv[1:])
- except getopt.GetoptError as exc:
- show_help(sys.stderr, name, str(exc))
- return 1
- if kw["help"]:
- show_help(sys.stdout, name)
- return 0
- if len(args) != 1:
- show_help(sys.stderr, name, "Specify one application only")
- return 1
-
-
-
- if logger.level == logging.NOTSET:
- logger.setLevel(logging.INFO)
- try:
- module, obj_name = match(args[0])
- except ValueError as exc:
- show_help(sys.stderr, name, str(exc))
- show_exception(sys.stderr)
- return 1
-
- sys.path.append(os.getcwd())
-
- try:
- app = resolve(module, obj_name)
- except ImportError:
- show_help(sys.stderr, name, f"Bad module '{module}'")
- show_exception(sys.stderr)
- return 1
- except AttributeError:
- show_help(sys.stderr, name, f"Bad object name '{obj_name}'")
- show_exception(sys.stderr)
- return 1
- if kw["call"]:
- app = app()
-
- del kw["call"], kw["help"]
- _serve(app, **kw)
- return 0
|