[54] | 1 | # HG changeset patch |
---|
| 2 | # Parent 52b9931fa5f5c6aeee617a4dad3e09d041ae4361 |
---|
| 3 | Add factory for accepting client connections. |
---|
| 4 | |
---|
| 5 | The new XMPPC2SServerFactory is a server factory for accepting client |
---|
| 6 | connections. It uses `XMPPClientListenAuthenticator` to perform the |
---|
| 7 | steps for authentication and binding of a resource, and keeps a list |
---|
| 8 | of all streams. |
---|
| 9 | |
---|
| 10 | Upon loss of the connection, the service is called with `unbindResource`. |
---|
| 11 | Received stanzas cause the service's `onElement` to be called. |
---|
| 12 | |
---|
| 13 | The factory has a `deliverStanza` method to deliver stanzas to a particular |
---|
| 14 | recipient. This is used for stanzas that have different recipient addressing |
---|
| 15 | than the actual recipient (for presence and messages from a different (or no) |
---|
| 16 | resource). |
---|
| 17 | |
---|
| 18 | TODO: |
---|
| 19 | |
---|
| 20 | * Add docstrings. |
---|
| 21 | * Add tests. |
---|
| 22 | |
---|
| 23 | diff -r 52b9931fa5f5 wokkel/client.py |
---|
| 24 | --- a/wokkel/client.py Wed Oct 05 09:50:55 2011 +0200 |
---|
| 25 | +++ b/wokkel/client.py Wed Oct 05 09:58:11 2011 +0200 |
---|
| 26 | @@ -16,9 +16,11 @@ |
---|
| 27 | from twisted.internet import reactor |
---|
| 28 | from twisted.names.srvconnect import SRVConnector |
---|
| 29 | from twisted.words.protocols.jabber import client, error, sasl, xmlstream |
---|
| 30 | +from twisted.words.xish import domish, utility |
---|
| 31 | |
---|
| 32 | from wokkel import generic |
---|
| 33 | from wokkel.subprotocols import StreamManager |
---|
| 34 | +from wokkel.compat import XmlStreamServerFactory |
---|
| 35 | |
---|
| 36 | NS_CLIENT = 'jabber:client' |
---|
| 37 | |
---|
| 38 | @@ -345,3 +347,98 @@ |
---|
| 39 | |
---|
| 40 | |
---|
| 41 | |
---|
| 42 | +class RecipientUnavailable(Exception): |
---|
| 43 | + """ |
---|
| 44 | + The addressed entity is not, or no longer, available. |
---|
| 45 | + """ |
---|
| 46 | + |
---|
| 47 | + |
---|
| 48 | + |
---|
| 49 | +class XMPPC2SServerFactory(XmlStreamServerFactory): |
---|
| 50 | + |
---|
| 51 | + def __init__(self, service): |
---|
| 52 | + self.service = service |
---|
| 53 | + |
---|
| 54 | + def authenticatorFactory(): |
---|
| 55 | + return XMPPClientListenAuthenticator(service) |
---|
| 56 | + |
---|
| 57 | + XmlStreamServerFactory.__init__(self, authenticatorFactory) |
---|
| 58 | + self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, |
---|
| 59 | + self.onConnectionMade) |
---|
| 60 | + self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, |
---|
| 61 | + self.onAuthenticated) |
---|
| 62 | + |
---|
| 63 | + self.serial = 0 |
---|
| 64 | + self.streams = {} |
---|
| 65 | + |
---|
| 66 | + |
---|
| 67 | + def onConnectionMade(self, xs): |
---|
| 68 | + """ |
---|
| 69 | + Called when a client-to-server connection was made. |
---|
| 70 | + |
---|
| 71 | + This enables traffic debugging on incoming streams. |
---|
| 72 | + """ |
---|
| 73 | + xs.serial = self.serial |
---|
| 74 | + self.serial += 1 |
---|
| 75 | + |
---|
| 76 | + log.msg("Client connection %d made" % xs.serial) |
---|
| 77 | + |
---|
| 78 | + def logDataIn(buf): |
---|
| 79 | + log.msg("RECV (%d): %r" % (xs.serial, buf)) |
---|
| 80 | + |
---|
| 81 | + def logDataOut(buf): |
---|
| 82 | + log.msg("SEND (%d): %r" % (xs.serial, buf)) |
---|
| 83 | + |
---|
| 84 | + if self.logTraffic: |
---|
| 85 | + xs.rawDataInFn = logDataIn |
---|
| 86 | + xs.rawDataOutFn = logDataOut |
---|
| 87 | + |
---|
| 88 | + xs.addObserver(xmlstream.STREAM_ERROR_EVENT, self.onError) |
---|
| 89 | + |
---|
| 90 | + |
---|
| 91 | + def onAuthenticated(self, xs): |
---|
| 92 | + log.msg("Client connection %d authenticated" % xs.serial) |
---|
| 93 | + |
---|
| 94 | + xs.addObserver(xmlstream.STREAM_END_EVENT, self.onConnectionLost, |
---|
| 95 | + 0, xs) |
---|
| 96 | + xs.addObserver('/*', self.onElement, 0, xs) |
---|
| 97 | + |
---|
| 98 | + # Record this stream as bound to the authenticated JID |
---|
| 99 | + self.streams[xs.otherEntity] = xs |
---|
| 100 | + |
---|
| 101 | + |
---|
| 102 | + def onConnectionLost(self, xs, reason): |
---|
| 103 | + log.msg("Client connection %d disconnected" % xs.serial) |
---|
| 104 | + |
---|
| 105 | + entity = xs.otherEntity |
---|
| 106 | + self.service.unbindResource(entity.user, |
---|
| 107 | + entity.host, |
---|
| 108 | + entity.resource, |
---|
| 109 | + reason) |
---|
| 110 | + |
---|
| 111 | + # If the lost connections had been bound, remove the reference |
---|
| 112 | + if xs.otherEntity in self.streams: |
---|
| 113 | + del self.streams[xs.otherEntity] |
---|
| 114 | + |
---|
| 115 | + |
---|
| 116 | + def onError(self, reason): |
---|
| 117 | + log.err(reason, "Stream Error") |
---|
| 118 | + |
---|
| 119 | + |
---|
| 120 | + def onElement(self, xs, stanza): |
---|
| 121 | + """ |
---|
| 122 | + Called when an element was received from one of the connected streams. |
---|
| 123 | + |
---|
| 124 | + """ |
---|
| 125 | + if stanza.handled: |
---|
| 126 | + return |
---|
| 127 | + else: |
---|
| 128 | + self.service.onElement(stanza, xs.otherEntity) |
---|
| 129 | + |
---|
| 130 | + |
---|
| 131 | + def deliverStanza(self, element, recipient): |
---|
| 132 | + if recipient in self.streams: |
---|
| 133 | + self.streams[recipient].send(element) |
---|
| 134 | + else: |
---|
| 135 | + raise RecipientUnavailable(u"There is no connection for %s" % |
---|
| 136 | + recipient.full()) |
---|