#-----------------------------------------------------------------------------# Copyright (c) 2012 - 2022, Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------''' Provide basic Bokeh server objects that use a Tornado ``HTTPServer`` and``BokeTornado`` Tornado Application to service Bokeh Server Applications.There are two public classes in this module::class:`~bokeh.server.server.BaseServer` This is a lightweight class to explicitly coordinate the components needed to run a Bokeh server (A :class:`~bokeh.server.tornado.BokehTornado` instance, and Tornado ``HTTPServer`` and a Tornado ``IOLoop``):class:`~bokeh.server.server.Server` This higher-level convenience class only needs to be configured with Bokeh :class:`~bokeh.application.application.Application` instances, and will automatically create and coordinate the lower level Tornado components.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsimportatexitimportsignalimportsocketimportsysfromtypesimportFrameTypefromtypingimport(TYPE_CHECKING,Any,Dict,List,Mapping,)# External importsfromtornadoimportversionastornado_versionfromtornado.httpserverimportHTTPServerfromtornado.ioloopimportIOLoop# Bokeh importsfrom..import__version__from..coreimportpropertiesaspfrom..core.propertiesimport(Bool,Int,Nullable,String,)from..resourcesimportDEFAULT_SERVER_PORTfrom..util.optionsimportOptionsfrom.tornadoimportDEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES,BokehTornadofrom.utilimportbind_sockets,create_hosts_allowlistifTYPE_CHECKING:from..application.applicationimportApplicationfrom..application.handlers.functionimportModifyDocfrom..core.typesimportIDfrom..util.browserimportBrowserTargetfrom.sessionimportServerSession#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('BaseServer','Server',)#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------
[docs]classBaseServer:''' Explicitly coordinate the level Tornado components required to run a Bokeh server: * A Tornado ``IOLoop`` to run the Bokeh server machinery. * a ``BokehTornado`` Tornado application that defines the Bokeh server machinery. * a Tornado ``HTTPServer`` to direct HTTP requests All three of these components must be passed to ``BaseServer``, which will initialize the ``BokehTornado`` instance on the ``io_loop``. The ``http_server`` must have been previously created and initialized with the ``BokehTornado`` instance. .. autoclasstoc:: '''
[docs]def__init__(self,io_loop:IOLoop,tornado_app:BokehTornado,http_server:HTTPServer)->None:''' Create a ``BaseServer`` instance. Args: io_loop (IOLoop) : A Tornado ``IOLoop`` to run the Bokeh Tornado application on. tornado_app (BokehTornado) : An instance of the Bokeh Tornado application that generates Bokeh Documents and Sessions. http_server (HTTPServer) : A Tornado ``HTTPServer`` to service HTTP requests for Bokeh applications. Should have already be configured with the ``tornado_app`` when created. '''self._started=Falseself._stopped=Falseself._http=http_serverself._loop=io_loopself._tornado=tornado_appself._tornado.initialize(io_loop)
@propertydefio_loop(self)->IOLoop:''' The Tornado ``IOLoop`` that this Bokeh Server is running on. '''returnself._loop
[docs]defstart(self)->None:''' Install the Bokeh Server and its background tasks on a Tornado ``IOLoop``. This method does *not* block and does *not* affect the state of the Tornado ``IOLoop`` You must start and stop the loop yourself, i.e. this method is typically useful when you are already explicitly managing an ``IOLoop`` yourself. To start a Bokeh server and immediately "run forever" in a blocking manner, see :func:`~bokeh.server.server.BaseServer.run_until_shutdown`. '''assertnotself._started,"Already started"self._started=Trueself._tornado.start()
[docs]defstop(self,wait:bool=True)->None:''' Stop the Bokeh Server. This stops and removes all Bokeh Server ``IOLoop`` callbacks, as well as stops the ``HTTPServer`` that this instance was configured with. Args: fast (bool): Whether to wait for orderly cleanup (default: True) Returns: None '''assertnotself._stopped,"Already stopped"self._stopped=Trueself._tornado.stop(wait)self._http.stop()
[docs]defunlisten(self)->None:''' Stop listening on ports. The server will no longer be usable after calling this function. .. note:: This function is mostly useful for tests Returns: None '''self._http.stop()self.io_loop.add_callback(self._http.close_all_connections)
[docs]defrun_until_shutdown(self)->None:''' Run the Bokeh Server until shutdown is requested by the user, either via a Keyboard interrupt (Ctrl-C) or SIGTERM. Calling this method will start the Tornado ``IOLoop`` and block all execution in the calling process. Returns: None '''ifnotself._started:self.start()# Install shutdown hooksatexit.register(self._atexit)signal.signal(signal.SIGTERM,self._sigterm)# type: ignore[arg-type]try:self._loop.start()exceptKeyboardInterrupt:print("\nInterrupted, shutting down")self.stop()
[docs]defget_session(self,app_path:str,session_id:ID)->ServerSession:''' Get an active a session by name application path and session ID. Args: app_path (str) : The configured application path for the application to return a session for. session_id (str) : The session ID of the session to retrieve. Returns: ServerSession '''returnself._tornado.get_session(app_path,session_id)
[docs]defget_sessions(self,app_path:str|None=None)->List[ServerSession]:''' Gets all currently active sessions for applications. Args: app_path (str, optional) : The configured application path for the application to return sessions for. If None, return active sessions for all applications. (default: None) Returns: list[ServerSession] '''ifapp_pathisnotNone:returnself._tornado.get_sessions(app_path)all_sessions:List[ServerSession]=[]forpathinself._tornado.app_paths:all_sessions+=self._tornado.get_sessions(path)returnall_sessions
[docs]defshow(self,app_path:str,browser:str|None=None,new:BrowserTarget="tab")->None:''' Opens an app in a browser window or tab. This method is useful for testing or running Bokeh server applications on a local machine but should not call when running Bokeh server for an actual deployment. Args: app_path (str) : the app path to open The part of the URL after the hostname:port, with leading slash. browser (str, optional) : browser to show with (default: None) For systems that support it, the **browser** argument allows specifying which browser to display in, e.g. "safari", "firefox", "opera", "windows-default" (see the :doc:`webbrowser <python:library/webbrowser>` module documentation in the standard lib for more details). new (str, optional) : window or tab (default: "tab") If ``new`` is 'tab', then opens a new tab. If ``new`` is 'window', then opens a new window. Returns: None '''ifnotapp_path.startswith("/"):raiseValueError("app_path must start with a /")address_string='localhost'ifself.addressisnotNoneandself.address!='':address_string=self.addressurl=f"http://{address_string}:{self.port}{self.prefix}{app_path}"frombokeh.util.browserimportviewview(url,browser=browser,new=new)
_atexit_ran=Falsedef_atexit(self)->None:ifself._atexit_ran:returnself._atexit_ran=Truelog.debug("Shutdown: cleaning up")ifnotself._stopped:self.stop(wait=False)def_sigterm(self,signum:signal.Signals,frame:FrameType)->None:print(f"Received signal {signum}, shutting down")# Tell self._loop.start() to return.self._loop.add_callback_from_signal(self._loop.stop)@propertydefport(self)->int:''' The configured port number that the server listens on for HTTP requests '''sock=next(sockforsockinself._http._sockets.values()ifsock.familyin(socket.AF_INET,socket.AF_INET6))returnsock.getsockname()[1]@propertydefaddress(self)->str|None:''' The configured address that the server listens on for HTTP requests '''sock=next(sockforsockinself._http._sockets.values()ifsock.familyin(socket.AF_INET,socket.AF_INET6))returnsock.getsockname()[0]@propertydefprefix(self)->str:''' The configured URL prefix to use for all Bokeh server paths. '''returnself._tornado.prefix@propertydefindex(self)->str|None:''' A path to a Jinja2 template to use for index at "/" '''returnself._tornado.index
#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------
[docs]classServer(BaseServer):''' A high level convenience class to run a Bokeh server. This class can automatically coordinate the three the base level components required to run a Bokeh server: * A Tornado ``IOLoop`` to run the Bokeh server machinery. * a ``BokehTornado`` Tornado application that defines the Bokeh server machinery. * a Tornado ``HTTPServer`` to direct HTTP requests This high level ``Server`` class has some limitations. In particular, it is not possible to set an explicit ``io_loop`` and ``num_procs`` other than 1 at the same time. To do that, it is necessary to use ``BaseServer`` and coordinate the three components above explicitly. .. autoclasstoc:: '''
[docs]def__init__(self,applications:Mapping[str,Application|ModifyDoc]|Application|ModifyDoc,io_loop:IOLoop|None=None,http_server_kwargs:Dict[str,Any]|None=None,**kwargs:Any)->None:''' Create a ``Server`` instance. Args: applications (dict[str, Application] or Application or callable) : A mapping from URL paths to Application instances, or a single Application to put at the root URL. The Application is a factory for Documents, with a new Document initialized for each Session. Each application is identified by a path that corresponds to a URL, like "/" or "/myapp" If a single Application is provided, it is mapped to the URL path "/" automatically. As a convenience, a callable may also be provided, in which an Application will be created for it using ``FunctionHandler``. io_loop (IOLoop, optional) : An explicit Tornado ``IOLoop`` to run Bokeh Server code on. If None, ``IOLoop.current()`` will be used (default: None) http_server_kwargs (dict, optional) : Extra arguments passed to ``tornado.httpserver.HTTPServer``. E.g. ``max_buffer_size`` to specify the maximum upload size. More details can be found at: http://www.tornadoweb.org/en/stable/httpserver.html#http-server If None, no extra arguments are passed (default: None) Additionally, the following options may be passed to configure the operation of ``Server``: .. bokeh-options:: _ServerOpts :module: bokeh.server.server Any remaining keyword arguments will be passed as-is to ``BokehTornado``. '''log.info("Starting Bokeh server version %s (running on Tornado %s)"%(__version__,tornado_version))opts=_ServerOpts(kwargs)ifopts.num_procs>1andio_loopisnotNone:raiseRuntimeError("Setting both num_procs and io_loop in Server is incompatible. Use BaseServer to coordinate an explicit IOLoop and multi-process HTTPServer")ifopts.num_procs>1andsys.platform=="win32":raiseRuntimeError("num_procs > 1 not supported on Windows")ifhttp_server_kwargsisNone:http_server_kwargs={}http_server_kwargs.setdefault('xheaders',opts.use_xheaders)ifopts.ssl_certfile:log.info("Configuring for SSL termination")importsslcontext=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)context.load_cert_chain(certfile=opts.ssl_certfile,keyfile=opts.ssl_keyfile,password=opts.ssl_password)http_server_kwargs['ssl_options']=contextsockets,self._port=bind_sockets(opts.address,opts.port)self._address=opts.addressextra_websocket_origins=create_hosts_allowlist(opts.allow_websocket_origin,self.port)try:tornado_app=BokehTornado(applications,extra_websocket_origins=extra_websocket_origins,prefix=opts.prefix,index=opts.index,websocket_max_message_size_bytes=opts.websocket_max_message_size,**kwargs)ifopts.num_procs!=1:assertall(app_context.application.safe_to_forkforapp_contextintornado_app.applications.values()),('User application code has run before attempting to start ''multiple processes. This is considered an unsafe operation.')http_server=HTTPServer(tornado_app,**http_server_kwargs)http_server.start(opts.num_procs)http_server.add_sockets(sockets)exceptException:forsinsockets:s.close()raise# Can only refer to IOLoop after HTTPServer.start() is called, see #5524ifio_loopisNone:io_loop=IOLoop.current()super().__init__(io_loop,tornado_app,http_server)
@propertydefport(self)->int:''' The configured port number that the server listens on for HTTP requests. '''returnself._port@propertydefaddress(self)->str|None:''' The configured address that the server listens on for HTTP requests. '''returnself._address
#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------# This class itself is intentionally undocumented (it is used to generate# documentation elsewhere)class_ServerOpts(Options):num_procs:int=Int(default=1,help=""" The number of worker processes to start for the HTTP server. If an explicit ``io_loop`` is also configured, then ``num_procs=1`` is the only compatible value. Use ``BaseServer`` to coordinate an explicit ``IOLoop`` with a multi-process HTTP server. A value of 0 will auto detect number of cores. Note that due to limitations inherent in Tornado, Windows does not support ``num_procs`` values greater than one! In this case consider running multiple Bokeh server instances behind a load balancer. """)# type: ignore[assignment]address:str|None=Nullable(String,help=""" The address the server should listen on for HTTP requests. """)# type: ignore[assignment]port:int=Int(default=DEFAULT_SERVER_PORT,help=""" The port number the server should listen on for HTTP requests. """)# type: ignore[assignment]prefix:str=String(default="",help=""" A URL prefix to use for all Bokeh server paths. """)# type: ignore[assignment]index:str|None=Nullable(String,help=""" A path to a Jinja2 template to use for the index "/" """)# type: ignore[assignment]allow_websocket_origin:List[str]|None=Nullable(p.List(String),help=""" A list of hosts that can connect to the websocket. This is typically required when embedding a Bokeh server app in an external web site using :func:`~bokeh.embed.server_document` or similar. If None, "localhost" is used. """)# type: ignore[assignment]use_xheaders:bool=Bool(default=False,help=""" Whether to have the Bokeh server override the remote IP and URI scheme and protocol for all requests with ``X-Real-Ip``, ``X-Forwarded-For``, ``X-Scheme``, ``X-Forwarded-Proto`` headers (if they are provided). """)# type: ignore[assignment]ssl_certfile:str|None=Nullable(String,help=""" The path to a certificate file for SSL termination. """)# type: ignore[assignment]ssl_keyfile:str|None=Nullable(String,help=""" The path to a private key file for SSL termination. """)# type: ignore[assignment]ssl_password:str|None=Nullable(String,help=""" A password to decrypt the SSL keyfile, if necessary. """)# type: ignore[assignment]websocket_max_message_size:int=Int(default=DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES,help=""" Set the Tornado ``websocket_max_message_size`` value. """)# type: ignore[assignment]#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------