[FEATURE] Add http json rpc proxy (#105)

This commit is contained in:
KonradStaniec 2021-06-29 17:13:31 +02:00 committed by GitHub
parent 809172abe4
commit 147ef3f562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 10 deletions

View File

@ -36,7 +36,8 @@ proc newRpcRouter*: RpcRouter {.deprecated.} =
proc register*(router: var RpcRouter, path: string, call: RpcProc) =
router.procs.add(path, call)
proc clear*(router: var RpcRouter) = router.procs.clear
proc clear*(router: var RpcRouter) =
router.procs.clear
proc hasMethod*(router: RpcRouter, methodName: string): bool = router.procs.hasKey(methodName)
@ -71,17 +72,19 @@ proc route*(router: RpcRouter, node: JsonNode): Future[StringOfJson] {.async, gc
return wrapError(INVALID_REQUEST, "'method' missing or invalid")
let rpcProc = router.procs.getOrDefault(methodName)
let params = node.getOrDefault("params")
if rpcProc == nil:
return wrapError(METHOD_NOT_FOUND, "'" & methodName & "' is not a registered RPC method", id)
else:
try:
let res = await rpcProc(if params == nil: newJArray() else: params)
return wrapReply(id, res)
let params = node.getOrDefault("params")
try:
let res = await rpcProc(if params == nil: newJArray() else: params)
return wrapReply(id, res)
except CatchableError as err:
debug "Error occurred within RPC", methodName = methodName, err = err.msg
return wrapError(
SERVER_ERROR, methodName & " raised an exception", id, newJString(err.msg))
except CatchableError as err:
debug "Error occurred within RPC", methodName = methodName, err = err.msg
return wrapError(
SERVER_ERROR, methodName & " raised an exception", id, newJString(err.msg))
proc route*(router: RpcRouter, data: string): Future[string] {.async, gcsafe.} =
## Route to RPC from string data. Data is expected to be able to be converted to Json.

46
json_rpc/rpcproxy.nim Normal file
View File

@ -0,0 +1,46 @@
{.push raises: [Defect].}
import
./servers/[httpserver],
./clients/[httpclient]
type RpcHttpProxy* = ref object of RootRef
rpcHttpClient*: RpcHttpClient
rpcHttpServer*: RpcHttpServer
proc proxyCall(client: RpcHttpClient, name: string): RpcProc =
return proc (params: JsonNode): Future[StringOfJson] {.async.} =
let res = await client.call(name, params)
return StringOfJson($res)
proc new*(T: type RpcHttpProxy, listenAddresses: openArray[string]): T {.raises: [Defect, CatchableError].}=
let client = newRpcHttpClient()
let router = RpcRouter.init()
T(rpcHttpClient: client, rpcHttpServer: newRpcHttpServer(listenAddresses, router))
proc newRpcHttpProxy*(listenAddresses: openArray[string]): RpcHttpProxy {.raises: [Defect, CatchableError].} =
RpcHttpProxy.new(listenAddresses)
proc start*(proxy:RpcHttpProxy, proxyServerUrl: string) {.async.} =
proxy.rpcHttpServer.start()
await proxy.rpcHttpClient.connect(proxyServerUrl)
proc start*(proxy:RpcHttpProxy, proxyServerAddress: string, proxyServerPort: Port) {.async.} =
proxy.rpcHttpServer.start()
await proxy.rpcHttpClient.connect(proxyServerAddress, proxyServerPort)
template rpc*(server: RpcHttpProxy, path: string, body: untyped): untyped =
server.rpcHttpServer.rpc(path, body)
proc registerProxyMethod*(proxy: var RpcHttpProxy, methodName: string) =
try:
proxy.rpcHttpServer.register(methodName, proxyCall(proxy.rpcHttpClient, methodName))
except CatchableError as err:
# Adding proc type to table gives invalid exception tracking, see Nim bug: https://github.com/nim-lang/Nim/issues/18376
raiseAssert err.msg
proc stop*(rpcHttpProxy: RpcHttpProxy) {.raises: [Defect, CatchableError].} =
rpcHttpProxy.rpcHttpServer.stop()
proc closeWait*(rpcHttpProxy: RpcHttpProxy) {.async.} =
await rpcHttpProxy.rpcHttpServer.closeWait()

View File

@ -299,9 +299,15 @@ proc addStreamServer*(server: RpcHttpServer, address: string, port: Port) =
proc new*(T: type RpcHttpServer): T =
T(router: RpcRouter.init(), servers: @[])
proc new*(T: type RpcHttpServer, router: RpcRouter): T =
T(router: router, servers: @[])
proc newRpcHttpServer*(): RpcHttpServer =
RpcHttpServer.new()
proc newRpcHttpServer*(router: RpcRouter): RpcHttpServer =
RpcHttpServer.new(router)
proc newRpcHttpServer*(addresses: openArray[TransportAddress]): RpcHttpServer =
## Create new server and assign it to addresses ``addresses``.
result = newRpcHttpServer()
@ -312,6 +318,11 @@ proc newRpcHttpServer*(addresses: openArray[string]): RpcHttpServer =
result = newRpcHttpServer()
result.addStreamServers(addresses)
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter): RpcHttpServer =
## Create new server and assign it to addresses ``addresses``.
result = newRpcHttpServer(router)
result.addStreamServers(addresses)
proc start*(server: RpcHttpServer) =
## Start the RPC server.
for item in server.servers:

View File

@ -1,4 +1,4 @@
{. warning[UnusedImport]:off .}
import
testrpcmacro, testserverclient, testethcalls, testhttp
testrpcmacro, testserverclient, testethcalls, testhttp, testproxy

47
tests/testproxy.nim Normal file
View File

@ -0,0 +1,47 @@
import
unittest, json, chronicles,
../json_rpc/[rpcclient, rpcserver, rpcproxy]
let srvAddress = "localhost:8545"
var srv = newRpcHttpServer([srvAddress])
let proxySrvAddress = "localhost:8546"
var proxy = newRpcHttpProxy([proxySrvAddress])
var client = newRpcHttpClient()
let duplicatedProcedureName = "duplicated"
# Create RPC on server
srv.rpc("myProc") do(input: string, data: array[0..3, int]):
return %("Hello " & input & " data: " & $data)
# Create RPC on proxy server
proxy.registerProxyMethod("myProc")
# Create standard handler on server
proxy.rpc("myProc1") do(input: string, data: array[0..3, int]):
return %("Hello " & input & " data: " & $data)
srv.start()
waitFor proxy.start("localhost", Port(8545))
waitFor client.connect("localhost", Port(8546))
suite "Proxy RPC":
test "Successful RPC call thorugh proxy":
let r = waitFor client.call("myProc", %[%"abc", %[1, 2, 3, 4]])
check r.getStr == "Hello abc data: [1, 2, 3, 4]"
test "Successful RPC call no proxy":
let r = waitFor client.call("myProc1", %[%"abc", %[1, 2, 3, 4]])
check r.getStr == "Hello abc data: [1, 2, 3, 4]"
test "Missing params":
expect(CatchableError):
discard waitFor client.call("myProc", %[%"abc"])
test "Method missing on server and proxy server":
expect(CatchableError):
discard waitFor client.call("missingMethod", %[%"abc"])
srv.stop()
waitFor srv.closeWait()
proxy.stop()
waitFor proxy.closeWait()