123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- r"""server is a module that enables using python through a web-server.
- This module can be used as the entry point to the application. In that case, it
- sets up a web-server.
- web-pages are determines by the command line arguments passed in.
- Use "--help" to list the supported arguments.
- """
- import argparse
- import asyncio
- import logging
- from wslink import websocket as wsl
- from wslink import backends
- ws_server = None
- # =============================================================================
- # Setup default arguments to be parsed
- # -s, --nosignalhandlers
- # -d, --debug
- # -i, --host localhost
- # -p, --port 8080
- # -t, --timeout 300 (seconds)
- # -c, --content '/www' (No content means WebSocket only)
- # -a, --authKey vtkweb-secret
- # =============================================================================
- def add_arguments(parser):
- """
- Add arguments known to this module. parser must be
- argparse.ArgumentParser instance.
- """
- parser.add_argument(
- "-d", "--debug", help="log debugging messages to stdout", action="store_true"
- )
- parser.add_argument(
- "-s",
- "--nosignalhandlers",
- help="Prevent installation of signal handlers so server can be started inside a thread.",
- action="store_true",
- )
- parser.add_argument(
- "-i",
- "--host",
- type=str,
- default="localhost",
- help="the interface for the web-server to listen on (default: 0.0.0.0)",
- )
- parser.add_argument(
- "-p",
- "--port",
- type=int,
- default=8080,
- help="port number for the web-server to listen on (default: 8080)",
- )
- parser.add_argument(
- "-t",
- "--timeout",
- type=int,
- default=300,
- help="timeout for reaping process on idle in seconds (default: 300s, 0 to disable)",
- )
- parser.add_argument(
- "-c",
- "--content",
- default="",
- help="root for web-pages to serve (default: none)",
- )
- parser.add_argument(
- "-a",
- "--authKey",
- default="wslink-secret",
- help="Authentication key for clients to connect to the WebSocket.",
- )
- parser.add_argument(
- "-ws",
- "--ws-endpoint",
- type=str,
- default="ws",
- dest="ws",
- help="Specify WebSocket endpoint. (e.g. foo/bar/ws, Default: ws)",
- )
- parser.add_argument(
- "--no-ws-endpoint",
- action="store_true",
- dest="nows",
- help="If provided, disables the websocket endpoint",
- )
- parser.add_argument(
- "--fs-endpoints",
- default="",
- dest="fsEndpoints",
- help="add another fs location to a specific endpoint (i.e: data=/Users/seb/Download|images=/Users/seb/Pictures)",
- )
- parser.add_argument(
- "--reverse-url",
- dest="reverse_url",
- help="Make the server act as a client to connect to a ws relay",
- )
- parser.add_argument(
- "--ssl",
- type=str,
- default="",
- dest="ssl",
- help="add a tuple file [certificate, key] (i.e: --ssl 'certificate,key') or adhoc string to generate temporary certificate (i.e: --ssl 'adhoc')",
- )
- return parser
- # =============================================================================
- # Parse arguments and start webserver
- # =============================================================================
- def start(argv=None, protocol=wsl.ServerProtocol, description="wslink web-server"):
- """
- Sets up the web-server using with __name__ == '__main__'. This can also be
- called directly. Pass the optional protocol to override the protocol used.
- Default is ServerProtocol.
- """
- parser = argparse.ArgumentParser(description=description)
- add_arguments(parser)
- args = parser.parse_args(argv)
- # configure protocol, if available
- try:
- protocol.configure(args)
- except AttributeError:
- pass
- start_webserver(options=args, protocol=protocol)
- # =============================================================================
- # Stop webserver
- # =============================================================================
- def stop_webserver():
- if ws_server:
- loop = asyncio.get_event_loop()
- return loop.create_task(ws_server.stop())
- # =============================================================================
- # Get webserver port (useful when 0 is provided and a dynamic one was picked)
- # =============================================================================
- def get_port():
- if ws_server:
- return ws_server.get_port()
- return -1
- # =============================================================================
- # Given a configuration file, create and return a webserver
- #
- # config = {
- # "host": "0.0.0.0",
- # "port": 8081
- # "ws": {
- # "/ws": serverProtocolInstance,
- # ...
- # },
- # static_routes: {
- # '/static': .../path/to/files,
- # ...
- # },
- # }
- # =============================================================================
- def create_webserver(server_config, backend="aiohttp"):
- return backends.create_webserver(server_config, backend=backend)
- # =============================================================================
- # Generate a webserver config from command line options, create a webserver,
- # and start it.
- # =============================================================================
- def start_webserver(
- options,
- protocol=wsl.ServerProtocol,
- disableLogging=False,
- backend="aiohttp",
- exec_mode="main",
- **kwargs,
- ):
- """
- Starts the web-server with the given protocol. Options must be an object
- with the following members:
- options.host : the interface for the web-server to listen on
- options.port : port number for the web-server to listen on
- options.timeout : timeout for reaping process on idle in seconds
- options.content : root for web-pages to serve.
- """
- global ws_server
- # Create default or custom ServerProtocol
- wslinkServer = protocol()
- if disableLogging:
- logging_level = None
- elif options.debug:
- logging_level = logging.DEBUG
- else:
- logging_level = logging.ERROR
- if options.reverse_url:
- server_config = {
- "reverse_url": options.reverse_url,
- "ws_protocol": wslinkServer,
- "logging_level": logging_level,
- }
- else:
- server_config = {
- "host": options.host,
- "port": options.port,
- "timeout": options.timeout,
- "logging_level": logging_level,
- }
- # Configure websocket endpoint
- if not options.nows:
- server_config["ws"] = {}
- server_config["ws"][options.ws] = wslinkServer
- # Configure default static route if --content requested
- if len(options.content) > 0:
- server_config["static"] = {}
- # Static HTTP + WebSocket
- server_config["static"]["/"] = options.content
- # Configure any other static routes
- if len(options.fsEndpoints) > 3:
- if "static" not in server_config:
- server_config["static"] = {}
- for fsResourceInfo in options.fsEndpoints.split("|"):
- infoSplit = fsResourceInfo.split("=")
- server_config["static"][infoSplit[0]] = infoSplit[1]
- # Confifugre SSL
- if len(options.ssl) > 0:
- from .ssl_context import generate_ssl_pair, ssl
- if options.ssl == "adhoc":
- options.ssl = generate_ssl_pair(server_config["host"])
- else:
- tokens = options.ssl.split(",")
- if len(tokens) != 2:
- raise Exception(
- f'ssl configure must be "adhoc" or a tuple of files "cert,key"'
- )
- options.ssl = tokens
- cert, key = options.ssl
- context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
- context.load_cert_chain(cert, key)
- server_config["ssl"] = context
- server_config["handle_signals"] = not options.nosignalhandlers
- # Create the webserver and start it
- ws_server = create_webserver(server_config, backend=backend)
- # Once we have python 3.7 minimum, we can start the server with asyncio.run()
- # asyncio.run(ws_server.start())
- # Until then, we can start the server this way
- loop = asyncio.get_event_loop()
- port_callback = None
- if hasattr(wslinkServer, "port_callback"):
- port_callback = wslinkServer.port_callback
- if hasattr(wslinkServer, "set_server"):
- wslinkServer.set_server(ws_server)
- def create_coroutine():
- return ws_server.start(port_callback)
- def main_exec():
- # Block until the loop finishes and then close the loop
- try:
- loop.run_until_complete(create_coroutine())
- finally:
- loop.close()
- def task_exec():
- return loop.create_task(create_coroutine())
- exec_modes = {
- "main": main_exec,
- "task": task_exec,
- "coroutine": create_coroutine,
- }
- if exec_mode not in exec_modes:
- raise Exception(f"Unknown exec_mode: {exec_mode}")
- return exec_modes[exec_mode]()
- if __name__ == "__main__":
- start()
|