work in progress: agent.talk - client connect basically working
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3289 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
f2a8a754fc
commit
e43810beb2
5 changed files with 142 additions and 31 deletions
|
@ -4,6 +4,8 @@ Agents for Job Execution and Communication Tasks
|
||||||
|
|
||||||
($Id$)
|
($Id$)
|
||||||
|
|
||||||
|
>>> from cybertools.agent.tests import tester
|
||||||
|
|
||||||
|
|
||||||
Communication Handling
|
Communication Handling
|
||||||
======================
|
======================
|
||||||
|
@ -40,9 +42,9 @@ that receive messages.
|
||||||
... def onMessage(self, interaction, data):
|
... def onMessage(self, interaction, data):
|
||||||
... print ('%s receiving: interaction=%s, data=%s' %
|
... print ('%s receiving: interaction=%s, data=%s' %
|
||||||
... (self.name, interaction, data))
|
... (self.name, interaction, data))
|
||||||
|
... tester.stop()
|
||||||
|
|
||||||
>>> serverSub = Subscriber('server')
|
>>> serverSub = Subscriber('server')
|
||||||
|
|
||||||
>>> master.servers[0].subscribe(serverSub, 'testing')
|
>>> master.servers[0].subscribe(serverSub, 'testing')
|
||||||
|
|
||||||
Set up a client
|
Set up a client
|
||||||
|
@ -61,9 +63,8 @@ work with the client but handle the client directly.
|
||||||
Run the communication dialog
|
Run the communication dialog
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
>>> from cybertools.agent.tests import tester
|
>>> tester.run()
|
||||||
>>> tester.iterate(400)
|
client receiving: interaction=None, data={u'status': u'OK'}
|
||||||
Session receiving, data={"message": "OK"}
|
|
||||||
|
|
||||||
|
|
||||||
Fin de Partie
|
Fin de Partie
|
||||||
|
|
|
@ -22,24 +22,63 @@ Handling asynchronous communication tasks - common and base classes.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from twisted.web.client import getPage
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
|
|
||||||
from cybertools.agent.talk.interfaces import ISession, IInteraction
|
from cybertools.agent.talk.interfaces import ISession, IInteraction
|
||||||
|
from cybertools.util import json
|
||||||
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
|
|
||||||
implements(ISession)
|
implements(ISession)
|
||||||
|
|
||||||
def __init__(self, manager):
|
def __init__(self, id, manager, subscriber, url):
|
||||||
|
self.id = id
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
|
self.subscriber = subscriber
|
||||||
|
self.url = url
|
||||||
self.state = 'logon'
|
self.state = 'logon'
|
||||||
self.id = None
|
|
||||||
self.queue = []
|
self.queue = []
|
||||||
self.interactions = {}
|
self.interactions = {}
|
||||||
|
|
||||||
def receive(self, data):
|
def connected(self, data):
|
||||||
print ('Session receiving, data=%s' % data)
|
data = json.loads(data)
|
||||||
|
self.state = 'open'
|
||||||
|
self.subscriber.onMessage(None, data)
|
||||||
|
self._processQueue()
|
||||||
|
# self._poll()
|
||||||
|
|
||||||
|
def received(self, data):
|
||||||
|
data = json.loads(data)
|
||||||
|
# TODO: check data
|
||||||
|
self._processQueue()
|
||||||
|
|
||||||
|
def pollReceived(self, data):
|
||||||
|
data = json.loads(data)
|
||||||
|
if data.get('action') != 'idle':
|
||||||
|
self.subscriber.onMessage(None, data)
|
||||||
|
# self._poll()
|
||||||
|
|
||||||
|
def _send(self, data, interaction):
|
||||||
|
if self.queue:
|
||||||
|
self.queue.append(data)
|
||||||
|
else:
|
||||||
|
self._sendData(data)
|
||||||
|
|
||||||
|
def _processQueue(self):
|
||||||
|
if not self.queue:
|
||||||
|
return
|
||||||
|
|
||||||
|
def _sendData(self, data):
|
||||||
|
content = dict(id=self.id, command='send', data=data)
|
||||||
|
d = getPage(self.url, postdata=json.dumps(content))
|
||||||
|
d.addCallback(s.received)
|
||||||
|
|
||||||
|
def _poll(self):
|
||||||
|
content = dict(id=self.id, command='poll')
|
||||||
|
d = getPage(self.url, postdata=json.dumps(content))
|
||||||
|
d.addCallback(s.pollReceived)
|
||||||
|
|
||||||
|
|
||||||
class Interaction(object):
|
class Interaction(object):
|
||||||
|
|
|
@ -22,7 +22,6 @@ Handling asynchronous and possibly asymmetric communication tasks via HTTP.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from twisted.internet import defer
|
|
||||||
from twisted.web.client import getPage
|
from twisted.web.client import getPage
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
from twisted.web.server import Site
|
from twisted.web.server import Site
|
||||||
|
@ -33,6 +32,7 @@ from cybertools.agent.components import servers, clients
|
||||||
from cybertools.agent.system.http import listener
|
from cybertools.agent.system.http import listener
|
||||||
from cybertools.agent.talk.base import Session, Interaction
|
from cybertools.agent.talk.base import Session, Interaction
|
||||||
from cybertools.agent.talk.interfaces import IServer, IClient
|
from cybertools.agent.talk.interfaces import IServer, IClient
|
||||||
|
from cybertools.util import json
|
||||||
|
|
||||||
|
|
||||||
# server implementation
|
# server implementation
|
||||||
|
@ -47,14 +47,16 @@ class HttpServer(object):
|
||||||
self.port = agent.config.talk.server.http.port
|
self.port = agent.config.talk.server.http.port
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
self.site = Site(RootResource())
|
self.site = Site(RootResource(self))
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
print 'Setting up HTTP handler for port %i.' % self.port
|
print 'Setting up HTTP handler for port %i.' % self.port
|
||||||
listener.listenTCP(self.port, self.site)
|
listener.listenTCP(self.port, self.site)
|
||||||
|
|
||||||
def subscribe(self, subscriber, aspect):
|
def subscribe(self, subscriber, aspect):
|
||||||
pass
|
subs = self.subscribers.setdefault(aspect, [])
|
||||||
|
if subscriber not in subs:
|
||||||
|
subs.append(subscriber)
|
||||||
|
|
||||||
def unsubscribe(self, subscriber, aspect):
|
def unsubscribe(self, subscriber, aspect):
|
||||||
pass
|
pass
|
||||||
|
@ -63,23 +65,54 @@ class HttpServer(object):
|
||||||
# respond to open poll request or put in queue
|
# respond to open poll request or put in queue
|
||||||
return defer.Deferred() # Interaction
|
return defer.Deferred() # Interaction
|
||||||
|
|
||||||
|
def _process(self, client, data):
|
||||||
|
command = data.get('command')
|
||||||
|
if not command:
|
||||||
|
return self._error('missing command')
|
||||||
|
cmethod = self.commands.get(command)
|
||||||
|
if cmethod is None:
|
||||||
|
return self._error('illegal command %r' % command)
|
||||||
|
id = data.get('id')
|
||||||
|
if not id:
|
||||||
|
return self._error('missing id')
|
||||||
|
sessionId = ':'.join((client, data['id']))
|
||||||
|
message = cmethod(self, sessionId, client, data)
|
||||||
|
if message:
|
||||||
|
return self._error(message)
|
||||||
|
return '{"status": "OK"}'
|
||||||
|
|
||||||
|
def _connect(self, sessionId, client, data):
|
||||||
|
if sessionId in self.sessions:
|
||||||
|
return 'duplicate session id %r' % sessionId
|
||||||
|
self.sessions[sessionId] = Session(sessionId, self, None, client)
|
||||||
|
# TODO: notify subscribers
|
||||||
|
|
||||||
|
def _poll(self, sessionId, client, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _send(self, sessionId, client, data):
|
||||||
|
for sub in self.subscribers.values():
|
||||||
|
sub.onMessage(data)
|
||||||
|
|
||||||
|
def _error(self, message):
|
||||||
|
return json.dumps(dict(status='error', message=message))
|
||||||
|
|
||||||
|
commands = dict(connect=_connect, poll=_poll, send=_send)
|
||||||
|
|
||||||
servers.register(HttpServer, Master, name='http')
|
servers.register(HttpServer, Master, name='http')
|
||||||
|
|
||||||
|
|
||||||
class RootResource(Resource):
|
class RootResource(Resource):
|
||||||
|
|
||||||
def getChild(self, path, request):
|
isLeaf = True
|
||||||
return CommandHandler(path)
|
|
||||||
|
|
||||||
|
def __init__(self, server):
|
||||||
class CommandHandler(Resource):
|
self.server = server
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.command = path
|
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
#print request
|
client = request.getClient()
|
||||||
return '{"message": "OK"}'
|
data = json.loads(request.content.read())
|
||||||
|
return self.server._process(client, data)
|
||||||
|
|
||||||
|
|
||||||
# client implementation
|
# client implementation
|
||||||
|
@ -92,17 +125,31 @@ class HttpClient(object):
|
||||||
def __init__(self, agent):
|
def __init__(self, agent):
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
def connect(self, subscriber, url, credentials=None):
|
def connect(self, subscriber, url, credentials=None):
|
||||||
s = Session(self)
|
id = self.generateSessionId()
|
||||||
d = getPage(url)
|
s = Session(self, id, subscriber, url)
|
||||||
d.addCallback(s.receive)
|
self.sessions[id] = s
|
||||||
|
data = dict(command='connect', id=id)
|
||||||
|
if credentials is not None:
|
||||||
|
data.update(credentials)
|
||||||
|
# s._send(data, None)
|
||||||
|
d = getPage(url, postdata=json.dumps(data))
|
||||||
|
d.addCallback(s.connected)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def disconnect(self, session):
|
def disconnect(self, session):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def send(self, session, data, interaction=None):
|
def send(self, session, data, interaction=None):
|
||||||
return defer.Deferred() # Interaction
|
if interaction is None:
|
||||||
|
interaction = Interaction(session)
|
||||||
|
session._send(data, interaction)
|
||||||
|
return interaction # Interaction
|
||||||
|
|
||||||
|
def generateSessionId(self):
|
||||||
|
self.count += 1
|
||||||
|
return '%07i' % self.count
|
||||||
|
|
||||||
clients.register(HttpClient, Master, name='http')
|
clients.register(HttpClient, Master, name='http')
|
||||||
|
|
|
@ -44,9 +44,11 @@ class IServer(Interface):
|
||||||
|
|
||||||
def send(session, data, interaction=None):
|
def send(session, data, interaction=None):
|
||||||
""" Send data to the remote client specified via the session given.
|
""" Send data to the remote client specified via the session given.
|
||||||
|
The session has to be created previously by a connect attempt
|
||||||
|
from the client.
|
||||||
|
|
||||||
If interaction is None, create a new one.
|
If interaction is None, create a new one.
|
||||||
Return a deferred providing the interaction with its current state.
|
Return the interaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,8 +75,9 @@ class IClient(Interface):
|
||||||
""" Send data to the server specified via the session given.
|
""" Send data to the server specified via the session given.
|
||||||
|
|
||||||
If interaction is None, create a new one.
|
If interaction is None, create a new one.
|
||||||
Return a deferred providing the interaction with its current state;
|
Return the interaction.
|
||||||
sending an interaction with ``finished`` set to True signifies
|
|
||||||
|
Sending an interaction with ``finished`` set to True signifies
|
||||||
the last message of an interaction.
|
the last message of an interaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -85,10 +88,14 @@ class ISubscriber(Interface):
|
||||||
""" May receive message notifications.
|
""" May receive message notifications.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def onMesssage(interaction, data):
|
def onMessage(interaction, data):
|
||||||
""" Callback method for message notifications.
|
""" Callback method for message notifications.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def onError(interaction, data):
|
||||||
|
""" Callback method for error notifications.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ISession(Interface):
|
class ISession(Interface):
|
||||||
""" Represents the connection to a server within a client or
|
""" Represents the connection to a server within a client or
|
||||||
|
@ -97,7 +104,8 @@ class ISession(Interface):
|
||||||
|
|
||||||
manager = Attribute("""The server or client object, respectively, that
|
manager = Attribute("""The server or client object, respectively, that
|
||||||
created the session.""")
|
created the session.""")
|
||||||
|
subscriber = Attribute("The subscriber that initiated the session.")
|
||||||
|
url = Attribute("The URL of the server the session connects to.")
|
||||||
state = Attribute("""A string specifying the current state of the session:
|
state = Attribute("""A string specifying the current state of the session:
|
||||||
'logon': The remote client is trying to connect/log in,
|
'logon': The remote client is trying to connect/log in,
|
||||||
data may contain credential information;
|
data may contain credential information;
|
||||||
|
|
|
@ -16,13 +16,29 @@ class Tester(object):
|
||||||
""" Used for controlled execution of reactor iteration cycles.
|
""" Used for controlled execution of reactor iteration cycles.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def iterate(self, n=10, delays={}):
|
stopped = False
|
||||||
|
|
||||||
|
def iterate(self, n=10, delay=0):
|
||||||
|
self.stopped = False
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
delay = delays.get(i, 0)
|
if self.stopped:
|
||||||
|
return
|
||||||
reactor.iterate(delay)
|
reactor.iterate(delay)
|
||||||
|
|
||||||
|
def run(self, maxduration=1.0, delay=0):
|
||||||
|
self.stopped = False
|
||||||
|
end = time.time() + maxduration
|
||||||
|
while not self.stopped:
|
||||||
|
reactor.iterate(delay)
|
||||||
|
if time.time() >= end:
|
||||||
|
return
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stopped = True
|
||||||
|
|
||||||
def stopThreads(self):
|
def stopThreads(self):
|
||||||
reactor.threadpool.stop()
|
reactor.threadpool.stop()
|
||||||
|
reactor.threadpool = None
|
||||||
|
|
||||||
tester = Tester()
|
tester = Tester()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue