How to send message when file changes detected? Twisted and Web Sockets - python

I am currently trying to create a small demo where I connect a web socket between my computer and my localhost ws://localhost:8080/ws. I want the web socket to monitor a file on my computer for changes. If there are changes, send a message. The connection and output is being monitored using Advanced Rest Client.
Is there a specific method I can use on the class to constantly check the contents of this file?
EDIT
I have implemented an observer using watchdog that detects any event for files in a specified directory. However, my message is not being sent in the sendFSEvent method and I also realized that my client isn't being registered when I connect to the web socket.
Here is my code in server.py
import sys
import os
from watchdog.observers import Observer
from twisted.web.static import File
from twisted.python import log
from twisted.web.server import Site
from twisted.internet import reactor, defer
from autobahn.twisted.websocket import WebSocketServerFactory, \
WebSocketServerProtocol, listenWS
from MessangerEventHandler import MessangerEventHandler
class WsProtocol(WebSocketServerProtocol):
def connectionMade(self):
print("Connection made")
WebSocketServerProtocol.connectionMade(self)
def onOpen(self):
WebSocketServerProtocol.onOpen(self)
print("WebSocket connection open")
def onMessage(self, payload, isBinary):
print("Message was: {}".format(payload))
self.sendMessage("message received")
def sendFSEvent(self, json):
WebSocketProtocol.sendMessage(self, json)
print('Sent FS event')
def onClose(self, wasClean, code, reason):
print("Connection closed: {}".format(reason))
WebSocketServerProtocol.onClose(self, wasClean, code, reason)
class WsServerFactory(WebSocketServerFactory):
protocol = WsProtocol
def __init__(self, url='ws://localhost', port=8080):
addr = url + ':' + str(port)
print("Listening on: {}".format(addr))
WebSocketServerFactory.__init__(self, addr)
self.clients = []
def register(self, client):
if not client in self.clients:
print("Registered client: {}".format(client))
self.clients.append(client)
def unregister(self, client):
if client in self.clients:
print("Unregistered client: {}".format(client))
self.clients.remove(client)
self._printConnected()
def _printConnected(self):
print("Connected clients:[")
def notify_clients(self, message):
print("Broadcasting: {}".format(message))
for c in self.clients:
c.sendFSEvent(message)
print("\nSent messages")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python server_defer.py <dirs>")
sys.exit(1)
log.startLogging(sys.stdout)
ffactory = WsServerFactory("ws://localhost", 8080)
ffactory.protocol = WsProtocol
listenWS(ffactory)
observers = []
for arg in sys.argv[1:]:
dir_path = os.path.abspath(arg)
if not os.path.exists(dir_path):
print('{} does not exist.'.format(dir_path))
sys.exit(1)
if not os.path.isdir(dir_path):
print('{} is not a directory.'.format(dir_path))
sys.exit(1)
# Check for and handle events
event_handler = MessangerEventHandler(ffactory, reactor, os.getcwd())
observer = Observer()
observer.schedule(event_handler, path=dir_path, recursive=True)
observer.start()
observers.append(observer)
try:
reactor.run()
except KeyboardInterrupt:
for obs in observers:
obs.stop()
reactor.stop()
print("\nGoodbye")
sys.exit(1)
Any help would be greatly appreciated.
Thank you,
Brian

Most enterprise distros come with inotify which is really well suited for monitoring files and directories. The basic idea is to capture a list of connected web socket clients as they connect. Then create a callback that will execute when a change occurs on the files you're monitoring. Within this callback, you can iterate the clients and send them a message like 'file: "blah/blah.txt" has changed'. It's a little wonky, but the code snippet should clear things up for you.
from functools import partial
from twisted.internet import inotify
from twisted.python import filepath
# the rest of your imports ...
class SomeServerProtocol(WebSocketServerProtocol):
def onConnect(self, request):
self.factory.append(self) # <== append this client to the list in the factory
def notification_callback(ignored, filepath, mask, ws_clients):
"""
function that will execute when files are modified
"""
payload = "event on {0}".format(filepath)
for client in ws_clients:
client.sendMessage(
payload.encode('utf8'), # <== don't forget to encode the str to bytes before sending!
isBinary = False)
if __name__ == '__main__':
root = File(".")
factory = WebSocketServerFactory(u"ws://127.0.01:8080")
factory.protocol = SomeServerProtocol
factory.clients = [] # <== create a container for the clients that connect
# inotify stuff
notify = partial(notification_callback, ws_clients=factory.clients) # <== use functools.partial to pass extra params
notifier = inotify.INotify()
notifier.startReading()
notifier.watch(filepath.FilePath("/some/directory"), callbacks=[notify])
# the rest of your code ...

Related

set time for check connection tcp client PYTHON:TWISTED

Im creating an application to receive data from multiple iot devices
Each iot device has a socket server
so i need to create multi socket client
Im using Twisted library for create multiple socket client
Problem is that my application can't detect connection failure and it wont try for reconnection
Herer is my code:
import time
import requests
from twisted.internet.protocol import Protocol, ReconnectingClientFactory as rcf, connectionDone
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.python import failure
client_list = [("device ip", device_port)]
endpoints = {}
class Client(Protocol):
"""
when receive a massage call this function
"""
def connectionMade(self):
self.transport.setTcpKeepAlive(True)
def dataReceived(self, data):
reactor.callInThread(self._dataReceived, data)
def _dataReceived(self, data):
if self.connected == 1:
data = data[:-1].decode("utf-8")
data = data.split('\n')
for tag in data:
reactor.callInThread(self._push_data, tag, self.transport.addr[0])
def _push_data(self, data, ip):
print("start pushing")
print(data[1:-1])
try:
pass
// send a web request
except Exception as xp:
print(xp, end="\n")
class ClientFactory(rcf):
def buildProtocol(self, addr):
print(addr, 'Connected.')
return Client()
if __name__ == "__main__":
for client in client_list:
endpoint = TCP4ClientEndpoint(reactor, client[0], client[1])
endpoints[client] = endpoint
endpoint.connect(ClientFactory())
reactor.run()
i writing some code for fix this problem but that's not worked
import time
import requests
from twisted.internet.protocol import Protocol, ReconnectingClientFactory as clf, connectionDone
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.python import failure
client_list = [("device ip", device_port)]
endpoints = {}
class Client(Protocol):
"""
when receive a massage call this function
"""
def dataReceived(self, data):
reactor.callInThread(self._dataReceived, data)
def _dataReceived(self, data):
if self.connected == 1:
data = data[:-1].decode("utf-8")
data = data.split('\n')
for tag in data:
reactor.callInThread(self._push_data, tag, self.transport.addr[0])
def _push_data(self, data, ip):
print("start pushing")
print(data[1:-1])
try:
pass
// send a web request
except Exception as xp:
print(xp, end="\n")
def connectionLost(self, reason: failure.Failure = connectionDone):
try:
endpoints.pop(self.transport.addr[0])
except Exception as xp:
print(xp)
print(f"Protocol client {self.transport.addr[0]} connectionLost \n ", reason)
status = True
while status:
try:
endpoint = TCP4ClientEndpoint(reactor, self.transport.addr[0], 100)
endpoints[client] = endpoint
endpoint.connect(ClientFactory())
status = False
except Exception as xp:
print(xp)
class ClientFactory(clf):
def buildProtocol(self, addr):
print(addr, 'Connected.')
return Client()
def clientConnectionFailed(self, connector, reason):
print("can't start connect to the server")
print(reason)
clf.clientConnectionFailed(self, connector, reason)
def clientConnectionLost(self, connector, reason):
print(reason)
clf.clientConnectionLost(self, connector, reason)
if __name__ == "__main__":
for client in client_list:
endpoint = TCP4ClientEndpoint(reactor, client[0], client[1])
endpoints[client] = endpoint
endpoint.connect(ClientFactory())
reactor.run()
my question is how i can set an interval ping for check connection

Tornado web socket communication on demand?

I'm setting up a tornado web socket and want to use it to send log data over my network. Seems to work fine, but:
(1) Can I send data from client to server arbitrarily once the connection is established, or do I have to establish a new connection and use the on_open method to print/work with the sent message every time?
(2) In my client specifically (link): why isn't ws.close() called? Doesn't seem to happen. How can I terminate a connection then?
(3) Is there a better way to identify clients other that connection.request.remote_ip?
Code: Server
import tornado.ioloop
import tornado.web
import tornado.websocket
host = "localhost:2000"
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("<h3>HELLO</h3>")
class EchoWebSocket(tornado.websocket.WebSocketHandler):
connections = set()
ips = set()
def open(self):
self.connections.add(self)
self.ips.add(self.request.remote_ip)
print("[MASTER]: WebSocket opened by {}".format(self.request.remote_ip))
def on_message(self, message):
print("[CLIENT {}]: {}".format(self.request.remote_ip, message))
[con.write_message("[MASTER]: {}".format(message)) for con in self.connections]
def on_close(self):
self.connections.remove(self)
self.ips.remove(self.request.remote_ip)
print("[MASTER]: WebSocket closed - Client {}".format(self.request.remote_ip))
def setup():
return tornado.web.Application([(r"/", MainHandler),
(r"/ws", EchoWebSocket)])
if __name__ == "__main__":
s = tornado.httpserver.HTTPServer(setup())
s.listen(2000)
print("------------- INFO -------------\nStarted Server at {}\nSocket available at /ws".format(host))
tornado.ioloop.IOLoop.current().start()
Client:
from tornado.websocket import websocket_connect
import tornado
import time
url = "ws://localhost:2000/ws"
class Client(object):
def __init__(self, url, log=None):
self.url = url
self.ioloop = tornado.ioloop.IOLoop.current()
self.conn = None
self.log = log
def start(self):
websocket_connect(
self.url,
self.ioloop,
callback=self.on_connected,
on_message_callback=self.on_message)
self.ioloop.start()
def on_connected(self, f):
try:
self.conn = f.result()
self.conn.write_message("Client #1 connected")
except Exception as e:
print("[ERROR]: {}".format(e))
self.conn.write_message("ERROR: {}".format(e))
self.ioloop.stop()
def on_message(self, message):
if message is None:
print("[ERROR]: No message received")
self.conn.write_message("[ERROR]: No message received")
self.ioloop.stop()
else:
print(message)
def close():
self.conn.close()
if __name__ == '__main__':
ws = Client(url)
ws.start()
ws.close()

python-twisted: writing a broadcaster

im learning python twisted and i refer to this example:
#!/usr/bin/env python
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An example demonstrating how to send and receive UDP broadcast messages.
Every second, this application will send out a PING message with a unique ID.
It will respond to all PING messages with a PONG (including ones sent by
itself). You can tell how many copies of this script are running on the local
network by the number of "RECV PONG".
Run using twistd:
$ twistd -ny udpbroadcast.py
"""
from uuid import uuid4
from twisted.application import internet, service
from twisted.internet.protocol import DatagramProtocol
from twisted.python import log
class PingPongProtocol(DatagramProtocol):
noisy = False
def __init__(self, controller, port):
self.port = port
def startProtocol(self):
self.transport.setBroadcastAllowed(True)
def sendPing(self):
pingMsg = "PING {0}".format(uuid4().hex)
self.transport.write(pingMsg, ('<broadcast>', self.port))
log.msg("SEND " + pingMsg)
def datagramReceived(self, datagram, addr):
if datagram[:4] == "PING":
uuid = datagram[5:]
pongMsg = "PONG {0}".format(uuid)
self.transport.write(pongMsg, ('<broadcast>', self.port))
log.msg("RECV " + datagram)
elif datagram[:4] == "PONG":
log.msg("RECV " + datagram)
class Broadcaster(object):
def ping(self, proto):
proto.sendPing()
def makeService(self):
application = service.Application('Broadcaster')
root = service.MultiService()
root.setServiceParent(application)
proto = PingPongProtocol(controller=self, port=8555)
root.addService(internet.UDPServer(8555, proto))
root.addService(internet.TimerService(1, self.ping, proto))
return application
application = Broadcaster().makeService()
i noticed that for this broadcaster example, we need to run as twistd -ny udpbroadcast.py
how do i modify the code if i were to run as a package, ie: python2.7 -m org.somepackagename. udpbroadcast? i know i need to use the reactor, but how should i do it? i see that this example is using timeservice and UDPServer. i suspect i have to listen to reactor.listenUDP(0, protocol)??
i actually commented application = Broadcaster().makeService(), change def __init__(self, controller, port): to def __init__(self, port): and added the following code below
from twisted.internet.task import LoopingCall
protocol = PingPongProtocol(port=8009)
lc = LoopingCall(protocol)
reactor.listenUDP(0, protocol)
reactor.run()
but it's still not sending and recv data
=========================================================
EDITED CODE
#!/usr/bin/env python
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An example demonstrating how to send and receive UDP broadcast messages.
Every second, this application will send out a PING message with a unique ID.
It will respond to all PING messages with a PONG (including ones sent by
itself). You can tell how many copies of this script are running on the local
network by the number of "RECV PONG".
Run using twistd:
$ twistd -ny udpbroadcast.py
"""
from uuid import uuid4
from twisted.application import internet, service
from twisted.internet.protocol import DatagramProtocol
from twisted.python import log
class PingPongProtocol(DatagramProtocol):
noisy = False
# def __init__(self, controller, port):
def __init__(self, port): # removed controller
self.port = port
def startProtocol(self):
self.transport.setBroadcastAllowed(True)
def sendPing(self):
pingMsg = "PING {0}".format(uuid4().hex)
self.transport.write(pingMsg, ('<broadcast>', self.port))
log.msg("SEND " + pingMsg)
def datagramReceived(self, datagram, addr):
print "RECV:" + datagram
if datagram[:4] == "PING":
uuid = datagram[5:]
pongMsg = "PONG {0}".format(uuid)
self.transport.write(pongMsg, ('<broadcast>', self.port))
log.msg("RECV " + datagram)
elif datagram[:4] == "PONG":
log.msg("RECV " + datagram)
class Broadcaster(object):
def ping(self, proto):
proto.sendPing()
def makeService(self):
application = service.Application('Broadcaster')
root = service.MultiService()
root.setServiceParent(application)
proto = PingPongProtocol(port=8009) #removed controller
root.addService(internet.UDPServer(8009, proto))
root.addService(internet.TimerService(1, self.ping, proto))
return application
# application = Broadcaster().makeService() #commented. to use the code below
protocol = PingPongProtocol(port=8009)
from twisted.internet.task import LoopingCall
lc = LoopingCall(protocol)
# lc.start(3) #commented cos its throwing error
from twisted.internet import reactor
reactor.listenUDP(8009, protocol)
reactor.run()

How to detect the server closing a unix domain socket?

I'm messing around with the python twisted library, and I can't seem to figure out how to get my client to detect a server closing its socket. My client continues to let me send data to the non existent server. Here is my server:
test_server.py
from twisted.internet import protocol, reactor, endpoints, stdio
from twisted.protocols.basic import LineReceiver
class ConnectionProtocol(LineReceiver):
from os import linesep as delimiter
def lineReceived(self, line):
print 'got line: %s' % line
self.sendLine(line)
class ConnectionFactory(protocol.Factory):
def buildProtocol(self, addr):
return ConnectionProtocol()
def main():
endpoint = endpoints.UNIXServerEndpoint(reactor, './test.sock', 5, 0777, False)
endpoint.listen(ConnectionFactory())
print 'starting the reactor'
reactor.run()
main()
All it does is send each line it gets back to the connecting client.
Here is the client:
test_client.py
import os
from twisted.internet import protocol, reactor, endpoints, stdio
from twisted.protocols.basic import LineReceiver
class CommandProtocol(protocol.Protocol):
def dataReceived(self, data):
print data,
class StdinProtocol(LineReceiver):
from os import linesep as delimiter
def __init__(self, client):
self._client = client
def connectionMade(self):
self.transport.write('>>> ')
def lineReceived(self, line):
print 'writing line: %s' % line
self._client.transport.write(line + os.linesep)
def printError(failure):
print str(failure)
def main():
point = endpoints.UNIXClientEndpoint(reactor, './test.sock')
proto = CommandProtocol()
d = endpoints.connectProtocol(point, proto)
d.addErrback(printError)
stdio.StandardIO(StdinProtocol(proto))
reactor.run()
main()
If I run the server and then the client, and then kill the server, the client still writes to the CommandProtocol's transport like nothing happened. I thought the errback function would at least report something. In the case where the client is run before the server, the errback function is called with a ConnectError, but I'm specifically looking to detect the situation where the client has already connected to a server, and the server exits.
How can I detect that the server has shutdown?
ITransport.write is a silent no-op if called on a transport that has been disconnected.
If you want to learn that a connection was lost, override the connectionLost method. Once you know that the connection has been lost you can arrange for your program to stop accepting input or do something else with the input it receives.
i guess os.stat method and stat module can help you. Can you change your client codes to like below:
import os
import stat
from twisted.internet import protocol, reactor, endpoints, stdio
from twisted.protocols.basic import LineReceiver
class CommandProtocol(protocol.Protocol):
def dataReceived(self, data):
print data,
class StdinProtocol(LineReceiver):
from os import linesep as delimiter
def __init__(self, client):
self._client = client
def connectionMade(self):
self.transport.write('>>> ')
def lineReceived(self, line):
print 'writing line: %s' % line
self._client.transport.write(line + os.linesep)
def printError(failure):
print str(failure)
def main():
if os.stat('./test.sock').st_mode & (stat.S_IRGRP | stat.S_IRUSR | stat.S_IROTH):
print "1"
print "using control socket"
point = endpoints.UNIXClientEndpoint(reactor, './test.sock')
proto = CommandProtocol()
d = endpoints.connectProtocol(point, proto)
d.addErrback(printError)
stdio.StandardIO(StdinProtocol(proto))
reactor.run()
else:
print "not ready"
main()

Problem with Twisted python - sending binary data

What I'm trying to do is fairly simple: send a file from client to server. First, the client sends information about the file - the size of it that is. Then it sends the actual file.
This is what I've done so far:
Server.py
from twisted.internet import reactor, protocol
from twisted.protocols.basic import LineReceiver
import pickle
import sys
class Echo(LineReceiver):
def connectionMade(self):
self.factory.clients.append(self)
self.setRawMode()
def connectionLost(self, reason):
self.factory.clients.remove(self)
def lineReceived(self, data):
print "line", data
def rawDataReceived(self, data):
try:
obj = pickle.loads(data)
print obj
except:
print data
#self.transport.write("wa2")
def main():
"""This runs the protocol on port 8000"""
factory = protocol.ServerFactory()
factory.protocol = Echo
factory.clients = []
reactor.listenTCP(8000,factory)
reactor.run()
# this only runs if the module was *not* imported
if __name__ == '__main__':
main()
Client.py
import pickle
from twisted.internet import reactor, protocol
import time
import os.path
from twisted.protocols.basic import LineReceiver
class EchoClient(LineReceiver):
def connectionMade(self):
file = "some file that is a couple of megs"
filesize = os.path.getsize(file)
self.sendLine(pickle.dumps({"size":filesize}))
f = open(file, "rb")
contents = f.read()
print contents[:20]
self.sendLine(contents[:20])
f.close()
# self.sendLine("hej")
# self.sendLine("wa")
def connectionLost(self, reason):
print "connection lost"
class EchoFactory(protocol.ClientFactory):
protocol = EchoClient
def clientConnectionFailed(self, connector, reason):
print "Connection failed - goodbye!"
reactor.stop()
def clientConnectionLost(self, connector, reason):
print "Connection lost - goodbye!"
reactor.stop()
# this connects the protocol to a server runing on port 8000
def main():
f = EchoFactory()
reactor.connectTCP("localhost", 8000, f)
reactor.run()
# this only runs if the module was *not* imported
if __name__ == '__main__':
main()
The server will only output the deserialized object:
{'size': 183574528L}
How come? What happend to the 20 chars from the file I wanted to send?
If use the "hej" and "wa" sends instead, I will get them both (in the same message, not twice).
Somebody?
You've set your server to raw mode with setRawMode(), so the callback rawDataReceived is being called with the incoming data (not lineReceived). If you print the data you receive in rawDataReceived, you see everything including the file content, but as you call pickle to deserialize the data, it's being ignored.
Either you change the way you send data to the server (I would suggest the netstring format) or you pass the content inside the pickle serialized object, and do this in one call.
self.sendLine(pickle.dumps({"size":filesize, 'content': contents[:20]}))

Categories