File size: 4,727 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
""" 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,
),
],
)
|