Skip to content

A system for generating remote-procedure-calls for any pair of server and client

License

Notifications You must be signed in to change notification settings

choltreppe/polyrpc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

polyrpc

A system for generating remote-procedure-calls for any pair of server and client.

Example

have a lookn at tests/ to get a general idea of the system.

predefined connections

To establish a connection you can use the predefined ones.
For example to connect karax client with prologue server:

import polyrpc/connections/http

makeRpcConnection(
  client = $/karaxRpc,
  server = $/prologueRpc
)

The polyrpc/connections/http provides the makeRpcConnection macro, to generate a http connection.
client is the clients rpc connections module, and server the server one.
for convinence $/ can be used as a shortcut for predefined ones.
So this is the same without $/:

makeRpcConnection(
  client = polyrpc/connections/http/clients/karaxRpc,
  server = polyrpc/connections/http/servers/prologueRpc
)

And that expands to:

when defined(js):
  import polyrpc/connections/http/clients/karaxRpc
else:
  import polyrpc/connections/http/servers/prologueRpc

using the connection

first of all you need to seperate your code into server and client parts.
to do so use the server and client macros to define sections like so:

server:
  # do some stuff on server

client:
  # do some stuff on client
  
# do some stuff on both

server: # do some more on server

or use them as pragma on single procs:

proc foo {.server.}

now if you want to make a server proc rpc callable just add the rpc pragma with the url it schould use,
or the rpca pragma for auto generated urls

server:
  proc add(a,b: int): int {.rpc("/add").} = a + b
  proc isNeg(x: int): bool {.rpca.} = x < 0

You can change the base url for rpca (default is "/rpc")

setRpcAnnonymousUrl "/foo"

custom connections

server

To define a custom cennection import polyrpc/server and use the makeRpcServerHandler template.
Here is how prologueRpcs initRpc is defined:

template initRpc*(app: Prologue): untyped =
  makeRpcServerHandler:
    app.post(requestUrl,
      proc(ctx {. inject .}: Context) {.async.} =
        {.cast(gcsafe).}:
          resp callProc(ctx.request.body)
    )

inside the definition you can use:
requestUrl
callProc to call the proc (it expects a string as input and returns a string, it does the rest for you)

client

To define a custom cennection import polyrpc/client and use the makeRpcClientCb or the makeRpcClientReturn template.

Use makeRpcClientCb for a callback pattern.
With the callback pattern:

proc foo(x: int): bool {.rpca.}

becomes:

proc foo(x: int, cb: proc(r: bool))

Here is how karaxRpc is defined using makeRpcClientCb:

makeRpcClientCb:
  ajaxPost(requestUrl.kstring, [], requestBody.kstring,
    proc(httpStatus: int, response: kstring) =
      case httpStatus
      of 200: callback($response)
      else: discard
  )

Inside the defenition there are following things defined:
requestUrl: the url which the rpc should use
requestBody: the body string that needs to be send
callback: the callback that needs to be called with the response string

Use makeRpcClientReturn for generating functions that return the result. (just like the original proc, but with potentialy modified return type)
Here is how ajaxFutureRpc is defined using makeRpcClientCb:

makeRpcClientReturn( when T is Future: T else: Future[T] ):
  return newPromise[T](proc(resolve: proc(response: T)) =
    var xhr = new_xmlhttp_request()
    if xhr.is_nil: return
    
    proc onRecv(e:Event) =
      if xhr.readyState == rsDONE:
        if xhr.status == 200:
          resolve resultVal($xhr.responseText)

    xhr.onreadystatechange = onRecv
    xhr.open("POST", requestUrl)
    xhr.send(requestBody.cstring)
  )

The first parameter defines what the return type should be,
based on the original return type T
The second parameter defines the proc body. There are following things defined inside:
requestUrl: the url which the rpc should use
requestBody: the body string that needs to be send
resultVal: proc to turn result string into actual type

change serializing / deserializing procs

for example to use jsony:

static:
  serializeCall =
    proc(val: NimNode): NimNode =
      quote do: `val`.toJson

  deserializeCall =
    proc(str, T: NimNode): NimNode =
      quote do: `str`.fromJson(`T`)

This will probably be simplyfied with macros in the future.

Contribution

Issues and PRs welcome

About

A system for generating remote-procedure-calls for any pair of server and client

Resources

License

Stars

Watchers

Forks

Packages

No packages published