Firstly, there is an IO class, which on __init__ is passed the asyncio loop object (io = IO(loop)), created earlier in the main class. IO class then at some point initializes the Socket class by doing self.socket = Socket(self), so that the socket object has a backwards access. Later, the Socket class initializes Websocket class which is a subclass of Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
So, socket object calls self.transport.open() (where self.transport = Websocket(self)), which creates autobahn factory, creates asyncio connection by doing self.socket.loop.create_connection() and then adds the coro future to the loop by executing run_until_complete().
Now, this is where the problem starts:
autobahn factory requires a class, which must inherit from autobahn.asyncio.websocket.WebSocketClientProtocol
My class Protocol(WebSocketClientProtocol) has the usual:
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
print('socket opened!')
This works perfectly fine, the print('socket opened!') does print the string and my server also says the connection is open.
The question:
from the Protocol() class, when the onOpen() callback is called by autobahn, how can I make this method call the transport.onOpen() method and do print('print me please!')?
OK, so I fixed it! Easily done with PyDispatch module.
Here is my solution:
import asyncio
from pydispatch import dispatcher
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
dispatcher.connect(self.onOpen, signal='open', sender=dispatcher.Anonymous)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
dispatcher.send(signal='open')
UPDATE
I've got another, IMO better solution to this. This one is not using PyDispatch. Since there is a callback when an asyncio task finishes, which returns the user-defined protocol object (which inherits from WebSocketClientProtocol), we can use that to link the two objects together:
import asyncio
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Protocol(WebSocketClientProtocol):
def __init__(self):
self.ws = None
super().__init__()
#asyncio.coroutine
def onConnect(self, response):
pass # connect handeled when SocketIO 'connect' packet is received
#asyncio.coroutine
def onOpen(self):
self.ws.onOpen()
#asyncio.coroutine
def onMessage(self, payload, isBinary):
self.ws.onMessage(payload=payload, isBinary=isBinary)
#asyncio.coroutine
def onClose(self, wasClean, code, reason):
if not wasClean:
self.ws.onError(code=code, reason=reason)
self.ws.onClose()
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket, **kwargs):
super().__init__(socket)
loop = kwargs.pop('loop', None)
self.loop = loop or asyncio.get_event_loop()
self.transport = None
self.protocol = None
self.ready = True
def open(self):
url = self.prepareUrl()
if bool(self.socket.options.query):
url = '{0}?{1}'.format(url, self.socket.options.query)
factory = WebSocketClientFactory(url=url, headers=self.socket.options.headers)
factory.protocol = Protocol
coro = self.loop.create_connection(factory, host=self.socket.options.host, port=self.socket.options.port, ssl=self.socket.options.secure)
task = self.loop.create_task(coro)
task.add_done_callback(self.onWebSocketInit)
def onWebSocketInit(self, future):
try:
self.transport, self.protocol = future.result()
self.protocol.ws = self
except Exception:
self.onClose()
def send(self, data):
self.protocol.sendMessage(payload=data.encode('utf-8'), isBinary=False)
return self
def close(self):
if self.isOpen:
self.protocol.sendClose()
return self
def onOpen(self):
super().onOpen()
self.socket.setBuffer(False)
def onMessage(self, payload, isBinary):
if not isBinary:
self.onData(payload.decode('utf-8'))
else:
self.onError('Message arrived in binary')
def onClose(self):
super().onClose()
self.socket.setBuffer(True)
def onError(self, code, reason):
self.socket.onError(reason)
Related
i have a mqtt initializer class
class Initializer():
def __init__(self):
self.client = mqtt.Client(mqtt_server+str(int(time.time())))
self.client.username_pw_set(
username=mqtt_username, password=mqtt_password)
self.client.on_connect = self.on_connect
self.client.subscribe(
"topic")
self.client.connect(broker, mqtt_port)
self.client.loop_start()
inherited this class to another class
class publishdata(Initializer):
def __init__(self):
super().__init__()
self.client.on_message = self.on_message
self.client.on_subscribe = self.on_subscribe
def on_subscribe(self, client, userdata, mid, granted_qos):
print("Subscription started")
def on_message(self, client, userdata, message):
print("message.topic", message.payload)
def begin(self,topic,data):
self.client.publish(
topic, str(data))
publishData = PublishData()
publishData.begin(topic,data)
publish and subscribe works properly. but when i call publishedData .client.connect and client.loop_start in the Initializer class also runs.i dont want that to be excecuted on every publish call. is there any better way to do this
If you don't want to initialize a client every time that you create a new PublishData instance, then the code that does this initialization does not belong in the PublishData class. One possible solution is to create the client outside of the PublishData class and pass it in. This is called dependency injection. It looks something like this:
def start_client():
client = mqtt.Client(mqtt_server+str(int(time.time())))
client.username_pw_set(username=mqtt_username, password=mqtt_password)
client.on_connect = self.on_connect
client.subscribe("topic")
client.connect(broker, mqtt_port)
client.loop_start()
return client
client = start_client()
publish_data = PublishData(client)
publishData.begin(topic,data)
I am writing a custom SSL proxy with Twisted. I keep running in to an issue that happens every so often and I cant figure out what the problem is.
When I try to connect the client transport to the server's transport through the registerProducer function exactly as twisted.protocols.portforward functions I keep getting this error.
File "/opt/Memory/Mobile/Proxy/forwarder.py", line 40, in connectionMade
self.peer.transport.registerProducer(self.transport, True)
File "/usr/lib/python3.9/site-packages/twisted/protocols/tls.py", line 602, in registerProducer
self.transport.registerProducer(producer, True)
File "/usr/lib/python3.9/site-packages/twisted/internet/_newtls.py", line 233, in registerProducer
FileDescriptor.registerProducer(self, producer, streaming)
File "/usr/lib/python3.9/site-packages/twisted/internet/abstract.py", line 104, in registerProducer
raise RuntimeError(
builtins.RuntimeError: Cannot register producer <twisted.protocols.tls._ProducerMembrane object at 0x7fb5799f0910>, because producer <twisted.protocols.tls._ProducerMembrane object at 0x7fb579b474c0> was never unregistered.
Here are my Inherited Classes from twisted?
from twisted.internet import reactor
from twisted.internet import ssl
from twisted.protocols import portforward
from twisted.internet import protocol
from twisted.python import log
import sys
##SSLProxy base class that will be inherited
class SSLProxy(protocol.Protocol):
noisy = True
peer = None
def setPeer(self, peer):
#log.msg("SSLProxy.setPeer")
self.peer = peer
def connectionLost(self, reason):
#log.msg("SSLProxy.connectionLost")
if self.peer is not None:
self.peer.transport.loseConnection()
self.peer = None
elif self.noisy:
log.msg("Unable to connect to peer: {}".format(reason))
def dataReceived(self, data):
#log.msg("SSLProxy.dataReceived")
if self.peer is not None:
self.peer.transport.write(data)
##Foward data from Proxy to => Remote Server
class SSLProxyClient(SSLProxy):
def connectionMade(self):
#log.msg("SSLProxyClient.connectionMade")
self.peer.setPeer(self)
self.transport.registerProducer(self.peer.transport, True)
self.peer.transport.registerProducer(self.transport, True)
# We're connected, everybody can read to their hearts content.
self.peer.transport.resumeProducing()
class SSLProxyClientFactory(protocol.ClientFactory):
protocol = SSLProxyClient
def setServer(self, server):
#log.msg("SSLProxyClientFactory.setServer")
self.server = server
def buildProtocol(self, *args, **kw):
#log.msg("SSLProxyClientFactory.buildProtocol")
prot = protocol.ClientFactory.buildProtocol(self, *args, **kw)
prot.setPeer(self.server)
return prot
def clientConnectionFailed(self, connector, reason):
#log.msg("SSLProxyClientFactory.clientConnectionFailed")
self.server.transport.loseConnection()
class SSLProxyServer(SSLProxy):
clientProtocolFactory = SSLProxyClientFactory
reactor = None
def connectionMade(self):
log.msg("SSLProxyServer.connectionMade")
#Get Current SSL Context
ssl_context = self.transport._tlsConnection.get_context()
#Hack to get SNI to do two functions in diffrent classes
ssl_context._finishSNI = self.SNICallback
def SNICallback(self, connection):
#log.msg("SSLProxyServer.SNICallback: {}".format(connection))
#print(connection.get_context().new_host)
self.transport.pauseProducing()
#self.transport.transport.pauseProducing()
#print(dir())
self.dst_host, self.dst_port = connection.get_context().new_host
#Setup Clients
self.client = self.clientProtocolFactory()
self.client.setServer(self)
#Start stuff
log.msg('Redirecting to {}:{}'.format(self.dst_host, self.dst_port))
if self.reactor is None:
self.reactor = reactor
log.msg("Making Connection to Dest Server: {}:{}".format(self.dst_host, self.dst_port))
self.reactor.connectSSL(self.dst_host, self.dst_port, self.client, ssl.ClientContextFactory())
#self.transport.resumeProducing()
#Client -> Proxy
def dataReceived(self, data):
log.msg("SSLProxyServer.dataReceived: {}".format(data))
#Call Inherited Function
super().dataReceived(data)
class SSLProxyFactory(protocol.Factory):
"""Factory for port forwarder."""
protocol = SSLProxyServer
def __init__(self):
super().__init__()
#log.msg("SSLProxyFactory.__init__")
def sslToSSL(localport, remotehost, remoteport, serverContextFactory):
log.msg("SSL on localhost:{} forwarding to SSL {}:{}".format(localport, remotehost, remoteport))
return reactor.listenSSL(localport, SSLProxyFactory(), serverContextFactory)
Any guidance would be appreciated.
I found that if you just unregister the current Producer you can register the new one.
##Foward data from Proxy to => Remote Server
class SSLProxyClient(SSLProxy):
def connectionMade(self):
#log.msg("SSLProxyClient.connectionMade")
self.peer.setPeer(self)
self.transport.registerProducer(self.peer.transport, True)
self.peer.transport.unregisterProducer()
self.peer.transport.registerProducer(self.transport, True)
# We're connected, everybody can read to their hearts content.
self.peer.transport.resumeProducing()
Is it possible, if multiple socket clients are connected to a tornado websocket server, to send a message to a specific one?
I don't know if there is a possibility of getting a client's id and then send a message to that id.
My client code is:
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado import gen
from tornado.websocket import websocket_connect
class Client(object):
def __init__(self, url, timeout):
self.url = url
self.timeout = timeout
self.ioloop = IOLoop.instance()
self.ws = None
self.connect()
PeriodicCallback(self.keep_alive, 20000, io_loop=self.ioloop).start()
self.ioloop.start()
#gen.coroutine
def connect(self):
print "trying to connect"
try:
self.ws = yield websocket_connect(self.url)
except Exception, e:
print "connection error"
else:
print "connected"
self.run()
#gen.coroutine
def run(self):
while True:
msg = yield self.ws.read_message()
print msg
if msg is None:
print "connection closed"
self.ws = None
break
def keep_alive(self):
if self.ws is None:
self.connect()
else:
self.ws.write_message("keep alive")
if __name__ == "__main__":
client = Client("ws://xxx", 5 )
When your client connects to the websocket server will be invoked method 'open' on WebSocketHandler, in this method you can keep socket in the Appliaction.
Like this:
from tornado import web
from tornado.web import url
from tornado.websocket import WebSocketHandler
class Application(web.Application):
def __init__(self):
handlers = [
url(r"/websocket/server/(?P<some_id>[0-9]+)/", WebSocketServer),
]
web.Application.__init__(self, handlers)
self.sockets = {}
class WebSocketServer(WebSocketHandler):
def open(self, some_id):
self.application.sockets[some_id] = self
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
You also may use message for connection, in this message you have to tell socket id and save socket into the Application.
Here is my simple twisted server code:
from twisted.internet.protocol import Protocol,Factory
from twisted.internet import reactor
class MultiEcho(Protocol):
def __init__(self, factory):
self.recvBuf = ""
self.factory = factory
def connectionMade(self):
self.factory.echoers.append(self)
print self.factory.echoers
def dataReceived(self, data):
self.recvBuf += data
self.transport.write(data)
def connectionLost(self, reason):
self.factory.echoers.remove(self)
class MultiEchoFactory(Factory):
def __init__(self):
self.echoers=[]
def buildProtocol(self, addr):
return MultiEcho(self)
if __name__ == '__main__':
reactor.listenTCP(4321, MultiEchoFactory())
reactor.run()
Use 'telnet 127.0.0.1 4321' ,will print a list:
[<__main__.MultiEcho instance at 0x0000000002EA6B08>]
add another 'telnet 127.0.0.1 4321',the list is:
[<__main__.MultiEcho instance at 0x0000000002EA6B08>, <__main__.MultiEcho instance at 0x0000000002EA6EC8>]
I know a client is a protocol object's instance ,and the list is MultiEchoFactory.echoers,so I want to keep the list,
import them in another .py file use list[n].transport.write(somedata).
I try from myserver import MultiEchoFactory,but the MultiEchoFactory.echoers is empty.
I also rewrite the code
class MultiEchoFactory(Factory):
def __init__(self):
self.echoers=[]
to
class MultiEchoFactory(Factory):
echoers=[]
But I still can't use the protocol list in another .py file.
So,what should I do?
You need to save it in buildProtocol(), i.e.:
class MultiEchoFactory(Factory):
echoers=[]
def buildProtocol(self, addr):
echoer = MultiEcho(self)
MultiEchoFactory.echoers.append(echoer)
return echoer
What I need is a sort of man-in-the-middle implementation: I need a server who receives connections from clients (binary data with different lengths) and forwards the stream to a server it connects to (acting as a client), and then sends the data back from the server it is connected to, to the clients.
It actually works standing between the clients and the servers, and passing the data they exchange (which is a stream, so it continuously get from one side and sends to the other one).
The server is static, so it is always the same, and its address can even be hardcoded; however when a client drops the connection, this server must also drop the connection to the "real" server.
I've been looking around, but couldn't find a solution or an example for such a simple problem.
The code I've made works actually, but I have not yet managed to find how to put a reference into the server part that says "this is your assigned client", or into the client that says "this is your server". Here's my code:
#!/usr/bin/env python
from twisted.internet import protocol, reactor
from twisted.protocols import basic
client = None
server = None
class ServerProtocol(protocol.Protocol):
def connectionMade(self):
global server
factory = protocol.ClientFactory()
factory.protocol = ClientProtocol
server = self
reactor.connectTCP('localhost', 1324, factory)
def dataReceived(self, data):
global client
client.transport.write(data)
class ClientProtocol(protocol.Protocol):
def connectionMade(self):
global client
# Here's the instance of the client
client = self
def dataReceived(self, data):
global server
server.transport.write(data)
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = ServerProtocol
# Here's the instance of the server
server = ServerProtocol
reactor.listenTCP(2593, factory)
reactor.run()
if __name__ == '__main__':
main()
Now, the point is that the instance can't be contained into the global objects, and should be put inside the two classes: how?
I've managed to solve the issue by myself and, for future references (or to help anybody else who had this problem), here's the code I used to solve it.
I think both my solution and the one kindly given by jedwards work; now I just have to study his own a little more to be sure that what I've done is correct: this is my first application using the Twisted framework and studying somebody else's solution is the way to learn something new! :)
#!/usr/bin/env python
from twisted.internet import protocol, reactor
from twisted.protocols import basic
class ServerProtocol(protocol.Protocol):
def __init__(self):
self.buffer = None
self.client = None
def connectionMade(self):
factory = protocol.ClientFactory()
factory.protocol = ClientProtocol
factory.server = self
reactor.connectTCP('gameserver16.gamesnet.it', 2593, factory)
def dataReceived(self, data):
if (self.client != None):
self.client.write(data)
else:
self.buffer = data
def write(self, data):
self.transport.write(data)
print 'Server: ' + data.encode('hex')
class ClientProtocol(protocol.Protocol):
def connectionMade(self):
self.factory.server.client = self
self.write(self.factory.server.buffer)
self.factory.server.buffer = ''
def dataReceived(self, data):
self.factory.server.write(data)
def write(self, data):
self.transport.write(data)
print 'Client: ' + data.encode('hex')
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = ServerProtocol
reactor.listenTCP(2593, factory)
reactor.run()
if __name__ == '__main__':
main()
Consider this approach
#!/usr/bin/env python
import sys
from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
from twisted.protocols import basic
from twisted.python import log
LISTEN_PORT = 2593
SERVER_PORT = 1234
class ServerProtocol(Protocol):
def connectionMade(self):
reactor.connectTCP('localhost', SERVER_PORT, MyClientFactory(self))
def dataReceived(self, data):
self.clientProtocol.transport.write(data)
class ClientProtocol(Protocol):
def connectionMade(self):
# Pass ServerProtocol a ref. to ClientProtocol
self.serverProtocol.clientProtocol = self;
def dataReceived(self, data):
self.serverProtocol.transport.write(data)
class MyServerFactory(ServerFactory):
protocol = ServerProtocol
def buildProtocol(self, addr):
# Create ServerProtocol
p = ServerFactory.buildProtocol(self, addr)
return p
class MyClientFactory(ClientFactory):
protocol = ClientProtocol
def __init__(self, serverProtocol_):
self.serverProtocol = serverProtocol_
def buildProtocol(self, addr):
# Create ClientProtocol
p = ClientFactory.buildProtocol(self,addr)
# Pass ClientProtocol a ref. to ServerProtocol
p.serverProtocol = self.serverProtocol
return p
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(LISTEN_PORT, MyServerFactory())
reactor.run()
if __name__ == '__main__':
main()
The ServerProtcol instance, passes a reference of itself to the MyClientFactory constructor, which then sets tells the ClientProtcol what ServerProtocol instance it's associated with.
Similarly, when the ClientProtocol connection is established, it uses it's reference to the ServerProtocol to tell the ServerProtocol what ClientProtocol to use.
Note: There's no error checking in this code, so you may encounter errors regarding NoneType if things go wrong (for example, if the real server isn't listening).
The important lines are:
reactor.connectTCP('localhost', SERVER_PORT, MyClientFactory(self))
#...
def __init__(self, serverProtocol_):
self.serverProtocol = serverProtocol_
Here, you pass a reference to the ServerProtocol to the MyClientFactory constructor. It stores this reference in a member variable. You do this so that that when the client factory creates a ClientProtocol, it can pass the reference on:
# Pass ClientProtocol a ref. to ServerProtocol
p.serverProtocol = self.serverProtocol
Then, once the connection is made from your script to the real server, the reverse happens. The ClientProtocol gives the ServerProtocol a reference to itself:
# Pass ServerProtocol a ref. to ClientProtocol
self.serverProtocol.clientProtocol = self;
Finally, both protocols use the stored references of each other to send data when it is received:
def dataReceived(self, data):
self.clientProtocol.transport.write(data)
#...
def dataReceived(self, data):
self.serverProtocol.transport.write(data)