changed
README.md
|
@@ -90,12 90,20 @@ end
|
90
90
|
|
91
91
|
If you plan to use the HTTP(S) server, you also need to add `plug` and `cowboy` to your deps/apps.
|
92
92
|
|
93
|
For Cowboy 1.x
|
93
94
|
```elixir
|
94
95
|
def deps do
|
95
96
|
[..., {:plug, "~> 1.3"}, {:cowboy, "~> 1.1"}]
|
96
97
|
end
|
97
98
|
```
|
98
99
|
|
100
|
For Cowboy 2.x
|
101
|
```elixir
|
102
|
def deps do
|
103
|
[..., {:plug, "~> 1.3"}, {:cowboy, "~> 2.4"}]
|
104
|
end
|
105
|
```
|
106
|
|
99
107
|
```elixir
|
100
108
|
def application do
|
101
109
|
[applications: [..., :plug, :cowboy]]
|
changed
hex_metadata.config
|
@@ -3,12 3,17 @@
|
3
3
|
{<<"description">>,<<"JSON-RPC 2.0 for Elixir.">>}.
|
4
4
|
{<<"elixir">>,<<"~> 1.3">>}.
|
5
5
|
{<<"files">>,
|
6
|
- [<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"lib/jsonrpc2.ex">>,
|
7
|
- <<"lib/jsonrpc2/clients/http.ex">>,<<"lib/jsonrpc2/clients/tcp.ex">>,
|
6
|
[<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"lib">>,<<"lib/jsonrpc2">>,
|
7
|
<<"lib/jsonrpc2.ex">>,<<"lib/jsonrpc2/clients">>,
|
8
|
<<"lib/jsonrpc2/clients/http.ex">>,<<"lib/jsonrpc2/clients/tcp">>,
|
9
|
<<"lib/jsonrpc2/clients/tcp.ex">>,
|
8
10
|
<<"lib/jsonrpc2/clients/tcp/protocol.ex">>,<<"lib/jsonrpc2/request.ex">>,
|
9
|
- <<"lib/jsonrpc2/response.ex">>,<<"lib/jsonrpc2/serializers/jiffy.ex">>,
|
10
|
- <<"lib/jsonrpc2/server/handler.ex">>,<<"lib/jsonrpc2/servers/http.ex">>,
|
11
|
- <<"lib/jsonrpc2/servers/http/plug.ex">>,<<"lib/jsonrpc2/servers/tcp.ex">>,
|
11
|
<<"lib/jsonrpc2/response.ex">>,<<"lib/jsonrpc2/serializers">>,
|
12
|
<<"lib/jsonrpc2/serializers/jiffy.ex">>,<<"lib/jsonrpc2/server">>,
|
13
|
<<"lib/jsonrpc2/server/handler.ex">>,<<"lib/jsonrpc2/servers">>,
|
14
|
<<"lib/jsonrpc2/servers/http">>,<<"lib/jsonrpc2/servers/http.ex">>,
|
15
|
<<"lib/jsonrpc2/servers/http/plug.ex">>,<<"lib/jsonrpc2/servers/tcp">>,
|
16
|
<<"lib/jsonrpc2/servers/tcp.ex">>,
|
12
17
|
<<"lib/jsonrpc2/servers/tcp/protocol.ex">>]}.
|
13
18
|
{<<"licenses">>,[<<"Apache 2.0">>]}.
|
14
19
|
{<<"links">>,
|
|
@@ -36,6 41,11 @@
|
36
41
|
{<<"optional">>,true},
|
37
42
|
{<<"repository">>,<<"hexpm">>},
|
38
43
|
{<<"requirement">>,<<"~> 1.6">>}],
|
44
|
[{<<"app">>,<<"cowboy">>},
|
45
|
{<<"name">>,<<"cowboy">>},
|
46
|
{<<"optional">>,true},
|
47
|
{<<"repository">>,<<"hexpm">>},
|
48
|
{<<"requirement">>,<<"~> 1.1 or ~> 2.4">>}],
|
39
49
|
[{<<"app">>,<<"plug">>},
|
40
50
|
{<<"name">>,<<"plug">>},
|
41
51
|
{<<"optional">>,true},
|
|
@@ -46,4 56,4 @@
|
46
56
|
{<<"optional">>,true},
|
47
57
|
{<<"repository">>,<<"hexpm">>},
|
48
58
|
{<<"requirement">>,<<"~> 0.3">>}]]}.
|
49
|
- {<<"version">>,<<"1.0.3">>}.
|
59
|
{<<"version">>,<<"1.1.0">>}.
|
changed
lib/jsonrpc2.ex
|
@@ -141,22 141,22 @@ defmodule JSONRPC2 do
|
141
141
|
"""
|
142
142
|
|
143
143
|
@typedoc "A JSON-RPC 2.0 method."
|
144
|
- @type method :: String.t
|
144
|
@type method :: String.t()
|
145
145
|
|
146
146
|
@typedoc "A decoded JSON object."
|
147
147
|
@type json ::
|
148
|
- nil |
|
149
|
- true |
|
150
|
- false |
|
151
|
- float |
|
152
|
- integer |
|
153
|
- String.t |
|
154
|
- [json] |
|
155
|
- %{optional(String.t) => json}
|
148
|
nil
|
149
|
| true
|
150
|
| false
|
151
|
| float
|
152
|
| integer
|
153
|
| String.t()
|
154
|
| [json]
|
155
|
| %{optional(String.t()) => json}
|
156
156
|
|
157
157
|
@typedoc "A JSON-RPC 2.0 params value."
|
158
|
- @type params :: [json] | %{optional(String.t) => json}
|
158
|
@type params :: [json] | %{optional(String.t()) => json}
|
159
159
|
|
160
160
|
@typedoc "A JSON-RPC 2.0 request ID."
|
161
|
- @type id :: String.t | number
|
161
|
@type id :: String.t() | number
|
162
162
|
end
|
changed
lib/jsonrpc2/clients/http.ex
|
@@ -3,7 3,7 @@ defmodule JSONRPC2.Clients.HTTP do
|
3
3
|
A client for JSON-RPC 2.0 using an HTTP transport with JSON in the body.
|
4
4
|
"""
|
5
5
|
|
6
|
- @type batch_result :: {:ok, JSONRPC2.Response.id_and_response} | {:error, any}
|
6
|
@type batch_result :: {:ok, JSONRPC2.Response.id_and_response()} | {:error, any}
|
7
7
|
|
8
8
|
@doc """
|
9
9
|
Make a call to `url` for JSON-RPC 2.0 `method` with `params`.
|
|
@@ -13,8 13,8 @@ defmodule JSONRPC2.Clients.HTTP do
|
13
13
|
|
14
14
|
See [hackney](https://github.com/benoitc/hackney) for more information on the available options.
|
15
15
|
"""
|
16
|
- @spec call(String.t, JSONRPC2.method, JSONRPC2.params, any, atom, list) ::
|
17
|
- {:ok, any} | {:error, any}
|
16
|
@spec call(String.t(), JSONRPC2.method(), JSONRPC2.params(), any, atom, list) ::
|
17
|
{:ok, any} | {:error, any}
|
18
18
|
def call(url, method, params, headers \\ [], http_method \\ :post, hackney_opts \\ []) do
|
19
19
|
serializer = Application.get_env(:jsonrpc2, :serializer)
|
20
20
|
{:ok, payload} = JSONRPC2.Request.serialized_request({method, params, 0}, serializer)
|
|
@@ -46,8 46,7 @@ defmodule JSONRPC2.Clients.HTTP do
|
46
46
|
|
47
47
|
See [hackney](https://github.com/benoitc/hackney) for more information on the available options.
|
48
48
|
"""
|
49
|
- @spec notify(String.t, JSONRPC2.method, JSONRPC2.params, any, atom, list) ::
|
50
|
- :ok | {:error, any}
|
49
|
@spec notify(String.t(), JSONRPC2.method(), JSONRPC2.params(), any, atom, list) :: :ok | {:error, any}
|
51
50
|
def notify(url, method, params, headers \\ [], http_method \\ :post, hackney_opts \\ []) do
|
52
51
|
serializer = Application.get_env(:jsonrpc2, :serializer)
|
53
52
|
{:ok, payload} = JSONRPC2.Request.serialized_request({method, params}, serializer)
|
|
@@ -67,10 66,11 @@ defmodule JSONRPC2.Clients.HTTP do
|
67
66
|
|
68
67
|
See [hackney](https://github.com/benoitc/hackney) for more information on the available options.
|
69
68
|
"""
|
70
|
- @spec batch(String.t, [JSONRPC2.Request.request], any, atom, list) ::
|
71
|
- [batch_result] | :ok | {:error, any}
|
69
|
@spec batch(String.t(), [JSONRPC2.Request.request()], any, atom, list) ::
|
70
|
[batch_result] | :ok | {:error, any}
|
72
71
|
def batch(url, requests, headers \\ [], http_method \\ :post, hackney_opts \\ []) do
|
73
72
|
serializer = Application.get_env(:jsonrpc2, :serializer)
|
73
|
|
74
74
|
{:ok, payload} =
|
75
75
|
Enum.map(requests, &JSONRPC2.Request.request/1)
|
76
76
|
|> serializer.encode()
|
changed
lib/jsonrpc2/clients/tcp.ex
|
@@ -7,13 7,13 @@ defmodule JSONRPC2.Clients.TCP do
|
7
7
|
|
8
8
|
@default_timeout 5_000
|
9
9
|
|
10
|
- @type host :: binary | :inet.socket_address | :inet.hostname
|
10
|
@type host :: binary | :inet.socket_address() | :inet.hostname()
|
11
11
|
|
12
12
|
@type request_id :: any
|
13
13
|
|
14
14
|
@type call_option ::
|
15
|
- {:string_id, boolean} |
|
16
|
- {:timeout, pos_integer}
|
15
|
{:string_id, boolean}
|
16
|
| {:timeout, pos_integer}
|
17
17
|
|
18
18
|
@type call_options :: [call_option]
|
19
19
|
|
|
@@ -26,7 26,7 @@ defmodule JSONRPC2.Clients.TCP do
|
26
26
|
[here](https://github.com/lpgauth/shackle#client_options), as well as `pool_opts`, detailed
|
27
27
|
[here](https://github.com/lpgauth/shackle#pool_options).
|
28
28
|
"""
|
29
|
- @spec start(host, :inet.port_number, atom, Keyword.t, Keyword.t) :: :ok
|
29
|
@spec start(host, :inet.port_number(), atom, Keyword.t(), Keyword.t()) :: :ok
|
30
30
|
def start(host, port, name, client_opts \\ [], pool_opts \\ []) do
|
31
31
|
host = if is_binary(host), do: to_charlist(host), else: host
|
32
32
|
|
|
@@ -37,7 37,9 @@ defmodule JSONRPC2.Clients.TCP do
|
37
37
|
{:ok, ip} -> ip
|
38
38
|
{:error, :einval} -> host
|
39
39
|
end
|
40
|
- host -> host
|
40
|
|
41
|
host ->
|
42
|
host
|
41
43
|
end
|
42
44
|
|
43
45
|
client_opts = Keyword.merge([ip: ip, port: port, socket_options: [:binary, packet: :line]], client_opts)
|
|
@@ -63,10 65,10 @@ defmodule JSONRPC2.Clients.TCP do
|
63
65
|
For backwards compatibility reasons, you may also provide a boolean for the `options` parameter,
|
64
66
|
which will set `string_id` to the given boolean.
|
65
67
|
"""
|
66
|
- @spec call(atom, JSONRPC2.method, JSONRPC2.params, boolean | call_options) :: {:ok, any} | {:error, any}
|
68
|
@spec call(atom, JSONRPC2.method(), JSONRPC2.params(), boolean | call_options) ::
|
69
|
{:ok, any} | {:error, any}
|
67
70
|
def call(name, method, params, options \\ [])
|
68
71
|
|
69
|
-
|
70
72
|
def call(name, method, params, string_id) when is_boolean(string_id) do
|
71
73
|
call(name, method, params, string_id: string_id)
|
72
74
|
end
|
|
@@ -95,8 97,8 @@ defmodule JSONRPC2.Clients.TCP do
|
95
97
|
For backwards compatibility reasons, you may also provide a boolean for the `options` parameter,
|
96
98
|
which will set `string_id` to the given boolean.
|
97
99
|
"""
|
98
|
- @spec cast(atom, JSONRPC2.method, JSONRPC2.params, boolean | cast_options) ::
|
99
|
- {:ok, request_id} | {:error, :backlog_full}
|
100
|
@spec cast(atom, JSONRPC2.method(), JSONRPC2.params(), boolean | cast_options) ::
|
101
|
{:ok, request_id} | {:error, :backlog_full}
|
100
102
|
def cast(name, method, params, options \\ [])
|
101
103
|
|
102
104
|
def cast(name, method, params, string_id) when is_boolean(string_id) do
|
|
@@ -124,8 126,7 @@ defmodule JSONRPC2.Clients.TCP do
|
124
126
|
|
125
127
|
This function returns a `request_id`, but it should not be used with `receive_response/1`.
|
126
128
|
"""
|
127
|
- @spec notify(atom, JSONRPC2.method, JSONRPC2.params) ::
|
128
|
- {:ok, request_id} | {:error, :backlog_full}
|
129
|
@spec notify(atom, JSONRPC2.method(), JSONRPC2.params()) :: {:ok, request_id} | {:error, :backlog_full}
|
129
130
|
def notify(name, method, params) do
|
130
131
|
:shackle.cast(name, {:notify, method, params}, nil, 0)
|
131
132
|
end
|
changed
lib/jsonrpc2/clients/tcp/protocol.ex
|
@@ -45,7 45,9 @@ defmodule JSONRPC2.Clients.TCP.Protocol do
|
45
45
|
{:ok, {nil, result}} ->
|
46
46
|
_ =
|
47
47
|
Logger.error([
|
48
|
- inspect(__MODULE__), " received response with null ID: ", inspect(result)
|
48
|
inspect(__MODULE__),
|
49
|
" received response with null ID: ",
|
50
|
inspect(result)
|
49
51
|
])
|
50
52
|
|
51
53
|
{:ok, [], state}
|
|
@@ -56,7 58,9 @@ defmodule JSONRPC2.Clients.TCP.Protocol do
|
56
58
|
{:error, error} ->
|
57
59
|
_ =
|
58
60
|
Logger.error([
|
59
|
- inspect(__MODULE__), " received invalid response, error: ", inspect(error)
|
61
|
inspect(__MODULE__),
|
62
|
" received invalid response, error: ",
|
63
|
inspect(error)
|
60
64
|
])
|
61
65
|
|
62
66
|
{:ok, [], state}
|
|
@@ -68,6 72,6 @@ defmodule JSONRPC2.Clients.TCP.Protocol do
|
68
72
|
end
|
69
73
|
|
70
74
|
defp external_request_id(request_counter) do
|
71
|
- rem(request_counter, 2147483647)
|
75
|
rem(request_counter, 2_147_483_647)
|
72
76
|
end
|
73
77
|
end
|
changed
lib/jsonrpc2/request.ex
|
@@ -4,8 4,8 @@ defmodule JSONRPC2.Request do
|
4
4
|
"""
|
5
5
|
|
6
6
|
@type request ::
|
7
|
- {JSONRPC2.method, JSONRPC2.params} |
|
8
|
- {JSONRPC2.method, JSONRPC2.params, JSONRPC2.id}
|
7
|
{JSONRPC2.method(), JSONRPC2.params()}
|
8
|
| {JSONRPC2.method(), JSONRPC2.params(), JSONRPC2.id()}
|
9
9
|
|
10
10
|
@doc """
|
11
11
|
Returns a serialized `request` using `serializer`.
|
|
@@ -24,7 24,7 @@ defmodule JSONRPC2.Request do
|
24
24
|
def request(request)
|
25
25
|
|
26
26
|
def request({method, params})
|
27
|
- when is_binary(method) and (is_list(params) or is_map(params)) do
|
27
|
when is_binary(method) and (is_list(params) or is_map(params)) do
|
28
28
|
%{
|
29
29
|
"jsonrpc" => "2.0",
|
30
30
|
"method" => method,
|
|
@@ -33,7 33,7 @@ defmodule JSONRPC2.Request do
|
33
33
|
end
|
34
34
|
|
35
35
|
def request({method, params, id})
|
36
|
- when (is_number(id) or is_binary(id)) do
|
36
|
when is_number(id) or is_binary(id) do
|
37
37
|
{method, params}
|
38
38
|
|> request()
|
39
39
|
|> Map.put("id", id)
|
changed
lib/jsonrpc2/response.ex
|
@@ -4,12 4,12 @@ defmodule JSONRPC2.Response do
|
4
4
|
"""
|
5
5
|
|
6
6
|
@type id_and_response ::
|
7
|
- {JSONRPC2.id | nil, {:ok, any} | {:error, code :: integer, message :: String.t, data :: any}}
|
7
|
{JSONRPC2.id() | nil, {:ok, any} | {:error, code :: integer, message :: String.t(), data :: any}}
|
8
8
|
|
9
9
|
@doc """
|
10
10
|
Deserialize the given `response` using `serializer`.
|
11
11
|
"""
|
12
|
- @spec deserialize_response(String.t, module) :: {:ok, id_and_response} | {:error, any}
|
12
|
@spec deserialize_response(String.t(), module) :: {:ok, id_and_response} | {:error, any}
|
13
13
|
def deserialize_response(response, serializer) do
|
14
14
|
case serializer.decode(response) do
|
15
15
|
{:ok, response} -> id_and_response(response)
|
changed
lib/jsonrpc2/server/handler.ex
|
@@ -46,12 46,12 @@ defmodule JSONRPC2.Server.Handler do
|
46
46
|
* `{:jsonrpc2, code, message}` or `{:jsonrpc2, code, message, data}` to return a custom error,
|
47
47
|
with or without extra data.
|
48
48
|
"""
|
49
|
- @callback handle_request(method :: JSONRPC2.method, params :: JSONRPC2.params) ::
|
50
|
- JSONRPC2.json | no_return
|
49
|
@callback handle_request(method :: JSONRPC2.method(), params :: JSONRPC2.params()) ::
|
50
|
JSONRPC2.json() | no_return
|
51
51
|
|
52
52
|
defmacro __using__(_) do
|
53
53
|
quote do
|
54
|
- @spec handle(String.t) :: {:reply, String.t} | :noreply
|
54
|
@spec handle(String.t()) :: {:reply, String.t()} | :noreply
|
55
55
|
def handle(json) do
|
56
56
|
serializer = Application.get_env(:jsonrpc2, :serializer)
|
57
57
|
|
|
@@ -61,16 61,10 @@ defmodule JSONRPC2.Server.Handler do
|
61
61
|
end
|
62
62
|
|
63
63
|
@doc false
|
64
|
- def handle(module, serializer, json) do
|
64
|
def handle(module, serializer, json) when is_binary(json) do
|
65
65
|
case serializer.decode(json) do
|
66
66
|
{:ok, decoded_request} ->
|
67
|
- case parse(decoded_request) do
|
68
|
- batch_rpc when is_list(batch_rpc) and length(batch_rpc) > 0 ->
|
69
|
- merge_responses(Enum.map(batch_rpc, &dispatch(module, &1)))
|
70
|
-
|
71
|
- rpc ->
|
72
|
- dispatch(module, rpc)
|
73
|
- end
|
67
|
parse(decoded_request) |> collate_for_dispatch(module)
|
74
68
|
|
75
69
|
{:error, _error} ->
|
76
70
|
standard_error_response(:parse_error, nil)
|
|
@@ -81,6 75,20 @@ defmodule JSONRPC2.Server.Handler do
|
81
75
|
|> encode_response(module, serializer, json)
|
82
76
|
end
|
83
77
|
|
78
|
def handle(module, serializer, json) do
|
79
|
parse(json)
|
80
|
|> collate_for_dispatch(module)
|
81
|
|> encode_response(module, serializer, json)
|
82
|
end
|
83
|
|
84
|
defp collate_for_dispatch(batch_rpc, module) when is_list(batch_rpc) and length(batch_rpc) > 0 do
|
85
|
merge_responses(Enum.map(batch_rpc, &dispatch(module, &1)))
|
86
|
end
|
87
|
|
88
|
defp collate_for_dispatch(rpc, module) do
|
89
|
dispatch(module, rpc)
|
90
|
end
|
91
|
|
84
92
|
defp parse(requests) when is_list(requests) do
|
85
93
|
for request <- requests, do: parse(request)
|
86
94
|
end
|
|
@@ -103,10 111,8 @@ defmodule JSONRPC2.Server.Handler do
|
103
111
|
end
|
104
112
|
|
105
113
|
defp valid_request?(version, method, params, id) do
|
106
|
- version == "2.0" and
|
107
|
- is_binary(method) and
|
108
|
- (is_list(params) or is_map(params)) and
|
109
|
- (id in [:undefined, :nil] or is_binary(id) or is_number(id))
|
114
|
version == "2.0" and is_binary(method) and (is_list(params) or is_map(params)) and
|
115
|
(id in [:undefined, nil] or is_binary(id) or is_number(id))
|
110
116
|
end
|
111
117
|
|
112
118
|
defp merge_responses(responses) do
|
|
@@ -122,8 128,17 @@ defmodule JSONRPC2.Server.Handler do
|
122
128
|
try do
|
123
129
|
result_response(module.handle_request(method, params), id)
|
124
130
|
rescue
|
125
|
- FunctionClauseError ->
|
126
|
- standard_error_response(:method_not_found, id)
|
131
|
e in FunctionClauseError ->
|
132
|
# if that error originates from the very module.handle_request call - handle, otherwise - reraise
|
133
|
case e do
|
134
|
%FunctionClauseError{function: :handle_request, module: ^module} ->
|
135
|
standard_error_response(:method_not_found, %{method: method, params: params}, id)
|
136
|
|
137
|
other_e ->
|
138
|
original_stacktrace = System.stacktrace()
|
139
|
log_error(module, method, params, :error, other_e)
|
140
|
Kernel.reraise(other_e, original_stacktrace)
|
141
|
end
|
127
142
|
catch
|
128
143
|
:throw, error when error in @throwable_errors ->
|
129
144
|
standard_error_response(error, id)
|
|
@@ -138,11 153,7 @@ defmodule JSONRPC2.Server.Handler do
|
138
153
|
error_response(code, message, data, id)
|
139
154
|
|
140
155
|
kind, payload ->
|
141
|
- _ =
|
142
|
- Logger.error([
|
143
|
- "Error in handler ", inspect(module), " for method ", method, " with params: ",
|
144
|
- inspect(params), ":\n\n", Exception.format(kind, payload, System.stacktrace())
|
145
|
- ])
|
156
|
log_error(module, method, params, kind, payload)
|
146
157
|
|
147
158
|
standard_error_response(:internal_error, id)
|
148
159
|
end
|
|
@@ -152,16 163,31 @@ defmodule JSONRPC2.Server.Handler do
|
152
163
|
standard_error_response(:invalid_request, nil)
|
153
164
|
end
|
154
165
|
|
166
|
defp log_error(module, method, params, kind, payload) do
|
167
|
_ =
|
168
|
Logger.error([
|
169
|
"Error in handler ",
|
170
|
inspect(module),
|
171
|
" for method ",
|
172
|
method,
|
173
|
" with params: ",
|
174
|
inspect(params),
|
175
|
":\n\n",
|
176
|
Exception.format(kind, payload, System.stacktrace())
|
177
|
])
|
178
|
end
|
179
|
|
155
180
|
defp result_response(_result, :undefined) do
|
156
181
|
:noreply
|
157
182
|
end
|
158
183
|
|
159
184
|
defp result_response(result, id) do
|
160
|
- {:reply, %{
|
161
|
- "jsonrpc" => "2.0",
|
162
|
- "result" => result,
|
163
|
- "id" => id
|
164
|
- }}
|
185
|
{:reply,
|
186
|
%{
|
187
|
"jsonrpc" => "2.0",
|
188
|
"result" => result,
|
189
|
"id" => id
|
190
|
}}
|
165
191
|
end
|
166
192
|
|
167
193
|
defp standard_error_response(error_type, id) do
|
|
@@ -232,8 258,14 @@ defmodule JSONRPC2.Server.Handler do
|
232
258
|
{:error, reason} ->
|
233
259
|
_ =
|
234
260
|
Logger.info([
|
235
|
- "Handler ", inspect(module), " returned invalid reply:\n Reason: ", inspect(reason),
|
236
|
- "\n Received: ", inspect(reply), "\n Request: ", json
|
261
|
"Handler ",
|
262
|
inspect(module),
|
263
|
" returned invalid reply:\n Reason: ",
|
264
|
inspect(reason),
|
265
|
"\n Received: ",
|
266
|
inspect(reply),
|
267
|
"\n Request: ",
|
268
|
json
|
237
269
|
])
|
238
270
|
|
239
271
|
standard_error_response(:internal_error, nil)
|
changed
lib/jsonrpc2/servers/http.ex
|
@@ -6,8 6,7 @@ defmodule JSONRPC2.Servers.HTTP do
|
6
6
|
handler within a Plug-based web app (such as Phoenix), please see `JSONRPC2.Servers.HTTP.Plug`.
|
7
7
|
"""
|
8
8
|
|
9
|
- alias Plug.Adapters.Cowboy
|
10
|
- alias JSONRPC2.Servers.HTTP.Plug
|
9
|
alias JSONRPC2.Servers.HTTP.Plug, as: JSONRPC2Plug
|
11
10
|
|
12
11
|
@doc """
|
13
12
|
Returns a supervisor child spec for the given `handler` via `scheme` with `cowboy_opts`.
|
|
@@ -20,10 19,10 @@ defmodule JSONRPC2.Servers.HTTP do
|
20
19
|
|
21
20
|
If the server `ref` is not set in `cowboy_opts`, `handler.HTTP` or `handler.HTTPS` is the default.
|
22
21
|
"""
|
23
|
- @spec child_spec(:http | :https, module, list) :: Supervisor.Spec.spec
|
22
|
@spec child_spec(:http | :https, module, list) :: Supervisor.Spec.spec()
|
24
23
|
def child_spec(scheme, handler, cowboy_opts \\ []) do
|
25
24
|
cowboy_opts = cowboy_opts [ref: ref(scheme, handler)]
|
26
|
- Cowboy.child_spec(scheme, Plug, handler, cowboy_opts)
|
25
|
cowboy_adapter().child_spec(scheme, JSONRPC2Plug, handler, cowboy_opts)
|
27
26
|
end
|
28
27
|
|
29
28
|
@doc """
|
|
@@ -37,7 36,7 @@ defmodule JSONRPC2.Servers.HTTP do
|
37
36
|
@spec http(module, list) :: {:ok, pid} | {:error, term}
|
38
37
|
def http(handler, cowboy_opts \\ []) do
|
39
38
|
cowboy_opts = cowboy_opts [ref: ref(:http, handler)]
|
40
|
- Cowboy.http(Plug, handler, cowboy_opts)
|
39
|
cowboy_adapter().http(JSONRPC2Plug, handler, cowboy_opts)
|
41
40
|
end
|
42
41
|
|
43
42
|
@doc """
|
|
@@ -53,7 52,7 @@ defmodule JSONRPC2.Servers.HTTP do
|
53
52
|
@spec https(module, list) :: {:ok, pid} | {:error, term}
|
54
53
|
def https(handler, cowboy_opts \\ []) do
|
55
54
|
cowboy_opts = cowboy_opts [ref: ref(:https, handler)]
|
56
|
- Cowboy.https(Plug, handler, cowboy_opts)
|
55
|
cowboy_adapter().https(JSONRPC2Plug, handler, cowboy_opts)
|
57
56
|
end
|
58
57
|
|
59
58
|
defp ref(scheme, handler) do
|
|
@@ -69,6 68,28 @@ defmodule JSONRPC2.Servers.HTTP do
|
69
68
|
"""
|
70
69
|
@spec shutdown(atom) :: :ok | {:error, :not_found}
|
71
70
|
def shutdown(ref) do
|
72
|
- Cowboy.shutdown(ref)
|
71
|
cowboy_adapter().shutdown(ref)
|
72
|
end
|
73
|
|
74
|
defp cowboy_adapter() do
|
75
|
cowboy_spec =
|
76
|
Application.loaded_applications()
|
77
|
|> List.keyfind(:cowboy, 0)
|
78
|
|
79
|
if cowboy_spec do
|
80
|
cowboy_spec
|
81
|
|> elem(2)
|
82
|
|> List.to_string()
|
83
|
|> Version.parse!()
|
84
|
|> Version.match?("~> 2.0")
|
85
|
|> case do
|
86
|
true -> Plug.Adapters.Cowboy2
|
87
|
false -> Plug.Adapters.Cowboy
|
88
|
end
|
89
|
else
|
90
|
:ok = Application.load(:cowboy)
|
91
|
|
92
|
cowboy_adapter()
|
93
|
end
|
73
94
|
end
|
74
95
|
end
|
changed
lib/jsonrpc2/servers/http/plug.ex
|
@@ -21,7 21,15 @@ defmodule JSONRPC2.Servers.HTTP.Plug do
|
21
21
|
|
22
22
|
@doc false
|
23
23
|
def call(%{method: "POST"} = conn, handler) do
|
24
|
- {:ok, req_body, conn} = Plug.Conn.read_body(conn)
|
24
|
req_body =
|
25
|
cond do
|
26
|
Plug.Conn.get_req_header(conn, "content-type")
|
27
|
|> Enum.member?("application/json") && conn.params != %{} && conn.params != %Plug.Conn.Unfetched{} ->
|
28
|
conn.params
|
29
|
|
30
|
true ->
|
31
|
get_plain_body(conn)
|
32
|
end
|
25
33
|
|
26
34
|
resp_body =
|
27
35
|
case handler.handle(req_body) do
|
|
@@ -38,4 46,9 @@ defmodule JSONRPC2.Servers.HTTP.Plug do
|
38
46
|
conn
|
39
47
|
|> Plug.Conn.resp(404, "")
|
40
48
|
end
|
49
|
|
50
|
defp get_plain_body(conn) do
|
51
|
{:ok, req_body, _} = Plug.Conn.read_body(conn)
|
52
|
req_body
|
53
|
end
|
41
54
|
end
|
changed
lib/jsonrpc2/servers/tcp.ex
|
@@ -20,7 20,7 @@ defmodule JSONRPC2.Servers.TCP do
|
20
20
|
* `timeout` - disconnect after this amount of milliseconds without a packet from a client.
|
21
21
|
Defaults to 1 hour.
|
22
22
|
"""
|
23
|
- @spec start_listener(module, :inet.port_number, Keyword.t) :: {:ok, pid}
|
23
|
@spec start_listener(module, :inet.port_number(), Keyword.t()) :: {:ok, pid}
|
24
24
|
def start_listener(handler, port, opts \\ []) do
|
25
25
|
apply(:ranch, :start_listener, ranch_args(handler, port, opts))
|
26
26
|
end
|
|
@@ -41,7 41,7 @@ defmodule JSONRPC2.Servers.TCP do
|
41
41
|
* `timeout` - disconnect after this amount of milliseconds without a packet from a client.
|
42
42
|
Defaults to 1 hour.
|
43
43
|
"""
|
44
|
- @spec child_spec(module, :inet.port_number, Keyword.t) :: {:ok, pid}
|
44
|
@spec child_spec(module, :inet.port_number(), Keyword.t()) :: {:ok, pid}
|
45
45
|
def child_spec(handler, port, opts \\ []) do
|
46
46
|
apply(:ranch, :child_spec, ranch_args(handler, port, opts))
|
47
47
|
end
|
changed
lib/jsonrpc2/servers/tcp/protocol.ex
|
@@ -25,12 25,12 @@ defmodule JSONRPC2.Servers.TCP.Protocol do
|
25
25
|
transport.setopts(socket, active: :once)
|
26
26
|
|
27
27
|
{:ok, _} =
|
28
|
- Task.start fn ->
|
28
|
Task.start(fn ->
|
29
29
|
case jsonrpc2_handler.handle(data) do
|
30
30
|
{:reply, reply} -> transport.send(socket, [reply, "\r\n"])
|
31
31
|
:noreply -> :noreply
|
32
32
|
end
|
33
|
- end
|
33
|
end)
|
34
34
|
|
35
35
|
{:noreply, state, timeout}
|
36
36
|
end
|
|
@@ -47,7 47,10 @@ defmodule JSONRPC2.Servers.TCP.Protocol do
|
47
47
|
def handle_info(message, state) do
|
48
48
|
_ =
|
49
49
|
Logger.info([
|
50
|
- inspect(__MODULE__), " with state:\n", inspect(state), "\nreceived unexpected message:\n",
|
50
|
inspect(__MODULE__),
|
51
|
" with state:\n",
|
52
|
inspect(state),
|
53
|
"\nreceived unexpected message:\n",
|
51
54
|
inspect(message)
|
52
55
|
])
|
changed
mix.exs
|
@@ -1,54 1,73 @@
|
1
1
|
defmodule JSONRPC2.Mixfile do
|
2
2
|
use Mix.Project
|
3
3
|
|
4
|
- @version "1.0.3"
|
4
|
@version "1.1.0"
|
5
5
|
|
6
6
|
def project do
|
7
|
- [app: :jsonrpc2,
|
8
|
- version: @version,
|
9
|
- elixir: "~> 1.3",
|
10
|
- deps: deps(),
|
11
|
- elixirc_paths: elixirc_paths(Mix.env),
|
12
|
- description: description(),
|
13
|
- package: package(),
|
14
|
- name: "JSONRPC2",
|
15
|
- docs: [source_ref: "v#{@version}", main: "readme",
|
16
|
- canonical: "http://hexdocs.pm/jsonrpc2",
|
17
|
- source_url: "https://github.com/fanduel/jsonrpc2-elixir",
|
18
|
- extras: ["README.md"]],
|
19
|
- dialyzer: [plt_add_apps: [:shackle, :ranch, :plug, :hackney]],
|
20
|
- xref: [exclude: [Poison, :hackney, :jiffy, :ranch, :shackle, :shackle_pool, Plug.Conn,
|
21
|
- Plug.Adapters.Cowboy]]]
|
7
|
[
|
8
|
app: :jsonrpc2,
|
9
|
version: @version,
|
10
|
elixir: "~> 1.3",
|
11
|
deps: deps(),
|
12
|
elixirc_paths: elixirc_paths(Mix.env()),
|
13
|
description: description(),
|
14
|
package: package(),
|
15
|
name: "JSONRPC2",
|
16
|
docs: [
|
17
|
source_ref: "v#{@version}",
|
18
|
main: "readme",
|
19
|
canonical: "http://hexdocs.pm/jsonrpc2",
|
20
|
source_url: "https://github.com/fanduel/jsonrpc2-elixir",
|
21
|
extras: ["README.md"]
|
22
|
],
|
23
|
dialyzer: [plt_add_apps: [:shackle, :ranch, :plug, :hackney]],
|
24
|
xref: [
|
25
|
exclude: [
|
26
|
Poison,
|
27
|
:hackney,
|
28
|
:jiffy,
|
29
|
:ranch,
|
30
|
:shackle,
|
31
|
:shackle_pool,
|
32
|
Plug.Conn,
|
33
|
Plug.Adapters.Cowboy,
|
34
|
Plug.Adapters.Cowboy2
|
35
|
]
|
36
|
]
|
37
|
]
|
22
38
|
end
|
23
39
|
|
24
40
|
def application do
|
25
|
- [applications: [:logger],
|
26
|
- env: [serializer: Poison]]
|
41
|
[applications: [:logger], env: [serializer: Poison]]
|
27
42
|
end
|
28
43
|
|
29
44
|
defp deps do
|
30
|
- [{:poison, "~> 3.0 or ~> 2.0", optional: true},
|
31
|
- {:jiffy, "~> 0.14", optional: true},
|
32
|
- {:ranch, "~> 1.2", optional: true},
|
33
|
- {:shackle, "~> 0.3", optional: true},
|
34
|
- {:plug, "~> 1.1", optional: true},
|
35
|
- {:hackney, "~> 1.6", optional: true},
|
36
|
- {:cowboy, "~> 1.1", only: :test},
|
37
|
- {:ex_doc, "~> 0.12", only: :dev},
|
38
|
- {:dialyxir, "~> 0.3", only: :dev}]
|
45
|
[
|
46
|
{:poison, "~> 3.0 or ~> 2.0", optional: true},
|
47
|
{:jiffy, "~> 0.14", optional: true},
|
48
|
{:ranch, "~> 1.2", optional: true},
|
49
|
{:shackle, "~> 0.3", optional: true},
|
50
|
{:plug, "~> 1.1", optional: true},
|
51
|
{:hackney, "~> 1.6", optional: true},
|
52
|
{:cowboy, "~> 1.1 or ~> 2.4", optional: true},
|
53
|
{:ex_doc, "~> 0.12", only: :dev},
|
54
|
{:dialyxir, "~> 0.3", only: :dev}
|
55
|
]
|
39
56
|
end
|
40
57
|
|
41
58
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
42
|
- defp elixirc_paths(_), do: ["lib"]
|
59
|
defp elixirc_paths(_), do: ["lib"]
|
43
60
|
|
44
61
|
defp description do
|
45
62
|
"JSON-RPC 2.0 for Elixir."
|
46
63
|
end
|
47
64
|
|
48
65
|
defp package do
|
49
|
- [maintainers: ["Eric Entin"],
|
50
|
- licenses: ["Apache 2.0"],
|
51
|
- links: %{"GitHub" => "https://github.com/fanduel/jsonrpc2-elixir"},
|
52
|
- files: ~w(mix.exs README.md LICENSE lib)]
|
66
|
[
|
67
|
maintainers: ["Eric Entin"],
|
68
|
licenses: ["Apache 2.0"],
|
69
|
links: %{"GitHub" => "https://github.com/fanduel/jsonrpc2-elixir"},
|
70
|
files: ~w(mix.exs README.md LICENSE lib)
|
71
|
]
|
53
72
|
end
|
54
73
|
end
|