Making an independent RPC server with autobahn wamp (twisted) - python

I need to create an independent class that provides an RPC server running in its own Python process using wamp and autobahn.
Following some guides found here, I managed to create this server in these classes saved into the file rpcwampserver.py:
from twisted.internet.defer import inlineCallbacks
from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
import multiprocessing
from twisted.internet import reactor
class Manager(ApplicationSession):
#inlineCallbacks
def onJoin(self, details):
print("session ready")
def test():
return u'hello!'
try:
yield self.register(test, u'rpc.test')
print("procedure registered")
except Exception as e:
print("could not register procedure: {0}".format(e))
class RPCWampServer:
def __init__(self):
self._url = u'ws://localhost:8080/ws'
self.real = u'realm'
self.runner = ApplicationRunner(url=self._url, realm=self.realm,
#debug=True, debug_wamp=True, debug_app=True
)
def start(self):
self.runner.run(Manager, start_reactor=False)
class RPC_Wamp_Server:
def __init__(self):
server = RPCWampServer()
server.start()
multi = multiprocessing.Process(target=reactor.run,args=())
multi.start()
If RPC_Wamp_Server is imported directly into a file close to rpcwampserver.py the the code works fine:
from rpcwampserver import RPC_Wamp_Server
c=RPC_Wamp_Server()
BUT
If I use this class inside a package located in a different path, the reactor doesn't work:
from mymodules.wamp.rpcwampserver import RPC_Wamp_Server
c=RPC_Wamp_Server()
The class RPC_Wamp_Server is found but the initialization of the Manager seems to be skipped.
Enabling the debug (commented in the code) it appears:
Starting factory [...]
and after a while
Stopped factory [...]
EDIT:
Using 127.0.0.1 instead of localhost solved the problem

Related

How to avoid ReactorNotRestartable in Autobahn Python

I have a python based page which recieves data by POST, which is then forwarded to the Crossbar server using Autobahn (Wamp). It works well the first 1-2 times but when it's called again after that it throws ReactorNotRestartable.
Now, I need this to work whichever way possible, either by reusing this "Reactor" based on a conditional check or by stopping it properly after every run. (The first one would be preferable because it might reduce the execution time)
Thanks for your help!
Edit:
This is in a webpage (Django View) so it needs to run as many times as the page is loaded/data is sent to it via POST.
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.application.internet import ClientService
from autobahn.wamp.types import ComponentConfig
from autobahn.twisted.wamp import ApplicationSession, WampWebSocketClientFactory
class MyAppSession(ApplicationSession):
def __init__(self, config):
ApplicationSession.__init__(self, config)
def onConnect(self):
self.join(self.config.realm)
def onChallenge(self, challenge):
pass
#inlineCallbacks
def onJoin(self, details):
yield self.call('receive_data', data=message)
yield self.leave()
def onLeave(self, details):
self.disconnect()
def onDisconnect(self):
reactor.stop()
message = "data from POST[]"
session = MyAppSession(ComponentConfig('realm_1', {}))
transport = WampWebSocketClientFactory(session, url='ws://127.0.0.1:8080')
endpoint = TCP4ClientEndpoint(reactor, '127.0.0.1', 8080)
service = ClientService(endpoint, transport)
service.startService()
reactor.run()
I figured out a probably hacky-and-not-so-good way by using multiprocessing and putting reactor.stop() inside onJoin() right after the function call. This way I don't have to bother with the "twisted running in the main thread" thing because its process gets killed as soon as my work is done.
Is there a better way?

Run WebSocket server in Thread

this is probably more a question about threading than about my websocket.
I'm using "SimpleWebSocket" from github ( https://github.com/dpallot/simple-websocket-server )
The example works fine:
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
class SimpleEcho(WebSocket):
def handleMessage(self):
# echo message back to client
self.sendMessage(self.data)
def handleConnected(self):
print self.address, 'connected'
def handleClose(self):
print self.address, 'closed'
server = SimpleWebSocketServer('', 8000, SimpleEcho)
server.serveforever()
The Server is running, I can connect and send Messages.
Now i try to run it as a Thread with those classes:
This one is supposed to create many threads including the WebSocketServer
from websockethread import WebSocketThread
class startManyThreads:
def __init__(self):
self.thread1 = WebSocketThread()
self.thread1.start()
if __name__ == "__main__":
startManyThreads = startManyThreads()
This class should run as my thread:
import threading
from SimpleWebSocketServer import SimpleWebSocketServer
from webSocketServer import WebSocketServer
class WebSocketThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
server = SimpleWebSocketServer('', 8000, WebSocketServer)
server.serveforever()
And this is the "customized" echo example:
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
class SimpleEcho(WebSocket):
def handleMessage(self):
# echo message back to client
self.sendMessage(self.data)
def handleConnected(self):
print self.address, 'connected'
def handleClose(self):
print self.address, 'closed'
I have also tried to derive this: class SimpleEcho(WebSocket, threading.Thread):
Any Ideas what i'm doing wrong?
&
Thanks alot in advance!
Edit:
The result when i run "simpleEcho" is that i get a prompt can connect via the websocket.html (provided on github), send and receive Messages
The result when i put it in a thread (anyone of the 3 ways i tried) is the same behaviour except when i try to "connect" from the websocket.html i get a "error: undefined". With nmap i checked and the Server seems so be running & listening to port 8000
Edit 2: Derived new Class from SimpleWebSocketServer
import threading
from SimpleWebSocketServer import SimpleWebSocketServer
class ThreadSimpleWebSocketThread(threading.Thread, SimpleWebSocketServer):
def __init__(self, serversocket):
threading.Thread.__init__(self)
self.serversocket = serversocket
def serveforever(self):
SimpleWebSocketServer.serversocket = self.serversocket
SimpleWebSocketServer.selectInterval = 0.1
SimpleWebSocketServer.listeners = [self.serversocket]
super(ThreadSimpleWebSocketThread, self).serveforever()
def run(self):
self.serveforever()
The main problem seems to be where you're starting the server. The Thread.__init__() method runs inside the main thread (of the caller), not the actual WebSocketThread(). This needs to be done in the Thread.run() method:
class WebSocketThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
server = SimpleWebSocketServer('', 8000, WebSocketServer)
server.serveforever()
The code inside run() actually runs inside the thread.
Note that because of the Global Interpreter Lock, threads won't improve performance much, and you'll probably need multiprocessing. However, if you just want to offload the I/O waiting, this should work fine.
Edit: From looking at this GitHub project, and rethinking what you're trying to do, this isn't trivial. You'll have to override WebSocket.serveforever() in your SimpleEcho() class and change it to accept the socket and pass the accepted socket to a Thread (see here).

Python UnitTest - Websocket Server

today I was working to create some unittests for my application: a websocket client..
In the real world, ws server is an embeeded pc in the home network.
Now, for my unittest, I'd like to create a fake ws server and use it to test the client.
can you suggest me some ws-server plug&play that I can call inside my unittest setup and use it for testing?
I tried to use Autobahn ws server, but it is not plug&play.. It should work but I'm not able to handle correctly it in a separate thread.
My goal is to test the client not to develop a dummy server.
Can you help me with something easy and ready-to-use?
Thanks in advance,
Salvo
Here the minimal code, I wrote, in order to avoid the blocking command (serve_forever)
I used ws4py as websocket library.
from wsgiref.simple_server import make_server
from ws4py.websocket import WebSocket
from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
from ws4py.server.wsgiutils import WebSocketWSGIApplication
import threading
class TestWebSocket(WebSocket):
def received_message(self, message):
self.send("+OK", False)
class TestServer:
def __init__(self, hostname='127.0.0.1', port=8080):
self.server = make_server(hostname,\
port,\
server_class=WSGIServer,\
handler_class=WebSocketWSGIRequestHandler,\
app=WebSocketWSGIApplication(handler_cls=TestWebSocket)\
)
self.server.initialize_websockets_manager()
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.start()
print("Server started for {}:{}".format(hostname, str(port)))
def shutdown(self):
self.server.shutdown()

Twisted Trial: How to test a MultiService with a client in it -- Reactor was unclean

I've inherited a Twisted MultiService that I'm trying to add tests to, but whatever I do, I end up with a DirtyReactorAggregateError. The service connects to a server with a twisted.application.internet.TCPClient. I think the error is because the TCPClient is not disconnecting, but I'm not sure how I'm supposed to disconnect it. What is the proper way to test a Twisted Service with a client in it?
Here's the test case:
from labrad.node import *
from twisted.trial import unittest
import os
from socket import gethostname
class NodeTestCase(unittest.TestCase):
def setUp(self):
import labrad
name = os.environ.get('LABRADNODE', gethostname()) + '_test'
self.node = Node(name,
labrad.constants.MANAGER_HOST,
labrad.constants.MANAGER_PORT)
self.node.startService()
#self.addCleanup(self.node.stopService)
def test_nothing(self):
self.assertEqual(3, 3)
def tearDown(self):
return self.node.stopService()
Here's the Node service itself:
class Node(MultiService):
"""Parent Service that keeps the node running.
If the manager is stopped or we lose the network connection,
this service attempts to restart it so that we will come
back online when the manager is back up.
"""
reconnectDelay = 10
def __init__(self, name, host, port):
MultiService.__init__(self)
self.name = name
self.host = host
self.port = port
def startService(self):
MultiService.startService(self)
self.startConnection()
def startConnection(self):
"""Attempt to start the node and connect to LabRAD."""
print 'Connecting to %s:%d...' % (self.host, self.port)
self.node = NodeServer(self.name, self.host, self.port)
self.node.onStartup().addErrback(self._error)
self.node.onShutdown().addCallbacks(self._disconnected, self._error)
self.cxn = TCPClient(self.host, self.port, self.node)
self.addService(self.cxn)
def stopService(self):
if hasattr(self, 'cxn'):
d = defer.maybeDeferred(self.cxn.stopService)
self.removeService(self.cxn)
del self.cxn
return defer.gatherResults([MultiService.stopService(self), d])
else:
return MultiService.stopService(self)
def _disconnected(self, data):
print 'Node disconnected from manager.'
return self._reconnect()
def _error(self, failure):
r = failure.trap(UserError)
if r == UserError:
print "UserError found!"
return None
print failure.getErrorMessage()
return self._reconnect()
def _reconnect(self):
"""Clean up from the last run and reconnect."""
## hack: manually clearing the dispatcher...
dispatcher.connections.clear()
dispatcher.senders.clear()
dispatcher._boundMethods.clear()
## end hack
if hasattr(self, 'cxn'):
self.removeService(self.cxn)
del self.cxn
reactor.callLater(self.reconnectDelay, self.startConnection)
print 'Will try to reconnect in %d seconds...' % self.reconnectDelay
You should refactor your service so that it can use something like MemoryReactor from twisted.test.proto_helpers (the one public module in the twisted.test package, although hopefully it will move out of twisted.test eventually).
The way that you use MemoryReactor is to pass it into your code as the reactor to use. If you then want to see what happens when the connection succeeds, look at some of its public attributes - tcpClients for connectTCP, tcpServers for listenTCP, etc. Your tests can then pull out the Factory instances which were passed to connectTCP/listenTCP, etc, and call buildProtocol on them and makeConnection on the result. To get an ITransport implementation you can use twisted.test.proto_helpers.StringTransportWithDisconnection. You might even look at the (private API! be careful! it'll break without warning! although it really should be public) twisted.test.iosim.IOPump to relay traffic between clients and servers.
If you really actually need to do whole-system non-deterministic real-world testing, with all the complexity and random unrelated failures that implies, here's an article on actually shutting down a client and server all the way.
I had a similar issue with trying to test a instance of an application. I ended up creating a Python base class that used setUp and tearDown methods to start/stop the application.
from twisted.application.app import startApplication
from twisted.application.service import IServiceCollection
from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest
class MyTest(unittest.TestCase):
def setUp(self):
startApplication(self.app, save=False)
#inlineCallbacks
def tearDown(self):
sc = self.app.getComponent(IServiceCollection)
yield sc.stopService()

Twisted deferred not callable after connection failure

Having my client factory here:
import logging, traceback
from twisted.internet.protocol import ClientFactory
from twisted.internet import defer, reactor, ssl
from twisted.application import service
from protocols.smpp.protocol import SMPPClientProtocol
class SMPPClientFactory(ClientFactory):
protocol = SMPPClientProtocol
def __init__(self, config):
self.config = config
def getConfig(self):
return self.config
def clientConnectionFailed(self, connector, reason):
print "clientConnectionFailed"
self.connectDeferred.errback(reason)
def clientConnectionLost(self, connector, reason):
print "clientConnectionLost"
def connect(self):
self.connectDeferred = defer.Deferred()
factory = SMPPClientFactory(self.config, self.msgHandler)
self.log.warning('Establishing TCP connection to %s:%d' % (self.config.host, self.config.port))
reactor.connectTCP(self.config.host, self.config.port, factory)
return self.connectDeferred
And it's launching code here:
import logging, traceback
from twisted.internet import reactor, defer
from protocols.smpp.configs import SMPPClientConfig
from protocols.smpp.smpp_operations import SMPPOperationFactory
from testbed.client import SMPPClientFactory
class SMPP(object):
def __init__(self, config=None):
if config is None:
config = SMPPClientConfig()
self.config = config
self.opFactory = SMPPOperationFactory(config)
def run(self):
try:
#Bind
SMPPClientFactory(self.config, self.handleMsg).connect().addErrback(self.connectFailed)
except Exception, e:
print "ERROR: %s" % str(e)
def connectFailed(self, reason):
print "Connection failed %s" % str(reason)
def handleMsg(self, smpp, pdu):
pass
if __name__ == '__main__':
config = SMPPClientConfig(host='127.0.0.1', port=2775, username='smppclient1', password='password',
log_level=logging.DEBUG)
logging.basicConfig(level=config.log_level, filename=config.log_file, format=config.log_format,datefmt=config.log_dateformat)
SMPP(config).run()
reactor.run()
When the connection is failing (the remote server is down), i get factory's clientConnectionFailed called but it is strangely getting a "exceptions.AttributeError: SMPPClientFactory instance has no attribute 'connectDeferred'".
I need to call the errback when the connection fails, it seems there's something missing when dealing with deferreds ..
On your launch code, you instantiated an SMPPClientFactory and called connect() on it. This particular instance will have the connectDeferred attribute. However, connect also instantiated another SMPPClientFactory: factory = SMPPClientFactory(self.config, self.msgHandler) and this is the instance you used to create the actual connection. This doesn't have the connectDeferred attribute because with this instance connect has never been called.

Categories

Resources