A lightweight, fully spec-compliant JSON-RPC 2.0 handler.
It only deals with the parsing and handling of JSON-RPC requests, producing appropriate JSON-RPC responses.
It does not deal with the transport layer (e.g. HTTP, WebSockets, etc.)
Add this line to your application's Gemfile:
gem 'json_rpc_handler'And then execute:
bundleOr install it yourself as:
gem install json_rpc_handlerIf you already have a request object parsed from JSON, you can use the handle
method. Pass a block to the handle call to look up methods by name:
require 'json_rpc_handler'
request = {
jsonrpc: '2.0',
id: 1,
method: 'add',
params: {a: 1, b: 2},
}
result = JsonRpcHandler.handle(request) do |method_name|
case method_name
when 'add'
->(params) { params[:a] + params[:b] }
end
end
puts result.to_json
# {"jsonrpc":"2.0","id":1,"result":3}Returning a nil from the method lookup block will result in a method not found
error.
If the request JSON is not already parsed, you can use the handle_json method:
request_json = '{"jsonrpc": "2.0","id":1,"method":"add","params":{"a":1,"b":2}}'
result_json = JsonRpcHandler.handle_json(request_json) do |method_name|
case method_name
when 'add'
->(params) { params[:a] + params[:b] }
end
end
puts result_json
# {"jsonrpc":"2.0","id":1,"result":3}A nil from handle or handle_json means "no content" - either the
method returned nil, or all method calls of a batch request returned nil. It
is up to the integration to apply the appropriate transport-layer semantics
(e.g. returning a 204 No Content).
Unexpected exceptions from method lookup or a registered method return code
-32603 and message Internal error, with no data member.
Pass on_error: to handle or handle_json to report these exceptions. The hook
receives the exception and a context hash with id and method keys. It runs
before the internal error response is built, once for each failing batch item,
including notifications. The notification's id is nil.
methods = { 'add' => ->(params) { params[:a] + params[:b] } }
on_error = ->(exception, context) {
warn "#{context[:method]} #{context[:id].inspect}: #{exception.message}"
}
JsonRpcHandler.handle(request, on_error:) { |name| methods[name] }
JsonRpcHandler.handle_json(request_json, on_error:) { |name| methods[name] }The hook's return value is ignored. If it raises a StandardError, that error is
ignored without calling the hook again. The original request still gets the
internal error response, or no response for a notification.
For an explicit error response, raise JsonRpcHandler::Error, a StandardError
subclass. Its constructor takes required code: and message: keywords and an
optional data: keyword:
response = JsonRpcHandler.handle(request) do |name|
if name == 'add'
->(_params) {
raise JsonRpcHandler::Error.new(
code: -32001,
message: 'Try later',
data: { retry: true },
)
}
end
end
puts response.to_json
# {"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":"Try later","data":{"retry":true}}}The supplied code, message, and data become the response's error fields. A nil
data value is omitted; values such as false and 0 are kept. There is no
fallback from data to the exception message. These explicit errors do not call
on_error: and still produce no response for notifications. Other response
errors, such as Method not found and Parse error, keep their existing fields and
do not call the hook.
By default, string request IDs are validated to contain only alphanumeric characters, dashes, and underscores.
Note: The JSON-RPC 2.0 specification does not specify a default ID validation pattern, but this default validation is recommended to protect against XSS vulnerabilities when IDs are reflected in responses.
# Default behavior - accepts alphanumerics, dashes, underscores
request = { jsonrpc: '2.0', id: 'request-123_abc', method: 'add', params: {a: 1, b: 2} }
JsonRpcHandler.handle(request) { |method_name| ... }
# => {"jsonrpc":"2.0","id":"request-123_abc","result":3}
# Rejects potentially dangerous characters
request = { jsonrpc: '2.0', id: '<script>alert("xss")</script>', method: 'add', params: {a: 1, b: 2} }
JsonRpcHandler.handle(request) { |method_name| ... }
# => {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid Request","data":"Request ID must match validation pattern, or be an integer or null"}}You can customize the validation pattern by passing the id_validation_pattern
parameter:
# Allow email-like IDs with a custom pattern
custom_pattern = /\A[a-zA-Z0-9_.\-@]+\z/
request = { jsonrpc: '2.0', id: 'user@example.com', method: 'add', params: {a: 1, b: 2} }
JsonRpcHandler.handle(request, id_validation_pattern: custom_pattern) do |method_name|
# ...
end
# Also works with handle_json
JsonRpcHandler.handle_json(request_json, id_validation_pattern: custom_pattern) do |method_name|
# ...
endTo disable ID validation entirely (not recommended), pass
nil as the pattern:
# Accepts any string
JsonRpcHandler.handle(request, id_validation_pattern: nil) do |method_name|
# ...
endAfter checking out the repo:
- Run
bundleto install dependencies. - Run
rake testto run the tests, including code coverage.
To install this gem onto your local machine, run bundle exec rake install.
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/json-rpc-handler. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct. Read more about contributing here.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in this repository is expected to follow the Code of Conduct.