diff --git a/json_rpc/router.nim b/json_rpc/router.nim index 03ae5de..45174ec 100644 --- a/json_rpc/router.nim +++ b/json_rpc/router.nim @@ -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. diff --git a/json_rpc/rpcproxy.nim b/json_rpc/rpcproxy.nim new file mode 100644 index 0000000..b60f4d3 --- /dev/null +++ b/json_rpc/rpcproxy.nim @@ -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() \ No newline at end of file diff --git a/json_rpc/servers/httpserver.nim b/json_rpc/servers/httpserver.nim index a2777f9..da20065 100644 --- a/json_rpc/servers/httpserver.nim +++ b/json_rpc/servers/httpserver.nim @@ -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: diff --git a/tests/all.nim b/tests/all.nim index 2913406..eb0270d 100644 --- a/tests/all.nim +++ b/tests/all.nim @@ -1,4 +1,4 @@ {. warning[UnusedImport]:off .} import - testrpcmacro, testserverclient, testethcalls, testhttp + testrpcmacro, testserverclient, testethcalls, testhttp, testproxy diff --git a/tests/testproxy.nim b/tests/testproxy.nim new file mode 100644 index 0000000..e7f7b1d --- /dev/null +++ b/tests/testproxy.nim @@ -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()