| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | """ |
|---|
| 7 | Reactor-based Services |
|---|
| 8 | |
|---|
| 9 | Here are services to run clients, servers and periodic services using |
|---|
| 10 | the reactor. |
|---|
| 11 | |
|---|
| 12 | This module (dynamically) defines various Service subclasses that let |
|---|
| 13 | you represent clients and servers in a Service hierarchy. |
|---|
| 14 | |
|---|
| 15 | They are as follows:: |
|---|
| 16 | |
|---|
| 17 | TCPServer, TCPClient, |
|---|
| 18 | UNIXServer, UNIXClient, |
|---|
| 19 | SSLServer, SSLClient, |
|---|
| 20 | UDPServer, UDPClient, |
|---|
| 21 | UNIXDatagramServer, UNIXDatagramClient, |
|---|
| 22 | MulticastServer |
|---|
| 23 | |
|---|
| 24 | These classes take arbitrary arguments in their constructors and pass |
|---|
| 25 | them straight on to their respective reactor.listenXXX or |
|---|
| 26 | reactor.connectXXX calls. |
|---|
| 27 | |
|---|
| 28 | For example, the following service starts a web server on port 8080: |
|---|
| 29 | C{TCPServer(8080, server.Site(r))}. See the documentation for the |
|---|
| 30 | reactor.listen/connect* methods for more information. |
|---|
| 31 | |
|---|
| 32 | Maintainer: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>} |
|---|
| 33 | """ |
|---|
| 34 | |
|---|
| 35 | from twisted.python import log |
|---|
| 36 | from twisted.application import service |
|---|
| 37 | from twisted.internet import task |
|---|
| 38 | |
|---|
| 39 | |
|---|
| 40 | class _VolatileDataService(service.Service): |
|---|
| 41 | |
|---|
| 42 | volatile = [] |
|---|
| 43 | |
|---|
| 44 | def __getstate__(self): |
|---|
| 45 | d = service.Service.__getstate__(self) |
|---|
| 46 | for attr in self.volatile: |
|---|
| 47 | if attr in d: |
|---|
| 48 | del d[attr] |
|---|
| 49 | return d |
|---|
| 50 | |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | class _AbstractServer(_VolatileDataService): |
|---|
| 54 | """ |
|---|
| 55 | @cvar volatile: list of attribute to remove from pickling. |
|---|
| 56 | @type volatile: C{list} |
|---|
| 57 | |
|---|
| 58 | @ivar method: the type of method to call on the reactor, one of B{TCP}, |
|---|
| 59 | B{UDP}, B{SSL} or B{UNIX}. |
|---|
| 60 | @type method: C{str} |
|---|
| 61 | |
|---|
| 62 | @ivar reactor: the current running reactor. |
|---|
| 63 | @type reactor: a provider of C{IReactorTCP}, C{IReactorUDP}, |
|---|
| 64 | C{IReactorSSL} or C{IReactorUnix}. |
|---|
| 65 | |
|---|
| 66 | @ivar _port: instance of port set when the service is started. |
|---|
| 67 | @type _port: a provider of C{IListeningPort}. |
|---|
| 68 | """ |
|---|
| 69 | |
|---|
| 70 | volatile = ['_port'] |
|---|
| 71 | method = None |
|---|
| 72 | reactor = None |
|---|
| 73 | |
|---|
| 74 | _port = None |
|---|
| 75 | |
|---|
| 76 | def __init__(self, *args, **kwargs): |
|---|
| 77 | self.args = args |
|---|
| 78 | if 'reactor' in kwargs: |
|---|
| 79 | self.reactor = kwargs.pop("reactor") |
|---|
| 80 | self.kwargs = kwargs |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | def privilegedStartService(self): |
|---|
| 84 | service.Service.privilegedStartService(self) |
|---|
| 85 | self._port = self._getPort() |
|---|
| 86 | |
|---|
| 87 | |
|---|
| 88 | def startService(self): |
|---|
| 89 | service.Service.startService(self) |
|---|
| 90 | if self._port is None: |
|---|
| 91 | self._port = self._getPort() |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | def stopService(self): |
|---|
| 95 | service.Service.stopService(self) |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | if self._port is not None: |
|---|
| 99 | d = self._port.stopListening() |
|---|
| 100 | del self._port |
|---|
| 101 | return d |
|---|
| 102 | |
|---|
| 103 | |
|---|
| 104 | def _getPort(self): |
|---|
| 105 | """ |
|---|
| 106 | Wrapper around the appropriate listen method of the reactor. |
|---|
| 107 | |
|---|
| 108 | @return: the port object returned by the listen method. |
|---|
| 109 | @rtype: an object providing L{IListeningPort}. |
|---|
| 110 | """ |
|---|
| 111 | if self.reactor is None: |
|---|
| 112 | from twisted.internet import reactor |
|---|
| 113 | else: |
|---|
| 114 | reactor = self.reactor |
|---|
| 115 | return getattr(reactor, 'listen%s' % (self.method,))( |
|---|
| 116 | *self.args, **self.kwargs) |
|---|
| 117 | |
|---|
| 118 | |
|---|
| 119 | |
|---|
| 120 | class _AbstractClient(_VolatileDataService): |
|---|
| 121 | """ |
|---|
| 122 | @cvar volatile: list of attribute to remove from pickling. |
|---|
| 123 | @type volatile: C{list} |
|---|
| 124 | |
|---|
| 125 | @ivar method: the type of method to call on the reactor, one of B{TCP}, |
|---|
| 126 | B{UDP}, B{SSL} or B{UNIX}. |
|---|
| 127 | @type method: C{str} |
|---|
| 128 | |
|---|
| 129 | @ivar reactor: the current running reactor. |
|---|
| 130 | @type reactor: a provider of C{IReactorTCP}, C{IReactorUDP}, |
|---|
| 131 | C{IReactorSSL} or C{IReactorUnix}. |
|---|
| 132 | |
|---|
| 133 | @ivar _connection: instance of connection set when the service is started. |
|---|
| 134 | @type _connection: a provider of C{IConnector}. |
|---|
| 135 | """ |
|---|
| 136 | volatile = ['_connection'] |
|---|
| 137 | method = None |
|---|
| 138 | reactor = None |
|---|
| 139 | |
|---|
| 140 | _connection = None |
|---|
| 141 | |
|---|
| 142 | def __init__(self, *args, **kwargs): |
|---|
| 143 | self.args = args |
|---|
| 144 | if 'reactor' in kwargs: |
|---|
| 145 | self.reactor = kwargs.pop("reactor") |
|---|
| 146 | self.kwargs = kwargs |
|---|
| 147 | |
|---|
| 148 | |
|---|
| 149 | def startService(self): |
|---|
| 150 | service.Service.startService(self) |
|---|
| 151 | self._connection = self._getConnection() |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | def stopService(self): |
|---|
| 155 | service.Service.stopService(self) |
|---|
| 156 | if self._connection is not None: |
|---|
| 157 | self._connection.disconnect() |
|---|
| 158 | del self._connection |
|---|
| 159 | |
|---|
| 160 | |
|---|
| 161 | def _getConnection(self): |
|---|
| 162 | """ |
|---|
| 163 | Wrapper around the appropriate connect method of the reactor. |
|---|
| 164 | |
|---|
| 165 | @return: the port object returned by the connect method. |
|---|
| 166 | @rtype: an object providing L{IConnector}. |
|---|
| 167 | """ |
|---|
| 168 | if self.reactor is None: |
|---|
| 169 | from twisted.internet import reactor |
|---|
| 170 | else: |
|---|
| 171 | reactor = self.reactor |
|---|
| 172 | return getattr(reactor, 'connect%s' % (self.method,))( |
|---|
| 173 | *self.args, **self.kwargs) |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | |
|---|
| 177 | _doc={ |
|---|
| 178 | 'Client': |
|---|
| 179 | """Connect to %(tran)s |
|---|
| 180 | |
|---|
| 181 | Call reactor.connect%(method)s when the service starts, with the |
|---|
| 182 | arguments given to the constructor. |
|---|
| 183 | """, |
|---|
| 184 | 'Server': |
|---|
| 185 | """Serve %(tran)s clients |
|---|
| 186 | |
|---|
| 187 | Call reactor.listen%(method)s when the service starts, with the |
|---|
| 188 | arguments given to the constructor. When the service stops, |
|---|
| 189 | stop listening. See twisted.internet.interfaces for documentation |
|---|
| 190 | on arguments to the reactor method. |
|---|
| 191 | """, |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | import new |
|---|
| 195 | for tran in 'Generic TCP UNIX SSL UDP UNIXDatagram Multicast'.split(): |
|---|
| 196 | for side in 'Server Client'.split(): |
|---|
| 197 | if tran == "Multicast" and side == "Client": |
|---|
| 198 | continue |
|---|
| 199 | base = globals()['_Abstract'+side] |
|---|
| 200 | method = {'Generic': 'With'}.get(tran, tran) |
|---|
| 201 | doc = _doc[side]%vars() |
|---|
| 202 | klass = new.classobj(tran+side, (base,), |
|---|
| 203 | {'method': method, '__doc__': doc}) |
|---|
| 204 | globals()[tran+side] = klass |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | class TimerService(_VolatileDataService): |
|---|
| 208 | |
|---|
| 209 | """Service to periodically call a function |
|---|
| 210 | |
|---|
| 211 | Every C{step} seconds call the given function with the given arguments. |
|---|
| 212 | The service starts the calls when it starts, and cancels them |
|---|
| 213 | when it stops. |
|---|
| 214 | """ |
|---|
| 215 | |
|---|
| 216 | volatile = ['_loop'] |
|---|
| 217 | |
|---|
| 218 | def __init__(self, step, callable, *args, **kwargs): |
|---|
| 219 | self.step = step |
|---|
| 220 | self.call = (callable, args, kwargs) |
|---|
| 221 | |
|---|
| 222 | def startService(self): |
|---|
| 223 | service.Service.startService(self) |
|---|
| 224 | callable, args, kwargs = self.call |
|---|
| 225 | |
|---|
| 226 | |
|---|
| 227 | |
|---|
| 228 | |
|---|
| 229 | self._loop = task.LoopingCall(callable, *args, **kwargs) |
|---|
| 230 | self._loop.start(self.step, now=True).addErrback(self._failed) |
|---|
| 231 | |
|---|
| 232 | def _failed(self, why): |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | |
|---|
| 236 | self._loop.running = False |
|---|
| 237 | log.err(why) |
|---|
| 238 | |
|---|
| 239 | def stopService(self): |
|---|
| 240 | if self._loop.running: |
|---|
| 241 | self._loop.stop() |
|---|
| 242 | return service.Service.stopService(self) |
|---|
| 243 | |
|---|
| 244 | |
|---|
| 245 | |
|---|
| 246 | class CooperatorService(service.Service): |
|---|
| 247 | """ |
|---|
| 248 | Simple L{service.IService} which starts and stops a L{twisted.internet.task.Cooperator}. |
|---|
| 249 | """ |
|---|
| 250 | def __init__(self): |
|---|
| 251 | self.coop = task.Cooperator(started=False) |
|---|
| 252 | |
|---|
| 253 | |
|---|
| 254 | def coiterate(self, iterator): |
|---|
| 255 | return self.coop.coiterate(iterator) |
|---|
| 256 | |
|---|
| 257 | |
|---|
| 258 | def startService(self): |
|---|
| 259 | self.coop.start() |
|---|
| 260 | |
|---|
| 261 | |
|---|
| 262 | def stopService(self): |
|---|
| 263 | self.coop.stop() |
|---|
| 264 | |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | __all__ = (['TimerService', 'CooperatorService'] + |
|---|
| 268 | [tran+side |
|---|
| 269 | for tran in 'Generic TCP UNIX SSL UDP UNIXDatagram Multicast'.split() |
|---|
| 270 | for side in 'Server Client'.split()]) |
|---|