Skip to content

Commit

Permalink
Support JSON-RPC 2.0 Notifications (messages without the id key) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
themylogin authored Jan 10, 2025
1 parent 6d60a76 commit 1ddc244
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 24 deletions.
53 changes: 30 additions & 23 deletions src/middlewared/middlewared/api/base/server/ws_handler/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from middlewared.service_exception import (CallException, CallError, ValidationError, ValidationErrors, adapt_exception,
get_errname)
from middlewared.utils.debug import get_frame_details
from middlewared.utils.lang import undefined
from middlewared.utils.limits import MsgSizeError, MsgSizeLimit, parse_message
from middlewared.utils.lock import SoftHardSemaphore, SoftHardSemaphoreLimit
from middlewared.utils.origin import ConnectionOrigin
Expand Down Expand Up @@ -268,7 +269,7 @@ async def validate_message(message: Any) -> None:
if not isinstance(message["id"], None | int | str):
raise ValueError("'id' member must be of type null, string or number")
except KeyError:
raise ValueError("Missing 'id' member")
pass

try:
if not isinstance(message["method"], str) or not message["method"]:
Expand All @@ -290,40 +291,44 @@ async def process_message(self, app: RpcWebSocketApp, message: Any):
# format of the message (i.e. needs to be a dict)
app.send_error(None, JSONRPCError.INVALID_REQUEST.value, "Invalid Message Format")
except ValueError as e:
app.send_error(message.get("id"), JSONRPCError.INVALID_REQUEST.value, str(e))
if (id_ := message.get("id", undefined)) != undefined:
app.send_error(id_, JSONRPCError.INVALID_REQUEST.value, str(e))
return

id_ = message.get("id", undefined)

try:
method = self.methods[message["method"]]
except KeyError:
app.send_error(
message["id"],
JSONRPCError.METHOD_NOT_FOUND.value,
"Method does not exist",
)
if id_ != undefined:
app.send_error(id_, JSONRPCError.METHOD_NOT_FOUND.value, "Method does not exist")
return

asyncio.ensure_future(
self.process_method_call(app, message["id"], method, message["params"])
self.process_method_call(app, id_, method, message["params"])
)

async def process_method_call(self, app: RpcWebSocketApp, id_: Any, method: Method, params: list):
try:
async with app.softhardsemaphore:
result = await method.call(app, params)
except SoftHardSemaphoreLimit as e:
app.send_error(id_, JSONRPCError.TRUENAS_TOO_MANY_CONCURRENT_CALLS.value,
f"Maximum number of concurrent calls ({e.args[0]}) has exceeded")
if id_ != undefined:
app.send_error(id_, JSONRPCError.TRUENAS_TOO_MANY_CONCURRENT_CALLS.value,
f"Maximum number of concurrent calls ({e.args[0]}) has exceeded")
except ValidationError as e:
app.send_truenas_validation_error(id_, sys.exc_info(), [
(e.attribute, e.errmsg, e.errno),
])
if id_ != undefined:
app.send_truenas_validation_error(id_, sys.exc_info(), [
(e.attribute, e.errmsg, e.errno),
])
except ValidationErrors as e:
app.send_truenas_validation_error(id_, sys.exc_info(), list(e))
if id_ != undefined:
app.send_truenas_validation_error(id_, sys.exc_info(), list(e))
except (CallException, Error) as e:
# CallException and subclasses are the way to gracefully send errors to the client
app.send_truenas_error(id_, JSONRPCError.TRUENAS_CALL_ERROR.value, "Method call error", e.errno, str(e),
sys.exc_info(), e.extra)
if id_ != undefined:
app.send_truenas_error(id_, JSONRPCError.TRUENAS_CALL_ERROR.value, "Method call error", e.errno, str(e),
sys.exc_info(), e.extra)
except Exception as e:
adapted = adapt_exception(e)
if adapted:
Expand All @@ -335,15 +340,17 @@ async def process_method_call(self, app: RpcWebSocketApp, id_: Any, method: Meth
error = e
extra = None

app.send_truenas_error(id_, JSONRPCError.TRUENAS_CALL_ERROR.value, "Method call error", errno_,
str(error) or repr(error), sys.exc_info(), extra)
if id_ != undefined:
app.send_truenas_error(id_, JSONRPCError.TRUENAS_CALL_ERROR.value, "Method call error", errno_,
str(error) or repr(error), sys.exc_info(), extra)

if not adapted and not app.py_exceptions:
self.middleware.logger.warning(f"Exception while calling {method.name}(*{method.dump_args(params)!r})",
exc_info=True)
else:
app.send({
"jsonrpc": "2.0",
"result": result,
"id": id_,
})
if id_ != undefined:
app.send({
"jsonrpc": "2.0",
"result": result,
"id": id_,
})
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
},
ValueError,
),
({"method": "test.method", "jsonrpc": "2.0", "params": ["a", "b"]}, ValueError),
# test "method" member
({"id": 1, "method": [], "jsonrpc": "2.0", "params": ["a", "b"]}, ValueError),
({"id": 1, "method": "", "jsonrpc": "2.0", "params": ["a", "b"]}, ValueError),
Expand Down

0 comments on commit 1ddc244

Please sign in to comment.