SECRET OF CSS

A Simple Way To Make RPCs With Python | by Tate Galbraith | Sep, 2022


Tell other systems what to do — the easy way

1*cf nHmZ YMKWONZYpkYo5w
Photo by GuerrillaBuzz Crypto PR on Unsplash

Remote procedure calls (RPCs) have been around forever and are one of the best ways to execute functions across distributed systems. There are many ways to get another system to run some code, like using a web API or some socket-based protocol, but most of them require a higher upfront development cost.

With a web API you’ll have to provide an HTTP interface while other socket-based solutions might require more boilerplate code just to get off the ground. This is especially true when you try and build your own interface from scratch. Why waste time when you can just use battle-tested RPCs?

In this article we’ll investigate one of the simplest ways to implement RPCs in Python. We’ll use the RPyC library, build a simple distributed client/server system, and see just how easy it is to implement.

Building the service

In order to have something for our client RPCs to work with, we’ll need a basic server setup listening for incoming connections. Let’s pull in the RPyC library now and create a new service to handle this:

# server.pyimport rpyc@rpyc.service
class TestService(rpyc.Service):
@rpyc.exposed
def foo(self):
return 'foo'

@rpyc.exposed
def bar(self):
return 'bar'

This is a simple RPyC service that exposes two methods foo and bar which will each return a simple string message when called. There are multiple ways to define exposed methods, personally I like the decorator method so that is what I’ve used here.

Each exposed method will appear in the root level of the RPC connection object and be callable by any clients that connect. If you do not wish to expose a method to clients, simply leave the decorator off of it in your service.

@rpyc.exposed
def my_public_method():
pass
def my_private_method():
pass

There are also other special methods like on_connect and on_disconnect that allow you to run code when a client connects to or disconnects from the server. Check the documentation for more implementation details.

Now that we have our service defined, we can craft the actual RPC server code that runs the service.

Building the server

In order to actually use the service we created, we’ll need to run it and then listen for incoming calls. To do this, we’ll use a simple ThreadedServer from RPyC. This will spin off a new thread for each incoming call, making it a bit more efficient than the normal server. It will be able to handle slightly higher load and is very simple to implement.

Remember though, these threads are only for incoming calls, if you want to thread the server instance you’ll need to handle that yourself. For the sake of this example, we’ll just keep it simple.

Now, let’s make a few additions to our service code from before:

# server.pyimport rpyc
from rpyc.utils.server import ThreadedServer
@rpyc.service
class TestService(rpyc.Service):
@rpyc.exposed
def foo(self):
return 'foo'

@rpyc.exposed
def bar(self):
return 'bar'

print('starting server')
server = ThreadedServer(TestService, port=18811)
server.start()

Here we are still using the exact same service, but now we’re passing it into our ThreadedServer and starting it up. It will listen on port 18811 for incoming RPC requests.

You can execute the server with the following syntax:

python3 server.py

Next, let’s leave this server running and test our client against it to start making trying some calls.

Building the client

In order for us to make RPCs we need a host and port to connect to. Since we’re just testing our service we can do all of this over localhost. In production you might be connecting to an IP address or domain name of a remote server.

Let’s look at what a simple loopback client looks like:

import rpycconnection = rpyc.connect('localhost', 18811)print(connection.root.foo())
print(connection.root.bar())

In this example, we connect to localhost on port 18811 which is where our server (which should still be running) is listening for incoming connections. Once we connect we can then start making calls against those methods defined in our service.

We call both the foo and bar methods which return the expected strings. These methods live under the root level (or NetRef) so all we need to do is look for them there. You can also create custom NetRefs, but this is a bit more complex for this article.

Congratulations, you’ve now made some RPCs using Python!





News Credit

%d bloggers like this: