-
Notifications
You must be signed in to change notification settings - Fork 22
API v2: completely rewritten with EEPs 68, 43 #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
m-2k
wants to merge
2
commits into
zuiderkwast:master
Choose a base branch
from
m-2k:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,6 @@ priv | |
| *.o | ||
| *.beam | ||
| *.plt | ||
| _build/ | ||
| rebar3 | ||
| rebar.lock | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,160 +1,104 @@ | ||
| JSON-RPC 2.0 for Erlang | ||
| ======================= | ||
|
|
||
| Transport agnostic library for JSON-RPC 2.0 servers and clients. | ||
| Library for JSON-RPC 2.0 using JSON library (EEP 68) from stdlib, logger (OTP 21.0) and Maps (EEP 43). | ||
|
|
||
|
|
||
| Dependencies | ||
| ------------ | ||
|
|
||
| * Erlang/OTP 27.0+ | ||
|
|
||
| This page contains the manual for the server part, the `jsonrpc2` module. The client part has a | ||
| separate module `jsonrpc2_client`. Client docs are yet to be written. For documentation on the | ||
| client library, see the source code: [jsonrpc2_client.erl](src/jsonrpc2_client.erl). | ||
|
|
||
| Features | ||
| -------- | ||
|
|
||
| * can use any JSON encoder and decoder that supports the eep0018 style terms | ||
| format, | ||
| * transport neutral | ||
| * dispatches parsed requests to a simple callback function | ||
| * transport neutral; | ||
| * dispatches parsed requests to a simple callback function; | ||
| * supports an optional callback "map" function for batch requests, e.g. to | ||
| support concurrent processing of the requests in a batch, | ||
| * handles rpc calls and notifications, | ||
| * supports named and unnamed parameters, | ||
| support concurrent processing of the requests in a batch or collecting statistics; | ||
| * handles rpc calls and notifications; | ||
| * supports named and unnamed parameters; | ||
| * includes unit tests for all examples in the JSON-RPC 2.0 specification. | ||
|
|
||
| Example | ||
| Server examples | ||
| ------- | ||
|
|
||
| ``` erlang | ||
| 1> Json = <<"{\"jsonrpc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">>. | ||
| <<"{\"jsonroc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">> | ||
| 2> | ||
| 2> MyHandler = fun (<<"foo">>, Params) -> lists:reverse(Params); | ||
| 2> (_, _) -> throw(method_not_found) | ||
| 2> end. | ||
| #Fun<erl_eval.12.82930912> | ||
| 3> | ||
| 3> jsonrpc2:handle(Json, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1). | ||
| {reply,<<"{\"jsonrpc\":\"2.0\",\"result\":[3,2,1],\"id\":1}">>} | ||
| 4> | ||
| 4> jsonrpc2:handle(<<"dummy">>, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1). | ||
| {reply,<<"{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error.\"},\"id\":null}">>} | ||
| 5> | ||
| 5> jsonrpc2:handle(<<"{\"x\":42}">>, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1). | ||
| {reply,<<"{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid Request.\"},\"id\":null}">>} | ||
| ``` | ||
|
|
||
| Types | ||
| ----- | ||
|
|
||
| ```Erlang | ||
| json() :: true | false | null | binary() | [json()] | {[{binary(), json()}]}. | ||
|
|
||
| handlerfun() :: fun((method(), params()) -> json()). | ||
| method() :: binary(). | ||
| params() :: [json()] | {[{binary(), json()}]}. | ||
|
|
||
| mapfun() :: fun((fun((A) -> B), [A]) -> [B]). %% the same as lists:map/2 | ||
| ``` | ||
|
|
||
| Functions | ||
| --------- | ||
|
|
||
| Any of the `jsonrpc2:handle/2,3,4,5` functions can be used to handle JSON-RPC | ||
| request by delegating the actual procedure call to a handler callback function. | ||
| They all return `{reply, Data}` where Data is a result or an error response or | ||
| `noreply` when no response should be sent to the client. The handler callback | ||
| function must return a term that can be encoded to JSON using the | ||
| representation explained on the page https://github.com/davisp/jiffy#data-format, | ||
| as required by jiffy and other compatible JSON parses. | ||
|
|
||
| ```Erlang | ||
| handle(json(), handlerfun()) -> {reply, json()} | noreply | ||
| ``` | ||
|
|
||
| Handles decoded JSON and returns a reply as decoded JSON or noreply. Use | ||
| this if you want to handle JSON encoding separately. | ||
|
|
||
| ```Erlang | ||
| handle(json(), handlerfun(), mapfun()) -> {reply, json()} | noreply | ||
| ``` | ||
|
|
||
| Like `handle/2`, handles decoded JSON, but takes an extra | ||
| "map" function callback to be used instead of `lists:map/2` | ||
| for batch processing. The map function should be a function that behaves | ||
| similarly to `lists:map/2`, such as the `plists:map/2` | ||
| from the plists library for concurrent batch handling. | ||
|
|
||
| ```Erlang | ||
| handle(Req::term(), handlerfun(), JsonDecode::fun(), JsonEncode::fun()) -> | ||
| {reply, term()} | noreply | ||
| ``` | ||
|
|
||
| Handles JSON as binary or string. Uses the supplied functions | ||
| JsonDecode to parse the JSON request and JsonEncode to encode the reply as JSON. | ||
| ```erlang | ||
| 1> Body = ~'{"jsonrpc": "2.0", "method": "foo", "params": [1,2,3], "id": 1}'. | ||
| <<"{\"jsonrpc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">> | ||
|
|
||
| 2> Handler = fun(~"foo", Params) -> {ok, lists:reverse(Params)}; (_, _) -> {error, method_not_found} end. | ||
| #Fun<erl_eval.41.81571850> | ||
|
|
||
| 3> jsonrpc2:handle(Body, Handler). | ||
| {reply,["{", | ||
| [[34,<<"id">>,34],58|<<"1">>], | ||
| [[44, | ||
| [34,<<"result">>,34], | ||
| 58,91,<<"3">>,44,<<"2">>,44,<<"1">>,93], | ||
| [44,[34,<<"jsonrpc">>,34],58,34,<<"2.0">>,34]], | ||
| "}"]} | ||
|
|
||
| 4> jsonrpc2:handle(~"dummy", Handler). | ||
| =WARNING REPORT==== 10-Aug-2025::13:58:12.336132 === | ||
| Failed to decode request in JSON format: error {invalid_byte,100} | ||
| {reply,["{", | ||
| [[34,<<"error">>,34], | ||
| 58,"{", | ||
| [[34,<<"code">>,34],58|<<"-32700">>], | ||
| [[44,[34,<<"message">>,34],58,34,<<"Parse error.">>,34]], | ||
| "}"], | ||
| [[44,[34,<<"id">>,34],58|<<"null">>], | ||
| [44,[34,<<"jsonrpc">>,34],58,34,<<"2.0">>,34]], | ||
| "}"]} | ||
|
|
||
| 5> {reply, R} = jsonrpc2:handle(~'{"x": 42}', Handler). | ||
| {reply,["{", | ||
| [[34,<<"error">>,34], | ||
| 58,"{", | ||
| [[34,<<"code">>,34],58|<<"-32600">>], | ||
| [[44,[34,<<"message">>,34],58,34,<<"Invalid Request.">>,34]], | ||
| "}"], | ||
| [[44,[34,<<"id">>,34],58|<<"null">>], | ||
| [44,[34,<<"jsonrpc">>,34],58,34,<<"2.0">>,34]], | ||
| "}"]} | ||
|
|
||
| 6> iolist_to_binary(R). | ||
| <<"{\"error\":{\"code\":-32600,\"message\":\"Invalid Request.\"},\"id\":null,\"jsonrpc\":\"2.0\"}">> | ||
|
|
||
|
|
||
| 7> Term = #{~"jsonrpc" => ~"2.0", ~"method" => ~"foo", ~"params" => [1,2,3], ~"id" => 1}. | ||
| #{<<"id">> => 1,<<"jsonrpc">> => <<"2.0">>, | ||
| <<"method">> => <<"foo">>, | ||
| <<"params">> => [1,2,3]} | ||
|
|
||
| 8> jsonrpc2:handle_term(Term, Handler). | ||
| {reply,#{id => 1,result => [3,2,1],jsonrpc => <<"2.0">>}} | ||
|
|
||
| ```Erlang | ||
| handle(Req::term(), handlerfun(), mapfun(), JsonDecode::fun(), | ||
| JsonEncode::fun()) -> {reply, term()} | noreply | ||
| ``` | ||
|
|
||
| Like `handle/4`, but also takes a map function for batch | ||
| processing. See `handle/3` above. | ||
|
|
||
| Error Handling | ||
| Client example | ||
| -------------- | ||
|
|
||
| A requests that is not valid JSON results in a "Parse error" JSON-RPC response. | ||
|
|
||
| An invalid JSON-RPC request (though valid JSON) results in an "Invalid Request" | ||
| response. In these two cases the handler callback function is never called. | ||
|
|
||
| To produce an error response from the handler function, you may throw one of | ||
| the exceptions below. They will be caught and turned into a corresponding | ||
| JSON-RPC error response. | ||
|
|
||
| * `throw(method_not_found)` is reported as "Method not found" (-32601) | ||
| * `throw(invalid_params)` is reported as "Invalid params" (-32602) | ||
| * `throw(internal_error)` is reported as "Internal error" (-32603) | ||
| * `throw(server_error)` is reported as "Server error" (-32000) | ||
|
Comment on lines
-114
to
-117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning RPC errors from the handler using throw is not supported anymore? Why not? |
||
|
|
||
| If you also want to include `data` in the JSON-RPC error response, throw a pair | ||
| with the error type and the data, such as `{internal_error, Data}`. | ||
|
|
||
| For your own *application-defined errors*, it is possible to set a custom error | ||
| code by throwing a tuple with the atom `jsonrpc2`, an integer error code, a | ||
| binary message and optional data. | ||
|
|
||
| * `throw({jsonrpc2, Code, Message)` | ||
| * `throw({jsonrpc2, Code, Message, Data})` | ||
|
|
||
| If any other exception is thrown or an error occurs in the handler, this is | ||
| caught, an error message is logged (using the standard error logger | ||
| `error_logger:error_msg/2`) and an "Internal error" response is returned. | ||
| ```erlang | ||
| 1> RequestsSpec = [ | ||
| #{method => add, params => [7000, -77]}, | ||
| #{method => add, params => [-1, 900], id => 0} | ||
| ]. | ||
| [#{params => [7000,-77],method => add}, | ||
| #{id => 0,params => [-1,900],method => add}] | ||
|
|
||
| If you're working with already parsed JSON, i.e. you're using `handle/2` or | ||
| `handle/3`, you may want to produce an error message that you can use when the | ||
| client sends invalid JSON that can't be parsed. Use `jsonrpc2:parseerror()` to | ||
| create the appropriate error response for this purpose. | ||
| 2> TransportFn = fun(Body) -> {ok, {_,_,Resp}} = httpc:request(post, {"http://localhost:8080/rpc", [{"Content-Length", integer_to_binary(iolist_size(Body))}], "application/json", Body}, [], [{body_format, binary}]), Resp end. | ||
| #Fun<erl_eval.42.81571850> | ||
|
|
||
| Examples: | ||
| jsonrpc2_client:multi_call(RequestsSpec, TransportFn, 1). | ||
| [{ok,899,undefined}] | ||
|
|
||
| ```erlang | ||
| my_handler(<<"Foo">>, [X, Y]) when is_integer(X), is_integer(Y) -> | ||
| {[{<<"Foo says">>}, X + Y + 42}]}; | ||
| my_handler(<<"Foo">>, _SomeOtherParams) -> | ||
| throw(invalid_params); | ||
| my_handler(<<"Logout">>, [Username]) -> | ||
| throw({jsonrpc2, 123, <<"Not logged in">>}); | ||
| my_handler(_SomeOtherMethod, _) -> | ||
| throw(method_not_found). | ||
| ``` | ||
|
|
||
| Compatible JSON parsers | ||
| ----------------------- | ||
|
|
||
| * Jiffy, https://github.com/davisp/jiffy | ||
| * erlang-json, https://github.com/hio/erlang-json | ||
| * Mochijson2 using ```mochijson2:decode(Bin, [{format, eep18}])``` | ||
| * Probably more... | ||
|
|
||
| Links | ||
| ----- | ||
|
|
@@ -163,11 +107,13 @@ Links | |
| * rjsonrpc2, a "restricted" implementation of JSON-RPC 2.0, https://github.com/imprest/rjsonrpc2 | ||
| * ejrpc2, another JSON-RPC 2 library, https://github.com/jvliwanag/ejrpc2 | ||
|
|
||
|
|
||
| License | ||
| ------- | ||
|
|
||
| ``` | ||
| Copyright 2013-2014 Viktor Söderqvist | ||
| Copyright 2025 Andy (https://github.com/m-2k) | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
|
|
@@ -181,15 +127,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| ``` | ||
|
|
||
| **Author's note:** | ||
| The Apache 2.0 is a very permissive license just like MIT and BSD, but as | ||
| FSF notes, it includes "certain patent termination and indemnification | ||
| provisions", which is a good thing. We (the authours) cannot come to you | ||
| (the users) to claim any patents we might have on something in the code. | ||
|
|
||
| If you have any compatibility issues with this license, keep in mind that if | ||
| you're using this as an external dependency (e.g. with Rebar or Erlang.mk) | ||
| you're not actually distributing this dependency anyway. Even if you do | ||
| distribute dependencies, they are not actually linked together until they | ||
| are loaded and run in the BEAM unless you compile the release with HiPE. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you changed from
lists:map/3tolists:mapfoldl/3.To process requests in parallel, you could replace
lists:map/3with a function that spawns a process for each request, waits for all of them to complete, then returns the list of replies.How would you do that with the mapfoldl API?
Also, how would you collect statistics and get them out?