2023-12-14 08:34:13 +07:00
|
|
|
# json-rpc
|
2024-01-30 14:11:49 +07:00
|
|
|
# Copyright (c) 2019-2024 Status Research & Development GmbH
|
2023-12-14 08:34:13 +07:00
|
|
|
# Licensed under either of
|
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
|
# at your option.
|
|
|
|
|
# This file may not be copied, modified, or distributed except according to
|
|
|
|
|
# those terms.
|
|
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
{.push raises: [], gcsafe.}
|
|
|
|
|
|
2021-02-15 13:45:51 +01:00
|
|
|
import
|
2021-03-26 13:17:00 +01:00
|
|
|
std/tables,
|
2024-01-03 20:06:53 +07:00
|
|
|
chronicles,
|
|
|
|
|
results,
|
2021-03-26 13:17:00 +01:00
|
|
|
chronos,
|
2024-01-03 20:06:53 +07:00
|
|
|
../client,
|
2024-01-08 10:37:29 +07:00
|
|
|
../errors,
|
2024-01-03 20:06:53 +07:00
|
|
|
../private/jrpc_sys
|
2022-03-21 15:19:49 +01:00
|
|
|
|
2024-03-15 09:05:33 +01:00
|
|
|
export client, errors
|
2018-07-12 18:36:40 +01:00
|
|
|
|
2024-02-19 08:53:10 +07:00
|
|
|
logScope:
|
|
|
|
|
topics = "JSONRPC-SOCKET-CLIENT"
|
|
|
|
|
|
2018-07-12 18:36:40 +01:00
|
|
|
type
|
|
|
|
|
RpcSocketClient* = ref object of RpcClient
|
|
|
|
|
transport*: StreamTransport
|
|
|
|
|
address*: TransportAddress
|
2019-05-14 18:42:51 +03:00
|
|
|
loop*: Future[void]
|
2018-07-12 18:36:40 +01:00
|
|
|
|
|
|
|
|
const defaultMaxRequestLength* = 1024 * 128
|
|
|
|
|
|
2021-02-15 13:45:51 +01:00
|
|
|
proc new*(T: type RpcSocketClient): T =
|
|
|
|
|
T()
|
|
|
|
|
|
2018-07-12 18:36:40 +01:00
|
|
|
proc newRpcSocketClient*: RpcSocketClient =
|
|
|
|
|
## Creates a new client instance.
|
2021-02-15 13:45:51 +01:00
|
|
|
RpcSocketClient.new()
|
2018-07-12 18:36:40 +01:00
|
|
|
|
2024-01-30 14:11:49 +07:00
|
|
|
method call*(client: RpcSocketClient, name: string,
|
2024-10-22 21:58:46 +02:00
|
|
|
params: RequestParamsTx): Future[JsonString] {.async.} =
|
2018-07-12 18:36:40 +01:00
|
|
|
## Remotely calls the specified RPC method.
|
2024-01-30 14:11:49 +07:00
|
|
|
if client.transport.isNil:
|
2024-01-03 20:06:53 +07:00
|
|
|
raise newException(JsonRpcError,
|
2018-07-14 11:25:27 +03:00
|
|
|
"Transport is not initialised (missing a call to connect?)")
|
2018-07-12 18:36:40 +01:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
let
|
|
|
|
|
id = client.getNextId()
|
|
|
|
|
reqBody = requestTxEncode(name, params, id) & "\r\n"
|
|
|
|
|
newFut = newFuture[JsonString]() # completed by processMessage
|
|
|
|
|
|
2018-07-12 18:36:40 +01:00
|
|
|
# add to awaiting responses
|
2024-01-30 14:11:49 +07:00
|
|
|
client.awaiting[id] = newFut
|
2019-06-26 13:39:58 +03:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
debug "Sending JSON-RPC request",
|
|
|
|
|
address = $client.address, len = len(reqBody), name, id
|
|
|
|
|
|
|
|
|
|
let res = await client.transport.write(reqBody)
|
2019-06-26 13:39:58 +03:00
|
|
|
# TODO: Add actions when not full packet was send, e.g. disconnect peer.
|
2024-10-22 21:58:46 +02:00
|
|
|
doAssert(res == reqBody.len)
|
2019-06-26 13:39:58 +03:00
|
|
|
|
2021-02-15 13:45:51 +01:00
|
|
|
return await newFut
|
2018-07-12 18:36:40 +01:00
|
|
|
|
2024-01-30 14:11:49 +07:00
|
|
|
method callBatch*(client: RpcSocketClient,
|
|
|
|
|
calls: RequestBatchTx): Future[ResponseBatchRx]
|
2024-10-22 21:58:46 +02:00
|
|
|
{.async.} =
|
2024-01-30 14:11:49 +07:00
|
|
|
if client.transport.isNil:
|
|
|
|
|
raise newException(JsonRpcError,
|
|
|
|
|
"Transport is not initialised (missing a call to connect?)")
|
|
|
|
|
|
|
|
|
|
if client.batchFut.isNil or client.batchFut.finished():
|
|
|
|
|
client.batchFut = newFuture[ResponseBatchRx]()
|
|
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
let reqBody = requestBatchEncode(calls) & "\r\n"
|
|
|
|
|
debug "Sending JSON-RPC batch",
|
|
|
|
|
address = $client.address, len = len(reqBody)
|
|
|
|
|
let res = await client.transport.write(reqBody)
|
2024-01-30 14:11:49 +07:00
|
|
|
|
|
|
|
|
# TODO: Add actions when not full packet was send, e.g. disconnect peer.
|
2024-10-22 21:58:46 +02:00
|
|
|
doAssert(res == reqBody.len)
|
2024-01-30 14:11:49 +07:00
|
|
|
|
|
|
|
|
return await client.batchFut
|
|
|
|
|
|
2024-02-19 08:53:10 +07:00
|
|
|
proc processData(client: RpcSocketClient) {.async: (raises: []).} =
|
2018-07-12 18:36:40 +01:00
|
|
|
while true:
|
2024-02-19 08:53:10 +07:00
|
|
|
var localException: ref JsonRpcError
|
2019-05-14 18:42:51 +03:00
|
|
|
while true:
|
2024-02-19 08:53:10 +07:00
|
|
|
try:
|
|
|
|
|
var value = await client.transport.readLine(defaultMaxRequestLength)
|
|
|
|
|
if value == "":
|
|
|
|
|
# transmission ends
|
|
|
|
|
await client.transport.closeWait()
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
let res = client.processMessage(value)
|
|
|
|
|
if res.isErr:
|
|
|
|
|
localException = newException(JsonRpcError, res.error)
|
|
|
|
|
break
|
|
|
|
|
except TransportError as exc:
|
|
|
|
|
localException = newException(JsonRpcError, exc.msg)
|
|
|
|
|
await client.transport.closeWait()
|
|
|
|
|
break
|
|
|
|
|
except CancelledError as exc:
|
|
|
|
|
localException = newException(JsonRpcError, exc.msg)
|
2019-05-14 18:42:51 +03:00
|
|
|
await client.transport.closeWait()
|
|
|
|
|
break
|
2018-07-12 18:36:40 +01:00
|
|
|
|
2024-02-19 08:53:10 +07:00
|
|
|
if localException.isNil.not:
|
|
|
|
|
for _,fut in client.awaiting:
|
|
|
|
|
fut.fail(localException)
|
|
|
|
|
if client.batchFut.isNil.not and not client.batchFut.completed():
|
|
|
|
|
client.batchFut.fail(localException)
|
2021-02-15 13:45:51 +01:00
|
|
|
|
2019-05-14 18:42:51 +03:00
|
|
|
# async loop reconnection and waiting
|
2024-02-19 08:53:10 +07:00
|
|
|
try:
|
2024-02-19 13:47:40 +07:00
|
|
|
info "Reconnect to server", address=`$`(client.address)
|
2024-02-19 08:53:10 +07:00
|
|
|
client.transport = await connect(client.address)
|
|
|
|
|
except TransportError as exc:
|
|
|
|
|
error "Error when reconnecting to server", msg=exc.msg
|
|
|
|
|
break
|
|
|
|
|
except CancelledError as exc:
|
2024-10-22 21:58:46 +02:00
|
|
|
debug "Server connection was cancelled", msg=exc.msg
|
2024-02-19 08:53:10 +07:00
|
|
|
break
|
2018-07-12 18:36:40 +01:00
|
|
|
|
|
|
|
|
proc connect*(client: RpcSocketClient, address: string, port: Port) {.async.} =
|
|
|
|
|
let addresses = resolveTAddress(address, port)
|
|
|
|
|
client.transport = await connect(addresses[0])
|
|
|
|
|
client.address = addresses[0]
|
2019-05-14 18:42:51 +03:00
|
|
|
client.loop = processData(client)
|
2019-06-12 16:44:19 +03:00
|
|
|
|
2024-01-07 14:40:59 +07:00
|
|
|
proc connect*(client: RpcSocketClient, address: TransportAddress) {.async.} =
|
|
|
|
|
client.transport = await connect(address)
|
|
|
|
|
client.address = address
|
|
|
|
|
client.loop = processData(client)
|
|
|
|
|
|
2019-06-17 19:56:19 +03:00
|
|
|
method close*(client: RpcSocketClient) {.async.} =
|
2021-04-06 21:48:32 +03:00
|
|
|
await client.loop.cancelAndWait()
|
|
|
|
|
if not client.transport.isNil:
|
|
|
|
|
client.transport.close()
|
|
|
|
|
client.transport = nil
|