You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
""" tornado handler for managing and communicating with language servers
|
|
"""
|
|
|
|
from typing import Optional, Text
|
|
|
|
from jupyter_core.utils import ensure_async
|
|
from jupyter_server.base.handlers import APIHandler, JupyterHandler
|
|
from jupyter_server.utils import url_path_join as ujoin
|
|
from tornado import web
|
|
from tornado.websocket import WebSocketHandler
|
|
|
|
try:
|
|
from jupyter_server.auth.decorator import authorized
|
|
except ImportError:
|
|
|
|
def authorized(method): # type: ignore
|
|
"""A no-op fallback for `jupyter_server 1.x`"""
|
|
return method
|
|
|
|
|
|
try:
|
|
from jupyter_server.base.websocket import WebSocketMixin
|
|
except ImportError:
|
|
from jupyter_server.base.zmqhandlers import WebSocketMixin
|
|
|
|
from .manager import LanguageServerManager
|
|
from .schema import SERVERS_RESPONSE
|
|
from .specs.utils import censored_spec
|
|
|
|
AUTH_RESOURCE = "lsp"
|
|
|
|
|
|
class BaseHandler(APIHandler):
|
|
manager = None # type: LanguageServerManager
|
|
|
|
def initialize(self, manager: LanguageServerManager):
|
|
self.manager = manager
|
|
|
|
|
|
class BaseJupyterHandler(JupyterHandler):
|
|
manager = None # type: LanguageServerManager
|
|
|
|
def initialize(self, manager: LanguageServerManager):
|
|
self.manager = manager
|
|
|
|
|
|
class LanguageServerWebSocketHandler( # type: ignore
|
|
WebSocketMixin, WebSocketHandler, BaseJupyterHandler
|
|
):
|
|
"""Setup tornado websocket to route to language server sessions.
|
|
|
|
The logic of `get` and `pre_get` methods is derived from jupyter-server ws handlers,
|
|
and should be kept in sync to follow best practice established by upstream; see:
|
|
https://github.com/jupyter-server/jupyter_server/blob/v2.12.5/jupyter_server/services/kernels/websocket.py#L36
|
|
"""
|
|
|
|
auth_resource = AUTH_RESOURCE
|
|
|
|
language_server: Optional[Text] = None
|
|
|
|
async def pre_get(self):
|
|
"""Handle a pre_get."""
|
|
# authenticate first
|
|
# authenticate the request before opening the websocket
|
|
user = self.current_user
|
|
if user is None:
|
|
self.log.warning("Couldn't authenticate WebSocket connection")
|
|
raise web.HTTPError(403)
|
|
|
|
if not hasattr(self, "authorizer"):
|
|
return
|
|
|
|
# authorize the user.
|
|
is_authorized = await ensure_async(
|
|
self.authorizer.is_authorized(self, user, "execute", AUTH_RESOURCE)
|
|
)
|
|
if not is_authorized:
|
|
raise web.HTTPError(403)
|
|
|
|
async def get(self, *args, **kwargs):
|
|
"""Get an event socket."""
|
|
await self.pre_get()
|
|
res = super().get(*args, **kwargs)
|
|
if res is not None:
|
|
await res
|
|
|
|
async def open(self, language_server):
|
|
await self.manager.ready()
|
|
self.language_server = language_server
|
|
self.manager.subscribe(self)
|
|
self.log.debug("[{}] Opened a handler".format(self.language_server))
|
|
super().open()
|
|
|
|
async def on_message(self, message):
|
|
self.log.debug("[{}] Handling a message".format(self.language_server))
|
|
await self.manager.on_client_message(message, self)
|
|
|
|
def on_close(self):
|
|
self.manager.unsubscribe(self)
|
|
self.log.debug("[{}] Closed a handler".format(self.language_server))
|
|
|
|
|
|
class LanguageServersHandler(BaseHandler):
|
|
"""Reports the status of all current servers
|
|
|
|
Response should conform to schema in schema/servers.schema.json
|
|
"""
|
|
|
|
auth_resource = AUTH_RESOURCE
|
|
validator = SERVERS_RESPONSE
|
|
|
|
@web.authenticated
|
|
@authorized
|
|
async def get(self):
|
|
"""finish with the JSON representations of the sessions"""
|
|
await self.manager.ready()
|
|
|
|
response = {
|
|
"version": 2,
|
|
"sessions": {
|
|
language_server: session.to_json()
|
|
for language_server, session in self.manager.sessions.items()
|
|
},
|
|
"specs": {
|
|
key: censored_spec(spec)
|
|
for key, spec in self.manager.all_language_servers.items()
|
|
},
|
|
}
|
|
|
|
errors = list(self.validator.iter_errors(response))
|
|
|
|
if errors: # pragma: no cover
|
|
self.log.warning("{} validation errors: {}".format(len(errors), errors))
|
|
|
|
self.finish(response)
|
|
|
|
|
|
def add_handlers(nbapp):
|
|
"""Add Language Server routes to the notebook server web application"""
|
|
lsp_url = ujoin(nbapp.base_url, "lsp")
|
|
re_langservers = "(?P<language_server>.*)"
|
|
|
|
opts = {"manager": nbapp.language_server_manager}
|
|
|
|
nbapp.web_app.add_handlers(
|
|
".*",
|
|
[
|
|
(ujoin(lsp_url, "status"), LanguageServersHandler, opts),
|
|
(
|
|
ujoin(lsp_url, "ws", re_langservers),
|
|
LanguageServerWebSocketHandler,
|
|
opts,
|
|
),
|
|
],
|
|
)
|