I would like to create notification system. When server save data into database (Notification model only) then it should be send by Tornado websocket to client (browser)
So far I configured websocket but I dont know how to send data to client.
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
self.id = None
self.client = None
super(WebSocketHandler, self).__init__(*args, **kwargs)
def open(self, *args):
self.id = self.get_argument("Id")
self.stream.set_nodelay(True)
clients[self.id] = {"id": self.id, "object": self}
def on_message(self, message):
message = json.loads(message)
print("Client %s received a message : %s" % (self.id, message))
self.write_message("message: " + str(message['body']))
def on_close(self):
print('closed?')
if self.id in clients:
del clients[self.id]
def check_origin(self, origin):
return True
def _connect_to_redis(self):
logging.info('connect to redis...')
self._redis_client = tornadoredis.Client(host='localhost', port=6379)
self._redis_client.connect()
app = tornado.web.Application([
(r'/socket', WebSocketHandler),
])
parse_command_line()
app.listen(8888)
tornado.ioloop.IOLoop.instance().start()
I guess I need to plugin Redis into that. Can someone help me with that ?
If someone need it I did it in this way:
import tornado.ioloop
import tornado.web
import tornado.websocket
from tornado.options import parse_command_line
from tornado import gen
import logging
import tornadoredis
import json
from urllib.parse import urlparse
from django.core.management.base import NoArgsCommand
from django.conf import settings
logging = logging.getLogger('base.tornado')
# store clients in dictionary..
clients = dict()
# REDIS_URL = 'redis://localhost:6379/'
# REDIS_UPDATES_CHANNEL = 'django_bus'
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
self.client_id = None
self._redis_client = None
super(WebSocketHandler, self).__init__(*args, **kwargs)
self._connect_to_redis()
self._listen()
def open(self, *args):
self.client_id = self.get_argument("Id")
self.stream.set_nodelay(True)
clients[self.client_id] = self
def on_message(self, message):
"""
:param message (str, not-parsed JSON): data from client (web browser)
"""
print("on message")
#gen.coroutine
def _on_update(self, message):
"""
Receive Message from Redis when data become published and send it to selected client.
:param message (Redis Message): data from redis
"""
body = json.loads(message.body)
if self.client_id == body['client_id']:
self.write_message(message.body)
#tornado.gen.engine
def _listen(self):
"""
Listening chanel 'REDIS_UPDATES_CHANNEL'
"""
yield tornado.gen.Task(self._redis_client.subscribe, settings.REDIS_UPDATES_CHANNEL)
self._redis_client.listen(self._on_update)
def on_close(self):
"""
When client will disconnect (close web browser) then shut down connection for selected client
"""
if self.client_id in clients:
del clients[self.client_id]
self._redis_client.unsubscribe(settings.REDIS_UPDATES_CHANNEL)
self._redis_client.disconnect()
def check_origin(self, origin):
"""
Check if incoming connection is in supported domain
:param origin (str): Origin/Domain of connection
"""
return True
def _connect_to_redis(self):
"""
Extracts connection parameters from settings variable 'REDIS_URL' and
connects stored client to Redis server.
"""
redis_url = settings.REDIS_URL
parsed = urlparse(redis_url)
self._redis_client = tornadoredis.Client(host=parsed.hostname, port=parsed.port)
self._redis_client.connect()
app = tornado.web.Application([
(r'/socket', WebSocketHandler),
])
class Command(NoArgsCommand):
def handle_noargs(self, **kwargs):
logging.info('Started Tornado')
parse_command_line()
app.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Related
I am using tornado websocket for simple test code.
In the test code, i want to get tornado.websocket.WebSocketHandler.
For example, I used this way below.
class ConvPlayerInterface(object):
class WebsocketHandler(tornado.websocket.WebSocketHandler):
client = None
queue = ipcQueue.IpcQueue()
def open(self):
print 'new connection'
self.client = self #in my simple code, it handles only one client.
self.write_message("Connection Open")
def on_message(self, message):
self.queue.put(message)
def on_close(self):
print 'connection closed'
def __init__(self, url='/ws'):
self.application = tornado.web.Application([(url, self.WebsocketHandler),])
self.httpServer = tornado.httpserver.HTTPServer(self.application)
self.httpServer.listen(8888)
self.queue = self.WebsocketHandler.queue
self.ioLoop = threading.Thread(target = tornado.ioloop.IOLoop.instance().start)
def start(self):
self.ioLoop.start()
def get(self):
return self.queue.get()
def put(self, command):
self.WebsocketHandler.client.write_message(command)
But the point when it calls self.WebsocketHandler.client.write_message(command) in put() method, Python says client is Non type.
Any advice?
And how usually it is used to get client connection handler object in tornado?
In this part of your code
def put(self, command):
self.WebsocketHandler.client.write_message(command)
you are accessing to WebsocketHandler class, not a class member.
And the "client" attribute of WebsocketHandler is None, as expected.
WebsocketHandler instance will be created for each request tornado will accept, so there can be several websocket handlers simultaneously.
If you really want to have handle only one connection - you can do something like this:
class ConvPlayerInterface(object):
the_only_handler = None
class WebsocketHandler(tornado.websocket.WebSocketHandler):
client = None
queue = ipcQueue.IpcQueue()
def open(self):
print 'new connection'
ConvPlayerInterface.the_only_handler = self
self.write_message("Connection Open")
def on_message(self, message):
self.queue.put(message)
def on_close(self):
ConvPlayerInterface.the_only_handler = None
print 'connection closed'
def __init__(self, url='/ws'):
self.application = tornado.web.Application([(url, self.WebsocketHandler),])
self.httpServer = tornado.httpserver.HTTPServer(self.application)
self.httpServer.listen(8888)
self.queue = self.WebsocketHandler.queue
self.ioLoop = threading.Thread(target = tornado.ioloop.IOLoop.instance().start)
def start(self):
self.ioLoop.start()
def get(self):
return self.queue.get()
def put(self, command):
if self.the_only_handler is not None
self.the_only_handler.write_message(command)
I should say, that before I've started to use zmq reactor instead of poller everything worked fine.
class BaseZmqReceiver(BaseZmqNode):
__metaclass__ = ABCMeta
def __init__(self, host, port, hwm, bind, on_receive_callback):
super(BaseZmqReceiver, self).__init__(host=host, port=port, bind=bind, hwm=hwm)
self.node.on_message_callback = on_receive_callback
self.stream = ZMQStream(self.socket)
self.stream.on_recv(self.on_message_received)
ZmqLoopRunner().start()
def on_message_received(self, message):
return self.node.on_message_callback(message)
def create_node(self):
return ReceivingNode(None, None)
class ZmqLoopRunner(Thread):
def __init__(self):
super(ZmqLoopRunner, self).__init__()
self.loop = IOLoop.instance()
self.daemon = True
def run(self):
self.loop.start()
def stop(self):
self.loop.stop()
class ZmqSubscriber(BaseZmqReceiver):
def __init__(self, host, port, on_receive_callback, bind=False, hwm=1000):
super(ZmqSubscriber, self).__init__(host=host, port=port, hwm=hwm, bind=bind,
on_receive_callback=on_receive_callback)
def create_socket(self):
socket = self.context.socket(zmq.SUB)
socket.setsockopt(zmq.SUBSCRIBE, "")
return socket
Here is my zmq code.
And I'm basically just receiving multipart message in callback.
def on_message(message):
part1, part2 = message
And every one hour I've got message that consist of only one part. So I got
TypeError: need more than one value to unpack.
EDIT here is my full zmq code.
https://drive.google.com/file/d/0B7jQezPDaLZFQWxBMUdXQkxnS1k/edit?usp=sharing
I am going the book twisted network programming essentials 2nd edition by Jessica McKellar and Abe Fetting. I am stuck on the following twisted cred auth example 9-1. I have double checked my code line by line, letter by letter. The code will run and when I connect with a telnet client and enter 'user pass' it will not log me in. I just get the not authorized message. The script it self does not generate any error message or exceptions.
Thanks
import sys
from zope.interface import implements, Interface
from twisted.cred import checkers, credentials, portal
from twisted.internet import protocol, reactor
from twisted.protocols import basic
from twisted.python.log import startLogging; startLogging(sys.stdout)
class IPortocolAvatar(Interface):
def logout():
"""
Clean up per-login resource allocated to this avatar.
"""
class EchoAvatar(object):
implements(IPortocolAvatar)
def logout(self):
pass
class Echo(basic.LineReceiver):
portal = None
avatar = None
logout = None
def connectionLost(self, reason):
if self.logout:
self.logout()
self.avatar = None
self.logout = None
def lineReceived(self, line):
if not self.avatar:
# print "line [32]: %s" % (line,)
username, password = line.strip().split(" ")
self.tryLogin(username, password)
else:
self.sendLine(line)
def tryLogin(self, username, password):
self.portal.login(credentials.UsernamePassword(username, password), None, PortocolAvatar).addCallbacks(self._cbLogin, self._ebLogin)
def _cbLogin(self, (interface, avatar, logout)):
self.avatar = avatar
self.logout = logout
self.sendLine("Login sucessful, please proceed.")
def _ebLogin(self, failure):
self.sendLine("Login denied, goodbye.")
self.transport.loseConnection()
class EchoFactory(protocol.Factory):
def __init__(self, portal):
self.portal = portal
def buildProtocol(self, addr):
proto = Echo()
proto.portal = self.portal
return proto
class Realm(object):
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if IPortocolAvatar in interfaces:
avatar = EchoAvatar()
return IPortocolAvatar, avatar, avatar.logout
raise NotImplementedError(
"This realm only supports the IPortocolAvatar interface.")
realm = Realm()
myPortal = portal.Portal(realm)
checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
checker.addUser("user", "pass")
myPortal.registerChecker(checker)
reactor.listenTCP(8000, EchoFactory(myPortal))
reactor.run()
You have a typo in tryLogin (ProtocolAvatar -> IProtocolAvatar):
def tryLogin(self, username, password):
self.portal.login(credentials.UsernamePassword(username, password), None, IProtocolAvatar).addCallbacks(self._cbLogin, self._ebLogin)
# ^
UPDATE
There is another typo: IPortocolAvatar -> IProtocolAvatar.
After run the server, try following test client code:
import telnetlib
import time
t = telnetlib.Telnet('localhost', 8000)
t.write(b'user pass\r\n')
t.write(b'blah blah\r\n')
time.sleep(1)
print(t.read_eager())
I'm also made the example run with python3.
Here is my altered code for the 'Example 9-1. echo_cred.py' from the book 'Twisted Network Programming Essentials, 2nd Edition'
from zope.interface import Interface, implementer
from twisted.cred import checkers, credentials, portal, error
from twisted.internet import protocol, reactor, defer
from twisted.protocols import basic
class IProtocolAvatar(Interface):
def logout():
"""
Clean up per-login resources allocated to this avatar.
"""
#implementer(IProtocolAvatar)
class EchoAvatar():
def logout(self):
pass
class Echo(basic.LineReceiver):
portal = None
avatar = None
logout = None
def connectionLost(self, reason):
if self.logout:
self.logout()
self.avatar = None
self.logout = None
def lineReceived(self, line):
print('line received')
if not self.avatar:
username, password = line.strip().split()
# convert 2 text code
username, password = username.decode(), password.decode()
d = defer.maybeDeferred(self.tryLogin, username, password)
d.addCallbacks(self._cbLogin, self._ebLogin)
else:
self.sendLine(line)
def tryLogin(self, username, password):
if self.portal is not None:
return self.portal.login(
credentials.UsernamePassword(username, password),
None,
IProtocolAvatar
)
raise error.UnauthorizedLogin()
def _cbLogin(self, ial):
interface, avatar, logout = ial
self.avatar = avatar
self.logout = logout
# convert 2 byte code
self.sendLine("Login successful, please proceed.".encode())
def _ebLogin(self, failure):
self.sendLine("Login denied, goodbye.".encode())
self.transport.loseConnection()
class EchoFactory(protocol.Factory):
def __init__(self, portal):
self.portal = portal
def buildProtocol(self, addr):
proto = Echo()
proto.portal = self.portal
return proto
#implementer(portal.IRealm)
class Realm():
def requestAvatar(self, avatarId, mind, *interfaces):
if IProtocolAvatar in interfaces:
avatar = EchoAvatar()
return IProtocolAvatar, avatar, avatar.logout
raise NotImplementedError("This realm only supports the IProtocolAvatar interface.")
if __name__ == '__main__':
realm = Realm()
myPortal = portal.Portal(realm)
checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
checker.addUser("user", "pass")
myPortal.registerChecker(checker)
reactor.listenTCP(8000, EchoFactory(myPortal))
reactor.run()
Can you anyone please help me (noob) call the broadcast function from class BroadcastServerFactory in class process, as per attached code
I have tried so many methods of call a function from another class, but no solution
import time, sys
from apscheduler.scheduler import Scheduler
import threading
import socket
from twisted.internet import reactor
from twisted.python import log
from twisted.web.server import Site
from twisted.web.static import File
from autobahn.websocket import WebSocketServerFactory, \
WebSocketServerProtocol, \
listenWS
class process(threading.Thread):
def __init__(self, buffer3):
threading.Thread.__init__(self)
self.setDaemon(True)
self.buffer3 = buffer3
def run(self):
factory.broadcast("I don't know what I'm doing!")
class BroadcastServerProtocol(WebSocketServerProtocol):
def onOpen(self):
self.factory.register(self)
def onMessage(self, msg, binary):
if not binary:
self.factory.broadcast("'%s' from %s" % (msg, self.peerstr))
def connectionLost(self, reason):
WebSocketServerProtocol.connectionLost(self, reason)
self.factory.unregister(self)
class BroadcastServerFactory(WebSocketServerFactory):
"""
Simple broadcast server broadcasting any message it receives to all
currently connected clients.
"""
def __init__(self, url, debug = False, debugCodePaths = False):
WebSocketServerFactory.__init__(self, url, debug = debug, debugCodePaths = debugCodePaths)
self.clients = []
self.tickcount = 0
self.tick()
def tick(self):
self.tickcount += 1
self.broadcast("'tick %d' from server" % self.tickcount)
reactor.callLater(1, self.tick)
def register(self, client):
if not client in self.clients:
print "registered client " + client.peerstr
self.clients.append(client)
def unregister(self, client):
if client in self.clients:
print "unregistered client " + client.peerstr
self.clients.remove(client)
def broadcast(self, msg):
print "broadcasting message '%s' .." % msg
for c in self.clients:
c.sendMessage(msg)
print "message sent to " + c.peerstr
class BroadcastPreparedServerFactory(BroadcastServerFactory):
"""
Functionally same as above, but optimized broadcast using
prepareMessage and sendPreparedMessage.
"""
def broadcast(self, msg):
print "broadcasting prepared message '%s' .." % msg
preparedMsg = self.prepareMessage(msg)
for c in self.clients:
c.sendPreparedMessage(preparedMsg)
print "prepared message sent to " + c.peerstr
def testing():
buffer2 - "hello"
myDisplay = process(buffer2)
myDisplay.start()
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
log.startLogging(sys.stdout)
debug = True
else:
debug = False
level_scheduler = Scheduler()
level_scheduler.add_interval_job(testing, seconds=5)
level_scheduler.start()
#ServerFactory = BroadcastServerFactory
ServerFactory = BroadcastPreparedServerFactory
factory = ServerFactory("ws://localhost:9000",
debug = debug,
debugCodePaths = debug)
factory.protocol = BroadcastServerProtocol
factory.setProtocolOptions(allowHixie76 = True)
listenWS(factory)
webdir = File(".")
web = Site(webdir)
reactor.listenTCP(8080, web)
reactor.run()
Thanks
Pass the class instance of BroadcastServerFactory to be called to the class instance that calls it process on creation
class process(threading.Thread):
def __init__(self, buffer3m, broadcast_server_factory):
threading.Thread.__init__(self)
self.setDaemon(True)
self.buffer3 = buffer3
self.factory = broadcast_server_factory
def run(self):
self.factory.broadcast("I don't know what I'm doing!")
and then call it (it's assigned as self.factory in the run statement. I can't see where you create a process class in your __main__ but it will be created with something like
p = process(buffer, factory)
Aside: Using capital letters for class names is considered good form in python process -> Process
Learn Twisted. I decided to write a server and client that once a second to share data.
Wrote one implementation, but it seems to me that it is not correct.
# -*- coding: utf-8 -*-
from twisted.spread import pb
from twisted.internet import reactor, task
from twisted.cred import credentials
from win32com.server import factory
class login_send:
def __init__(self):
self.count=0
self.timeout = 1.0
self.factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 8800, self.factory)
def testTimeout(self):
self.count+=1
print self.count
def1 = self.factory.login(credentials.UsernamePassword("test1","bb1b"))
def1.addCallbacks(self.good_connected, self.bad_connected)
def1.addCallback(self.send_data)
def1.addErrback(self.disconnect)
if self.count>10:def1.addBoth(self.disconnect)
def start(self):
l = task.LoopingCall(self.testTimeout)
l.start(self.timeout)
reactor.run()
def good_connected(self, perspective):
print 'good login and password', perspective
return perspective
def bad_connected(self, perspective):
print 'bad login or password', perspective
return perspective
def send_data(self, perspective):
print 'send'
return perspective.callRemote("foo", self.count)
def disconnect(self, perspective):
print 'disconnect'
reactor.stop()
if __name__ == "__main__":
st=login_send()
st.start()
Code: if login and password True -> send self.count, if login or password False -> disconnect, if self.count>10 -> disconnect
The first mistake, in my opinion is that I have to login every time.
def1 = self.factory.login(credentials.UsernamePassword("test1", "bb1b"))
How to make one authorization, and continue to send data every second?
simple test server code:
from zope.interface import implements
from twisted.spread import pb
from twisted.cred import checkers, portal
from twisted.internet import reactor
class MyPerspective(pb.Avatar):
def __init__(self, name):
self.name = name
def perspective_foo(self, arg):
print "I am", self.name, "perspective_foo(",arg,") called on", self
return arg
class MyRealm:
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if pb.IPerspective not in interfaces:
print 'qqqq'
raise NotImplementedError
return pb.IPerspective, MyPerspective(avatarId), lambda:None
p = portal.Portal(MyRealm())
c = checkers.InMemoryUsernamePasswordDatabaseDontUse(test1="bbb",
user2="pass2")
p.registerChecker(c)
reactor.listenTCP(8800, pb.PBServerFactory(p))
reactor.run()
I believe this should do the trick.
# Upper case first letter of class name is good policy.
class Login_send:
def __init__(self):
# initialize the state variable to False.
self.connection = False
self.count=0
self.timeout = 1.0
self.factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 8800, self.factory)
def testTimeout(self):
self.count+=1
print self.count
# no connection -- create one.
if not self.connection:
self.assign_connection()
# cached connection exists, call send_data manually.
elif self.count > 10:
self.disconnect(self.connection)
else:
#you probably want to send data only if it it should be valid.
self.send_data(self.connection)
def assign_connection(self):
''' Creates and stores a Deffered which represents the connection to
the server. '''
# cache the connection.
self.connection = self.factory.login(
credentials.UsernamePassword("test1","bb1b"))
# add connection callbacks as normal.
self.connection.addCallbacks(
self.good_connected, self.bad_connected)
self.connection.addCallback(self.send_data)
self.connection.addErrback(self.disconnect)
def disconnect(self, perspective):
# be sure to cleanup after yourself!
self.connection = False
print 'disconnect'
reactor.stop()
# the rest of your class goes here.