Recently, I’ve been toying around with again. After creating some simple apps I wanted to integrate some Erlang code inside a application (since that’s still my favorite day-to-day language, it’s used at work and I’m sort-of convinced Erlang would be a good choice for several of the applications we need to develop, integrated with our existing Python code). The most obvious solution would be to use an Erlang port, but this is IMHO rather cumbersome: it requires a developer to define a messaging format, parsing code for incoming messages, etc. There’s a available if you want to take this route.
A more elegant solution is creating a node using Python, similar to and equivalents. Luckily there’s an existing project working on a library to create Erlang nodes using Python and : .
One downside: it’s rather underdocumented… So here’s a very quick demo how to call functions on an Erlang node from within a Twisted application.
First of all we’ll create 2 Erlang functions: one which returns a simple “Hello” message, one which uses an extra process to return ‘pong’ messages on calls to ‘ping’, and counts those.
The code:
- -module(demo).
- -export([hello/1, ping/0, start/0]).
- hello(Name) ->
- Message = "Hello, " ++ Name,
- io:format(Message ++ "~n", []),
- Message.
- ping_loop(N) ->
- receive
- {get_id, From} ->
- From ! {pong, N},
- ping_loop(N + 1)
- end.
- ping() ->
- pingsrv ! {get_id, self()},
- receive
- {pong, N} -> ok
- end,
- {pong, N}.
- start() ->
- Pid = spawn_link(fun() -> ping_loop(1) end),
- register(pingsrv, Pid).
This should be straight-forward if you’re familiar with Erlang (which I assume).
The Python code is not that hard to get either: it follows the basic Twisted pattern. First one should create a connection to EPMD, the Erlang Port Mapper Daemon (used to find other nodes), then a connection to the server node should be created, and finally functions can be called (calls happen the same way as Erlang’s ).
Here’s the code. I’d advise to read it bottom-to-top:
- import sys
- from twisted.internet import reactor
- import twotp
- def error(e):
- '''A generic error handler'''
- print 'Error:'
- print e
- reactor.stop()
- def do_pingpong(proto):
- def handle_pong(result):
- # Parse the result
- # 'ping' returns a tuple of an atom ('pong') and an integer (the pong
- # id)
- # In TwOTP, an Atom object has a 'text' attribute, which is the string
- # form of the atom
- text, id_ = result[0].text, result[1]
- print 'Got ping result: %s %d' % (text, id_)
- # Recurse
- reactor.callLater(1, do_pingpong, proto)
- # Call the 'ping' function of the 'demo' module
- d = proto.factory.callRemote(proto, 'demo', 'ping')
- # Add an RPC call handler
- d.addCallback(handle_pong)
- # And our generic error handler
- d.addErrback(error)
- def call_hello(proto, name):
- def handle_hello(result):
- print 'Got hello result:', result
- # Erlang strings are lists of numbers
- # The default encoding is Latin1, this might need to be changed if your
- # Erlang node uses another encoding
- text = ''.join(chr(c) for c in result).decode('latin1')
- print 'String form:', text
- # Start pingpong loop
- do_pingpong(proto)
- # Call the 'hello' function of the 'demo' module, and pass in argument
- # 'name'
- d = proto.factory.callRemote(proto, 'demo', 'hello', name)
- # Add a callback for this function call
- d.addCallback(handle_hello)
- # And our generic error handler
- d.addErrback(error)
- def launch(epmd, remote, name):
- '''Entry point of our demo application'''
- # Connect to a node. This returns a deferred
- d = epmd.connectToNode(remote)
- # Add a callback, called when the connection to the node is established
- d.addCallback(call_hello, name)
- # And add our generic error handler
- d.addErrback(error)
- def main():
- remote = sys.argv[1]
- name = sys.argv[2]
- # Read out the Erlang cookie value
- cookie = twotp.readCookie()
- # Create a name for this node
- this_node = twotp.buildNodeName('demo_client')
- # Connect to EPMD
- epmd = twotp.OneShotPortMapperFactory(this_node, cookie)
- # Call our entry point function when the Twisted reactor is started
- reactor.callWhenRunning(launch, epmd, remote, name)
- # Start the reactor
- reactor.run()
- if __name__ == '__main__':
- main()
Finally, to run it, you should first start a server node, and run the ‘pingsrv’ process:
- MacBook:pyping nicolas$ erl -sname test@localhost
- Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [hipe] [kernel-poll:false]
- Eshell V5.6.5 (abort with ^G)
- (test@localhost)1> c(demo).
- {ok,demo}
- (test@localhost)2> demo:start().
- true
Notice we started erl providing test@localhost as short node name.
Now we can launch our client:
- (pythonenv)MacBook:pyping nicolas$ python hello.py 'test' Nicolas
- Got hello result: [72, 101, 108, 108, 111, 44, 32, 78, 105, 99, 111, 108, 97, 115]
- String form: Hello, Nicolas
- Got ping result: pong 1
- Got ping result: pong 2
- Got ping result: pong 3
‘test’ is the shortname of the server node.
You can stop the ping loop using CTRL-C. If you restart the client afterwards, you can see the ping IDs were retained:
- (pythonenv)MacBook:pyping nicolas$ python hello.py 'test' Nicolas
- Got hello result: [72, 101, 108, 108, 111, 44, 32, 78, 105, 99, 111, 108, 97, 115]
- String form: Hello, Nicolas
- Got ping result: pong 4
- Got ping result: pong 5
That’s about it. Using TwOTP you can also develop a node which exposes functions, which can be called from an Erlang node using rpc:call/4. Check the documentation provided with TwOTP for a basic example of this feature.
Combining Erlang applications as distributed, fault tolerant core infrastructure and Python/Twisted applications for ‘everyday coding’ can be an interesting match in several setups, an TwOTP provides all required functionalities to integrate the 2 platforms easily.
Posted in Development, .
Tagged with Development, , , , .
By – April 19, 2009
from: