| Index: third_party/twisted_8_1/twisted/protocols/amp.py
|
| diff --git a/third_party/twisted_8_1/twisted/protocols/amp.py b/third_party/twisted_8_1/twisted/protocols/amp.py
|
| deleted file mode 100644
|
| index 9f0d8e70644f72f96fac8a49167e882312877bfb..0000000000000000000000000000000000000000
|
| --- a/third_party/twisted_8_1/twisted/protocols/amp.py
|
| +++ /dev/null
|
| @@ -1,2223 +0,0 @@
|
| -# -*- test-case-name: twisted.test.test_amp -*-
|
| -# Copyright (c) 2005 Divmod, Inc.
|
| -# Copyright (c) 2007 Twisted Matrix Laboratories.
|
| -# See LICENSE for details.
|
| -
|
| -"""
|
| -This module implements AMP, the Asynchronous Messaging Protocol.
|
| -
|
| -AMP is a protocol for sending multiple asynchronous request/response pairs over
|
| -the same connection. Requests and responses are both collections of key/value
|
| -pairs.
|
| -
|
| -AMP is a very simple protocol which is not an application. This module is a
|
| -"protocol construction kit" of sorts; it attempts to be the simplest wire-level
|
| -implementation of Deferreds. AMP provides the following base-level features:
|
| -
|
| - - Asynchronous request/response handling (hence the name)
|
| -
|
| - - Requests and responses are both key/value pairs
|
| -
|
| - - Binary transfer of all data: all data is length-prefixed. Your
|
| - application will never need to worry about quoting.
|
| -
|
| - - Command dispatching (like HTTP Verbs): the protocol is extensible, and
|
| - multiple AMP sub-protocols can be grouped together easily.
|
| -
|
| -The protocol implementation also provides a few additional features which are
|
| -not part of the core wire protocol, but are nevertheless very useful:
|
| -
|
| - - Tight TLS integration, with an included StartTLS command.
|
| -
|
| - - Handshaking to other protocols: because AMP has well-defined message
|
| - boundaries and maintains all incoming and outgoing requests for you, you
|
| - can start a connection over AMP and then switch to another protocol.
|
| - This makes it ideal for firewall-traversal applications where you may
|
| - have only one forwarded port but multiple applications that want to use
|
| - it.
|
| -
|
| -Using AMP with Twisted is simple. Each message is a command, with a response.
|
| -You begin by defining a command type. Commands specify their input and output
|
| -in terms of the types that they expect to see in the request and response
|
| -key-value pairs. Here's an example of a command that adds two integers, 'a'
|
| -and 'b'::
|
| -
|
| - class Sum(amp.Command):
|
| - arguments = [('a', amp.Integer()),
|
| - ('b', amp.Integer())]
|
| - response = [('total', amp.Integer())]
|
| -
|
| -Once you have specified a command, you need to make it part of a protocol, and
|
| -define a responder for it. Here's a 'JustSum' protocol that includes a
|
| -responder for our 'Sum' command::
|
| -
|
| - class JustSum(amp.AMP):
|
| - def sum(self, a, b):
|
| - total = a + b
|
| - print 'Did a sum: %d + %d = %d' % (a, b, total)
|
| - return {'total': total}
|
| - Sum.responder(sum)
|
| -
|
| -Later, when you want to actually do a sum, the following expression will return
|
| -a Deferred which will fire with the result::
|
| -
|
| - ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
|
| - lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
|
| - lambda result: result['total'])
|
| -
|
| -You can also define the propagation of specific errors in AMP. For example,
|
| -for the slightly more complicated case of division, we might have to deal with
|
| -division by zero::
|
| -
|
| - class Divide(amp.Command):
|
| - arguments = [('numerator', amp.Integer()),
|
| - ('denominator', amp.Integer())]
|
| - response = [('result', amp.Float())]
|
| - errors = {ZeroDivisionError: 'ZERO_DIVISION'}
|
| -
|
| -The 'errors' mapping here tells AMP that if a responder to Divide emits a
|
| -L{ZeroDivisionError}, then the other side should be informed that an error of
|
| -the type 'ZERO_DIVISION' has occurred. Writing a responder which takes
|
| -advantage of this is very simple - just raise your exception normally::
|
| -
|
| - class JustDivide(amp.AMP):
|
| - def divide(self, numerator, denominator):
|
| - result = numerator / denominator
|
| - print 'Divided: %d / %d = %d' % (numerator, denominator, total)
|
| - return {'result': result}
|
| - Divide.responder(divide)
|
| -
|
| -On the client side, the errors mapping will be used to determine what the
|
| -'ZERO_DIVISION' error means, and translated into an asynchronous exception,
|
| -which can be handled normally as any L{Deferred} would be::
|
| -
|
| - def trapZero(result):
|
| - result.trap(ZeroDivisionError)
|
| - print "Divided by zero: returning INF"
|
| - return 1e1000
|
| - ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
|
| - lambda p: p.callRemote(Divide, numerator=1234,
|
| - denominator=0)
|
| - ).addErrback(trapZero)
|
| -
|
| -For a complete, runnable example of both of these commands, see the files in
|
| -the Twisted repository::
|
| -
|
| - doc/core/examples/ampserver.py
|
| - doc/core/examples/ampclient.py
|
| -
|
| -On the wire, AMP is a protocol which uses 2-byte lengths to prefix keys and
|
| -values, and empty keys to separate messages::
|
| -
|
| - <2-byte length><key><2-byte length><value>
|
| - <2-byte length><key><2-byte length><value>
|
| - ...
|
| - <2-byte length><key><2-byte length><value>
|
| - <NUL><NUL> # Empty Key == End of Message
|
| -
|
| -And so on. Because it's tedious to refer to lengths and NULs constantly, the
|
| -documentation will refer to packets as if they were newline delimited, like
|
| -so::
|
| -
|
| - C: _command: sum
|
| - C: _ask: ef639e5c892ccb54
|
| - C: a: 13
|
| - C: b: 81
|
| -
|
| - S: _answer: ef639e5c892ccb54
|
| - S: total: 94
|
| -
|
| -Notes:
|
| -
|
| -Values are limited to the maximum encodable size in a 16-bit length, 65535
|
| -bytes.
|
| -
|
| -Keys are limited to the maximum encodable size in a 8-bit length, 255 bytes.
|
| -Note that we still use 2-byte lengths to encode keys. This small redundancy
|
| -has several features:
|
| -
|
| - - If an implementation becomes confused and starts emitting corrupt data,
|
| - or gets keys confused with values, many common errors will be
|
| - signalled immediately instead of delivering obviously corrupt packets.
|
| -
|
| - - A single NUL will separate every key, and a double NUL separates
|
| - messages. This provides some redundancy when debugging traffic dumps.
|
| -
|
| - - NULs will be present at regular intervals along the protocol, providing
|
| - some padding for otherwise braindead C implementations of the protocol,
|
| - so that <stdio.h> string functions will see the NUL and stop.
|
| -
|
| - - This makes it possible to run an AMP server on a port also used by a
|
| - plain-text protocol, and easily distinguish between non-AMP clients (like
|
| - web browsers) which issue non-NUL as the first byte, and AMP clients,
|
| - which always issue NUL as the first byte.
|
| -
|
| -"""
|
| -
|
| -__metaclass__ = type
|
| -
|
| -import types, warnings
|
| -
|
| -from cStringIO import StringIO
|
| -from struct import pack
|
| -
|
| -from zope.interface import Interface, implements
|
| -
|
| -from twisted.python.reflect import accumulateClassDict
|
| -from twisted.python.failure import Failure
|
| -from twisted.python import log, filepath
|
| -
|
| -from twisted.internet.main import CONNECTION_LOST
|
| -from twisted.internet.error import PeerVerifyError, ConnectionLost
|
| -from twisted.internet.defer import Deferred, maybeDeferred, fail
|
| -from twisted.protocols.basic import Int16StringReceiver, StatefulStringProtocol
|
| -
|
| -from twisted.internet._sslverify import problemsFromTransport
|
| -
|
| -# I'd like this to use the exposed public API, but for some reason, when it was
|
| -# moved, these names were not exposed by internet.ssl.
|
| -
|
| -from twisted.internet.ssl import CertificateOptions, Certificate, DN, KeyPair
|
| -
|
| -ASK = '_ask'
|
| -ANSWER = '_answer'
|
| -COMMAND = '_command'
|
| -ERROR = '_error'
|
| -ERROR_CODE = '_error_code'
|
| -ERROR_DESCRIPTION = '_error_description'
|
| -UNKNOWN_ERROR_CODE = 'UNKNOWN'
|
| -UNHANDLED_ERROR_CODE = 'UNHANDLED'
|
| -
|
| -MAX_KEY_LENGTH = 0xff
|
| -MAX_VALUE_LENGTH = 0xffff
|
| -
|
| -
|
| -class IBoxSender(Interface):
|
| - """
|
| - A transport which can send L{AmpBox} objects.
|
| - """
|
| -
|
| - def sendBox(box):
|
| - """
|
| - Send an L{AmpBox}.
|
| -
|
| - @raise ProtocolSwitched: if the underlying protocol has been
|
| - switched.
|
| -
|
| - @raise ConnectionLost: if the underlying connection has already been
|
| - lost.
|
| - """
|
| -
|
| - def unhandledError(failure):
|
| - """
|
| - An unhandled error occurred in response to a box. Log it
|
| - appropriately.
|
| -
|
| - @param failure: a L{Failure} describing the error that occurred.
|
| - """
|
| -
|
| -
|
| -
|
| -class IBoxReceiver(Interface):
|
| - """
|
| - An application object which can receive L{AmpBox} objects and dispatch them
|
| - appropriately.
|
| - """
|
| -
|
| - def startReceivingBoxes(boxSender):
|
| - """
|
| - The L{ampBoxReceived} method will start being called; boxes may be
|
| - responded to by responding to the given L{IBoxSender}.
|
| -
|
| - @param boxSender: an L{IBoxSender} provider.
|
| - """
|
| -
|
| -
|
| - def ampBoxReceived(box):
|
| - """
|
| - A box was received from the transport; dispatch it appropriately.
|
| - """
|
| -
|
| -
|
| - def stopReceivingBoxes(reason):
|
| - """
|
| - No further boxes will be received on this connection.
|
| -
|
| - @type reason: L{Failure}
|
| - """
|
| -
|
| -
|
| -
|
| -class IResponderLocator(Interface):
|
| - """
|
| - An application object which can look up appropriate responder methods for
|
| - AMP commands.
|
| - """
|
| -
|
| - def locateResponder(self, name):
|
| - """
|
| - Locate a responder method appropriate for the named command.
|
| -
|
| - @param name: the wire-level name (commandName) of the AMP command to be
|
| - responded to.
|
| -
|
| - @return: a 1-argument callable that takes an L{AmpBox} with argument
|
| - values for the given command, and returns an L{AmpBox} containing
|
| - argument values for the named command, or a L{Deferred} that fires the
|
| - same.
|
| - """
|
| -
|
| -
|
| -
|
| -class AmpError(Exception):
|
| - """
|
| - Base class of all Amp-related exceptions.
|
| - """
|
| -
|
| -
|
| -
|
| -class ProtocolSwitched(Exception):
|
| - """
|
| - Connections which have been switched to other protocols can no longer
|
| - accept traffic at the AMP level. This is raised when you try to send it.
|
| - """
|
| -
|
| -
|
| -
|
| -class OnlyOneTLS(AmpError):
|
| - """
|
| - This is an implementation limitation; TLS may only be started once per
|
| - connection.
|
| - """
|
| -
|
| -
|
| -
|
| -class NoEmptyBoxes(AmpError):
|
| - """
|
| - You can't have empty boxes on the connection. This is raised when you
|
| - receive or attempt to send one.
|
| - """
|
| -
|
| -
|
| -
|
| -class InvalidSignature(AmpError):
|
| - """
|
| - You didn't pass all the required arguments.
|
| - """
|
| -
|
| -
|
| -
|
| -class TooLong(AmpError):
|
| - """
|
| - One of the protocol's length limitations was violated.
|
| -
|
| - @ivar isKey: true if the string being encoded in a key position, false if
|
| - it was in a value position.
|
| -
|
| - @ivar isLocal: Was the string encoded locally, or received too long from
|
| - the network? (It's only physically possible to encode "too long" values on
|
| - the network for keys.)
|
| -
|
| - @ivar value: The string that was too long.
|
| -
|
| - @ivar keyName: If the string being encoded was in a value position, what
|
| - key was it being encoded for?
|
| - """
|
| -
|
| - def __init__(self, isKey, isLocal, value, keyName=None):
|
| - AmpError.__init__(self)
|
| - self.isKey = isKey
|
| - self.isLocal = isLocal
|
| - self.value = value
|
| - self.keyName = keyName
|
| -
|
| -
|
| - def __repr__(self):
|
| - hdr = self.isKey and "key" or "value"
|
| - if not self.isKey:
|
| - hdr += ' ' + repr(self.keyName)
|
| - lcl = self.isLocal and "local" or "remote"
|
| - return "%s %s too long: %d" % (lcl, hdr, len(self.value))
|
| -
|
| -
|
| -
|
| -class BadLocalReturn(AmpError):
|
| - """
|
| - A bad value was returned from a local command; we were unable to coerce it.
|
| - """
|
| - def __init__(self, message, enclosed):
|
| - AmpError.__init__(self)
|
| - self.message = message
|
| - self.enclosed = enclosed
|
| -
|
| -
|
| - def __repr__(self):
|
| - return self.message + " " + self.enclosed.getBriefTraceback()
|
| -
|
| - __str__ = __repr__
|
| -
|
| -
|
| -
|
| -class RemoteAmpError(AmpError):
|
| - """
|
| - This error indicates that something went wrong on the remote end of the
|
| - connection, and the error was serialized and transmitted to you.
|
| - """
|
| - def __init__(self, errorCode, description, fatal=False, local=None):
|
| - """Create a remote error with an error code and description.
|
| -
|
| - @param errorCode: the AMP error code of this error.
|
| -
|
| - @param description: some text to show to the user.
|
| -
|
| - @param fatal: a boolean, true if this error should terminate the
|
| - connection.
|
| -
|
| - @param local: a local Failure, if one exists.
|
| - """
|
| - if local:
|
| - localwhat = ' (local)'
|
| - othertb = local.getBriefTraceback()
|
| - else:
|
| - localwhat = ''
|
| - othertb = ''
|
| - Exception.__init__(self, "Code<%s>%s: %s%s" % (
|
| - errorCode, localwhat,
|
| - description, othertb))
|
| - self.local = local
|
| - self.errorCode = errorCode
|
| - self.description = description
|
| - self.fatal = fatal
|
| -
|
| -
|
| -
|
| -class UnknownRemoteError(RemoteAmpError):
|
| - """
|
| - This means that an error whose type we can't identify was raised from the
|
| - other side.
|
| - """
|
| - def __init__(self, description):
|
| - errorCode = UNKNOWN_ERROR_CODE
|
| - RemoteAmpError.__init__(self, errorCode, description)
|
| -
|
| -
|
| -
|
| -class MalformedAmpBox(AmpError):
|
| - """
|
| - This error indicates that the wire-level protocol was malformed.
|
| - """
|
| -
|
| -
|
| -
|
| -class UnhandledCommand(AmpError):
|
| - """
|
| - A command received via amp could not be dispatched.
|
| - """
|
| -
|
| -
|
| -
|
| -class IncompatibleVersions(AmpError):
|
| - """
|
| - It was impossible to negotiate a compatible version of the protocol with
|
| - the other end of the connection.
|
| - """
|
| -
|
| -
|
| -PROTOCOL_ERRORS = {UNHANDLED_ERROR_CODE: UnhandledCommand}
|
| -
|
| -class AmpBox(dict):
|
| - """
|
| - I am a packet in the AMP protocol, much like a regular str:str dictionary.
|
| - """
|
| - __slots__ = [] # be like a regular dictionary, don't magically
|
| - # acquire a __dict__...
|
| -
|
| -
|
| - def copy(self):
|
| - """
|
| - Return another AmpBox just like me.
|
| - """
|
| - newBox = self.__class__()
|
| - newBox.update(self)
|
| - return newBox
|
| -
|
| -
|
| - def serialize(self):
|
| - """
|
| - Convert me into a wire-encoded string.
|
| -
|
| - @return: a str encoded according to the rules described in the module
|
| - docstring.
|
| - """
|
| - i = self.items()
|
| - i.sort()
|
| - L = []
|
| - w = L.append
|
| - for k, v in i:
|
| - if len(k) > MAX_KEY_LENGTH:
|
| - raise TooLong(True, True, k, None)
|
| - if len(v) > MAX_VALUE_LENGTH:
|
| - raise TooLong(False, True, v, k)
|
| - for kv in k, v:
|
| - w(pack("!H", len(kv)))
|
| - w(kv)
|
| - w(pack("!H", 0))
|
| - return ''.join(L)
|
| -
|
| -
|
| - def _sendTo(self, proto):
|
| - """
|
| - Serialize and send this box to a Amp instance. By the time it is being
|
| - sent, several keys are required. I must have exactly ONE of::
|
| -
|
| - _ask
|
| - _answer
|
| - _error
|
| -
|
| - If the '_ask' key is set, then the '_command' key must also be
|
| - set.
|
| -
|
| - @param proto: an AMP instance.
|
| - """
|
| - proto.sendBox(self)
|
| -
|
| - def __repr__(self):
|
| - return 'AmpBox(%s)' % (dict.__repr__(self),)
|
| -
|
| -# amp.Box => AmpBox
|
| -
|
| -Box = AmpBox
|
| -
|
| -class QuitBox(AmpBox):
|
| - """
|
| - I am an AmpBox that, upon being sent, terminates the connection.
|
| - """
|
| - __slots__ = []
|
| -
|
| -
|
| - def __repr__(self):
|
| - return 'QuitBox(**%s)' % (super(QuitBox, self).__repr__(),)
|
| -
|
| -
|
| - def _sendTo(self, proto):
|
| - """
|
| - Immediately call loseConnection after sending.
|
| - """
|
| - super(QuitBox, self)._sendTo(proto)
|
| - proto.transport.loseConnection()
|
| -
|
| -
|
| -
|
| -class _SwitchBox(AmpBox):
|
| - """
|
| - Implementation detail of ProtocolSwitchCommand: I am a AmpBox which sets
|
| - up state for the protocol to switch.
|
| - """
|
| -
|
| - # DON'T set __slots__ here; we do have an attribute.
|
| -
|
| - def __init__(self, innerProto, **kw):
|
| - """
|
| - Create a _SwitchBox with the protocol to switch to after being sent.
|
| -
|
| - @param innerProto: the protocol instance to switch to.
|
| - @type innerProto: an IProtocol provider.
|
| - """
|
| - super(_SwitchBox, self).__init__(**kw)
|
| - self.innerProto = innerProto
|
| -
|
| -
|
| - def __repr__(self):
|
| - return '_SwitchBox(%r, **%s)' % (self.innerProto,
|
| - dict.__repr__(self),)
|
| -
|
| -
|
| - def _sendTo(self, proto):
|
| - """
|
| - Send me; I am the last box on the connection. All further traffic will be
|
| - over the new protocol.
|
| - """
|
| - super(_SwitchBox, self)._sendTo(proto)
|
| - proto._lockForSwitch()
|
| - proto._switchTo(self.innerProto)
|
| -
|
| -
|
| -
|
| -class BoxDispatcher:
|
| - """
|
| - A L{BoxDispatcher} dispatches '_ask', '_answer', and '_error' L{AmpBox}es,
|
| - both incoming and outgoing, to their appropriate destinations.
|
| -
|
| - Outgoing commands are converted into L{Deferred}s and outgoing boxes, and
|
| - associated tracking state to fire those L{Deferred} when '_answer' boxes
|
| - come back. Incoming '_answer' and '_error' boxes are converted into
|
| - callbacks and errbacks on those L{Deferred}s, respectively.
|
| -
|
| - Incoming '_ask' boxes are converted into method calls on a supplied method
|
| - locator.
|
| -
|
| - @ivar _outstandingRequests: a dictionary mapping request IDs to
|
| - L{Deferred}s which were returned for those requests.
|
| -
|
| - @ivar locator: an object with a L{locateResponder} method that locates a
|
| - responder function that takes a Box and returns a result (either a Box or a
|
| - Deferred which fires one).
|
| -
|
| - @ivar boxSender: an object which can send boxes, via the L{_sendBox}
|
| - method, such as an L{AMP} instance.
|
| - @type boxSender: L{IBoxSender}
|
| - """
|
| -
|
| - implements(IBoxReceiver)
|
| -
|
| - _failAllReason = None
|
| - _outstandingRequests = None
|
| - _counter = 0L
|
| - boxSender = None
|
| -
|
| - def __init__(self, locator):
|
| - self._outstandingRequests = {}
|
| - self.locator = locator
|
| -
|
| -
|
| - def startReceivingBoxes(self, boxSender):
|
| - """
|
| - The given boxSender is going to start calling boxReceived on this
|
| - L{BoxDispatcher}.
|
| -
|
| - @param boxSender: The L{IBoxSender} to send command responses to.
|
| - """
|
| - self.boxSender = boxSender
|
| -
|
| -
|
| - def stopReceivingBoxes(self, reason):
|
| - """
|
| - No further boxes will be received here. Terminate all currently
|
| - oustanding command deferreds with the given reason.
|
| - """
|
| - self.failAllOutgoing(reason)
|
| -
|
| -
|
| - def failAllOutgoing(self, reason):
|
| - """
|
| - Call the errback on all outstanding requests awaiting responses.
|
| -
|
| - @param reason: the Failure instance to pass to those errbacks.
|
| - """
|
| - self._failAllReason = reason
|
| - OR = self._outstandingRequests.items()
|
| - self._outstandingRequests = None # we can never send another request
|
| - for key, value in OR:
|
| - value.errback(reason)
|
| -
|
| -
|
| - def _nextTag(self):
|
| - """
|
| - Generate protocol-local serial numbers for _ask keys.
|
| -
|
| - @return: a string that has not yet been used on this connection.
|
| - """
|
| - self._counter += 1
|
| - return '%x' % (self._counter,)
|
| -
|
| -
|
| - def _sendBoxCommand(self, command, box, requiresAnswer=True):
|
| - """
|
| - Send a command across the wire with the given C{amp.Box}.
|
| -
|
| - Mutate the given box to give it any additional keys (_command, _ask)
|
| - required for the command and request/response machinery, then send it.
|
| -
|
| - If requiresAnswer is True, returns a C{Deferred} which fires when a
|
| - response is received. The C{Deferred} is fired with an C{amp.Box} on
|
| - success, or with an C{amp.RemoteAmpError} if an error is received.
|
| -
|
| - If the Deferred fails and the error is not handled by the caller of
|
| - this method, the failure will be logged and the connection dropped.
|
| -
|
| - @param command: a str, the name of the command to issue.
|
| -
|
| - @param box: an AmpBox with the arguments for the command.
|
| -
|
| - @param requiresAnswer: a boolean. Defaults to True. If True, return a
|
| - Deferred which will fire when the other side responds to this command.
|
| - If False, return None and do not ask the other side for acknowledgement.
|
| -
|
| - @return: a Deferred which fires the AmpBox that holds the response to
|
| - this command, or None, as specified by requiresAnswer.
|
| -
|
| - @raise ProtocolSwitched: if the protocol has been switched.
|
| - """
|
| - if self._failAllReason is not None:
|
| - return fail(self._failAllReason)
|
| - box[COMMAND] = command
|
| - tag = self._nextTag()
|
| - if requiresAnswer:
|
| - box[ASK] = tag
|
| - box._sendTo(self.boxSender)
|
| - if requiresAnswer:
|
| - result = self._outstandingRequests[tag] = Deferred()
|
| - else:
|
| - result = None
|
| - return result
|
| -
|
| -
|
| - def callRemoteString(self, command, requiresAnswer=True, **kw):
|
| - """
|
| - This is a low-level API, designed only for optimizing simple messages
|
| - for which the overhead of parsing is too great.
|
| -
|
| - @param command: a str naming the command.
|
| -
|
| - @param kw: arguments to the amp box.
|
| -
|
| - @param requiresAnswer: a boolean. Defaults to True. If True, return a
|
| - Deferred which will fire when the other side responds to this command.
|
| - If False, return None and do not ask the other side for acknowledgement.
|
| -
|
| - @return: a Deferred which fires the AmpBox that holds the response to
|
| - this command, or None, as specified by requiresAnswer.
|
| - """
|
| - box = Box(kw)
|
| - return self._sendBoxCommand(command, box)
|
| -
|
| -
|
| - def callRemote(self, commandType, *a, **kw):
|
| - """
|
| - This is the primary high-level API for sending messages via AMP. Invoke it
|
| - with a command and appropriate arguments to send a message to this
|
| - connection's peer.
|
| -
|
| - @param commandType: a subclass of Command.
|
| - @type commandType: L{type}
|
| -
|
| - @param a: Positional (special) parameters taken by the command.
|
| - Positional parameters will typically not be sent over the wire. The
|
| - only command included with AMP which uses positional parameters is
|
| - L{ProtocolSwitchCommand}, which takes the protocol that will be
|
| - switched to as its first argument.
|
| -
|
| - @param kw: Keyword arguments taken by the command. These are the
|
| - arguments declared in the command's 'arguments' attribute. They will
|
| - be encoded and sent to the peer as arguments for the L{commandType}.
|
| -
|
| - @return: If L{commandType} has a C{requiresAnswer} attribute set to
|
| - L{False}, then return L{None}. Otherwise, return a L{Deferred} which
|
| - fires with a dictionary of objects representing the result of this
|
| - call. Additionally, this L{Deferred} may fail with an exception
|
| - representing a connection failure, with L{UnknownRemoteError} if the
|
| - other end of the connection fails for an unknown reason, or with any
|
| - error specified as a key in L{commandType}'s C{errors} dictionary.
|
| - """
|
| -
|
| - # XXX this takes command subclasses and not command objects on purpose.
|
| - # There's really no reason to have all this back-and-forth between
|
| - # command objects and the protocol, and the extra object being created
|
| - # (the Command instance) is pointless. Command is kind of like
|
| - # Interface, and should be more like it.
|
| -
|
| - # In other words, the fact that commandType is instantiated here is an
|
| - # implementation detail. Don't rely on it.
|
| -
|
| - co = commandType(*a, **kw)
|
| - return co._doCommand(self)
|
| -
|
| -
|
| - def unhandledError(self, failure):
|
| - """
|
| - This is a terminal callback called after application code has had a
|
| - chance to quash any errors.
|
| - """
|
| - return self.boxSender.unhandledError(failure)
|
| -
|
| -
|
| - def _answerReceived(self, box):
|
| - """
|
| - An AMP box was received that answered a command previously sent with
|
| - L{callRemote}.
|
| -
|
| - @param box: an AmpBox with a value for its L{ANSWER} key.
|
| - """
|
| - question = self._outstandingRequests.pop(box[ANSWER])
|
| - question.addErrback(self.unhandledError)
|
| - question.callback(box)
|
| -
|
| -
|
| - def _errorReceived(self, box):
|
| - """
|
| - An AMP box was received that answered a command previously sent with
|
| - L{callRemote}, with an error.
|
| -
|
| - @param box: an L{AmpBox} with a value for its L{ERROR}, L{ERROR_CODE},
|
| - and L{ERROR_DESCRIPTION} keys.
|
| - """
|
| - question = self._outstandingRequests.pop(box[ERROR])
|
| - question.addErrback(self.unhandledError)
|
| - errorCode = box[ERROR_CODE]
|
| - description = box[ERROR_DESCRIPTION]
|
| - if errorCode in PROTOCOL_ERRORS:
|
| - exc = PROTOCOL_ERRORS[errorCode](errorCode, description)
|
| - else:
|
| - exc = RemoteAmpError(errorCode, description)
|
| - question.errback(Failure(exc))
|
| -
|
| -
|
| - def _commandReceived(self, box):
|
| - """
|
| - @param box: an L{AmpBox} with a value for its L{COMMAND} and L{ASK}
|
| - keys.
|
| - """
|
| - cmd = box[COMMAND]
|
| - def formatAnswer(answerBox):
|
| - answerBox[ANSWER] = box[ASK]
|
| - return answerBox
|
| - def formatError(error):
|
| - if error.check(RemoteAmpError):
|
| - code = error.value.errorCode
|
| - desc = error.value.description
|
| - if error.value.fatal:
|
| - errorBox = QuitBox()
|
| - else:
|
| - errorBox = AmpBox()
|
| - else:
|
| - errorBox = QuitBox()
|
| - log.err(error) # here is where server-side logging happens
|
| - # if the error isn't handled
|
| - code = UNKNOWN_ERROR_CODE
|
| - desc = "Unknown Error"
|
| - errorBox[ERROR] = box[ASK]
|
| - errorBox[ERROR_DESCRIPTION] = desc
|
| - errorBox[ERROR_CODE] = code
|
| - return errorBox
|
| - deferred = self.dispatchCommand(box)
|
| - if ASK in box:
|
| - deferred.addCallbacks(formatAnswer, formatError)
|
| - deferred.addCallback(self._safeEmit)
|
| - deferred.addErrback(self.unhandledError)
|
| -
|
| -
|
| - def ampBoxReceived(self, box):
|
| - """
|
| - An AmpBox was received, representing a command, or an answer to a
|
| - previously issued command (either successful or erroneous). Respond to
|
| - it according to its contents.
|
| -
|
| - @param box: an AmpBox
|
| -
|
| - @raise NoEmptyBoxes: when a box is received that does not contain an
|
| - '_answer', '_command' / '_ask', or '_error' key; i.e. one which does not
|
| - fit into the command / response protocol defined by AMP.
|
| - """
|
| - if ANSWER in box:
|
| - self._answerReceived(box)
|
| - elif ERROR in box:
|
| - self._errorReceived(box)
|
| - elif COMMAND in box:
|
| - self._commandReceived(box)
|
| - else:
|
| - raise NoEmptyBoxes(box)
|
| -
|
| -
|
| - def _safeEmit(self, aBox):
|
| - """
|
| - Emit a box, ignoring L{ProtocolSwitched} and L{ConnectionLost} errors
|
| - which cannot be usefully handled.
|
| - """
|
| - try:
|
| - aBox._sendTo(self.boxSender)
|
| - except (ProtocolSwitched, ConnectionLost):
|
| - pass
|
| -
|
| -
|
| - def dispatchCommand(self, box):
|
| - """
|
| - A box with a _command key was received.
|
| -
|
| - Dispatch it to a local handler call it.
|
| -
|
| - @param proto: an AMP instance.
|
| - @param box: an AmpBox to be dispatched.
|
| - """
|
| - cmd = box[COMMAND]
|
| - responder = self.locator.locateResponder(cmd)
|
| - if responder is None:
|
| - return fail(RemoteAmpError(
|
| - UNHANDLED_ERROR_CODE,
|
| - "Unhandled Command: %r" % (cmd,),
|
| - False,
|
| - local=Failure(UnhandledCommand())))
|
| - return maybeDeferred(responder, box)
|
| -
|
| -
|
| -
|
| -class CommandLocator:
|
| - """
|
| - A L{CommandLocator} is a collection of responders to AMP L{Command}s, with
|
| - the help of the L{Command.responder} decorator.
|
| - """
|
| -
|
| - class __metaclass__(type):
|
| - """
|
| - This metaclass keeps track of all of the Command.responder-decorated
|
| - methods defined since the last CommandLocator subclass was defined. It
|
| - assumes (usually correctly, but unfortunately not necessarily so) that
|
| - those commands responders were all declared as methods of the class
|
| - being defined. Note that this list can be incorrect if users use the
|
| - Command.responder decorator outside the context of a CommandLocator
|
| - class declaration.
|
| -
|
| - The Command.responder decorator explicitly cooperates with this
|
| - metaclass.
|
| - """
|
| -
|
| - _currentClassCommands = []
|
| - def __new__(cls, name, bases, attrs):
|
| - commands = cls._currentClassCommands[:]
|
| - cls._currentClassCommands[:] = []
|
| - cd = attrs['_commandDispatch'] = {}
|
| - for base in bases:
|
| - cls._grabFromBase(cd, base)
|
| - for commandClass, responderFunc in commands:
|
| - cd[commandClass.commandName] = (commandClass, responderFunc)
|
| - subcls = type.__new__(cls, name, bases, attrs)
|
| - if (bases and (
|
| - subcls.lookupFunction != CommandLocator.lookupFunction)):
|
| - def locateResponder(self, name):
|
| - warnings.warn(
|
| - "Override locateResponder, not lookupFunction.",
|
| - category=PendingDeprecationWarning,
|
| - stacklevel=2)
|
| - return self.lookupFunction(name)
|
| - subcls.locateResponder = locateResponder
|
| - return subcls
|
| -
|
| - def _grabFromBase(cls, cd, base):
|
| - if hasattr(base, "_commandDispatch"):
|
| - cd.update(base._commandDispatch)
|
| - for subbase in base.__bases__:
|
| - cls._grabFromBase(cd, subbase)
|
| - _grabFromBase = classmethod(_grabFromBase)
|
| -
|
| - implements(IResponderLocator)
|
| -
|
| -
|
| - def _wrapWithSerialization(self, aCallable, command):
|
| - """
|
| - Wrap aCallable with its command's argument de-serialization
|
| - and result serialization logic.
|
| -
|
| - @param aCallable: a callable with a 'command' attribute, designed to be
|
| - called with keyword arguments.
|
| -
|
| - @param command: the command class whose serialization to use.
|
| -
|
| - @return: a 1-arg callable which, when invoked with an AmpBox, will
|
| - deserialize the argument list and invoke appropriate user code for the
|
| - callable's command, returning a Deferred which fires with the result or
|
| - fails with an error.
|
| - """
|
| - def doit(box):
|
| - kw = command.parseArguments(box, self)
|
| - def checkKnownErrors(error):
|
| - key = error.trap(*command.allErrors)
|
| - code = command.allErrors[key]
|
| - desc = str(error.value)
|
| - return Failure(RemoteAmpError(
|
| - code, desc, key in command.fatalErrors, local=error))
|
| - def makeResponseFor(objects):
|
| - try:
|
| - return command.makeResponse(objects, self)
|
| - except:
|
| - # let's helpfully log this.
|
| - originalFailure = Failure()
|
| - raise BadLocalReturn(
|
| - "%r returned %r and %r could not serialize it" % (
|
| - aCallable,
|
| - objects,
|
| - command),
|
| - originalFailure)
|
| - return maybeDeferred(aCallable, **kw).addCallback(
|
| - makeResponseFor).addErrback(
|
| - checkKnownErrors)
|
| - return doit
|
| -
|
| -
|
| - def lookupFunction(self, name):
|
| - """
|
| - Deprecated synonym for L{locateResponder}
|
| - """
|
| - if self.__class__.lookupFunction != CommandLocator.lookupFunction:
|
| - return CommandLocator.locateResponder(self, name)
|
| - else:
|
| - warnings.warn("Call locateResponder, not lookupFunction.",
|
| - category=PendingDeprecationWarning,
|
| - stacklevel=2)
|
| - return self.locateResponder(name)
|
| -
|
| -
|
| - def locateResponder(self, name):
|
| - """
|
| - Locate a callable to invoke when executing the named command.
|
| -
|
| - @param name: the normalized name (from the wire) of the command.
|
| -
|
| - @return: a 1-argument function that takes a Box and returns a box or a
|
| - Deferred which fires a Box, for handling the command identified by the
|
| - given name, or None, if no appropriate responder can be found.
|
| - """
|
| - # Try to find a high-level method to invoke, and if we can't find one,
|
| - # fall back to a low-level one.
|
| - cd = self._commandDispatch
|
| - if name in cd:
|
| - commandClass, responderFunc = cd[name]
|
| - responderMethod = types.MethodType(
|
| - responderFunc, self, self.__class__)
|
| - return self._wrapWithSerialization(responderMethod, commandClass)
|
| -
|
| -
|
| -
|
| -class SimpleStringLocator(object):
|
| - """
|
| - Implement the L{locateResponder} method to do simple, string-based
|
| - dispatch.
|
| - """
|
| -
|
| - implements(IResponderLocator)
|
| -
|
| - baseDispatchPrefix = 'amp_'
|
| -
|
| - def locateResponder(self, name):
|
| - """
|
| - Locate a callable to invoke when executing the named command.
|
| -
|
| - @return: a function with the name C{"amp_" + name} on L{self}, or None
|
| - if no such function exists. This function will then be called with the
|
| - L{AmpBox} itself as an argument.
|
| -
|
| - @param name: the normalized name (from the wire) of the command.
|
| - """
|
| - fName = self.baseDispatchPrefix + (name.upper())
|
| - return getattr(self, fName, None)
|
| -
|
| -
|
| -
|
| -PYTHON_KEYWORDS = [
|
| - 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda',
|
| - 'return', 'break', 'else', 'global', 'not', 'try', 'class', 'except',
|
| - 'if', 'or', 'while', 'continue', 'exec', 'import', 'pass', 'yield',
|
| - 'def', 'finally', 'in', 'print']
|
| -
|
| -
|
| -
|
| -def _wireNameToPythonIdentifier(key):
|
| - """
|
| - (Private) Normalize an argument name from the wire for use with Python
|
| - code. If the return value is going to be a python keyword it will be
|
| - capitalized. If it contains any dashes they will be replaced with
|
| - underscores.
|
| -
|
| - The rationale behind this method is that AMP should be an inherently
|
| - multi-language protocol, so message keys may contain all manner of bizarre
|
| - bytes. This is not a complete solution; there are still forms of arguments
|
| - that this implementation will be unable to parse. However, Python
|
| - identifiers share a huge raft of properties with identifiers from many
|
| - other languages, so this is a 'good enough' effort for now. We deal
|
| - explicitly with dashes because that is the most likely departure: Lisps
|
| - commonly use dashes to separate method names, so protocols initially
|
| - implemented in a lisp amp dialect may use dashes in argument or command
|
| - names.
|
| -
|
| - @param key: a str, looking something like 'foo-bar-baz' or 'from'
|
| -
|
| - @return: a str which is a valid python identifier, looking something like
|
| - 'foo_bar_baz' or 'From'.
|
| - """
|
| - lkey = key.replace("-", "_")
|
| - if lkey in PYTHON_KEYWORDS:
|
| - return lkey.title()
|
| - return lkey
|
| -
|
| -
|
| -
|
| -class Argument:
|
| - """
|
| - Base-class of all objects that take values from Amp packets and convert
|
| - them into objects for Python functions.
|
| - """
|
| - optional = False
|
| -
|
| -
|
| - def __init__(self, optional=False):
|
| - """
|
| - Create an Argument.
|
| -
|
| - @param optional: a boolean indicating whether this argument can be
|
| - omitted in the protocol.
|
| - """
|
| - self.optional = optional
|
| -
|
| -
|
| - def retrieve(self, d, name, proto):
|
| - """
|
| - Retrieve the given key from the given dictionary, removing it if found.
|
| -
|
| - @param d: a dictionary.
|
| -
|
| - @param name: a key in L{d}.
|
| -
|
| - @param proto: an instance of an AMP.
|
| -
|
| - @raise KeyError: if I am not optional and no value was found.
|
| -
|
| - @return: d[name].
|
| - """
|
| - if self.optional:
|
| - value = d.get(name)
|
| - if value is not None:
|
| - del d[name]
|
| - else:
|
| - value = d.pop(name)
|
| - return value
|
| -
|
| -
|
| - def fromBox(self, name, strings, objects, proto):
|
| - """
|
| - Populate an 'out' dictionary with mapping names to Python values
|
| - decoded from an 'in' AmpBox mapping strings to string values.
|
| -
|
| - @param name: the argument name to retrieve
|
| - @type name: str
|
| -
|
| - @param strings: The AmpBox to read string(s) from, a mapping of
|
| - argument names to string values.
|
| - @type strings: AmpBox
|
| -
|
| - @param objects: The dictionary to write object(s) to, a mapping of
|
| - names to Python objects.
|
| - @type objects: dict
|
| -
|
| - @param proto: an AMP instance.
|
| - """
|
| - st = self.retrieve(strings, name, proto)
|
| - nk = _wireNameToPythonIdentifier(name)
|
| - if self.optional and st is None:
|
| - objects[nk] = None
|
| - else:
|
| - objects[nk] = self.fromStringProto(st, proto)
|
| -
|
| -
|
| - def toBox(self, name, strings, objects, proto):
|
| - """
|
| - Populate an 'out' AmpBox with strings encoded from an 'in' dictionary
|
| - mapping names to Python values.
|
| -
|
| - @param name: the argument name to retrieve
|
| - @type name: str
|
| -
|
| - @param strings: The AmpBox to write string(s) to, a mapping of
|
| - argument names to string values.
|
| - @type strings: AmpBox
|
| -
|
| - @param objects: The dictionary to read object(s) from, a mapping of
|
| - names to Python objects.
|
| -
|
| - @type objects: dict
|
| -
|
| - @param proto: the protocol we are converting for.
|
| - @type proto: AMP
|
| - """
|
| - obj = self.retrieve(objects, _wireNameToPythonIdentifier(name), proto)
|
| - if self.optional and obj is None:
|
| - # strings[name] = None
|
| - pass
|
| - else:
|
| - strings[name] = self.toStringProto(obj, proto)
|
| -
|
| -
|
| - def fromStringProto(self, inString, proto):
|
| - """
|
| - Convert a string to a Python value.
|
| -
|
| - @param inString: the string to convert.
|
| -
|
| - @param proto: the protocol we are converting for.
|
| - @type proto: AMP
|
| -
|
| - @return: a Python object.
|
| - """
|
| - return self.fromString(inString)
|
| -
|
| -
|
| - def toStringProto(self, inObject, proto):
|
| - """
|
| - Convert a Python object to a string.
|
| -
|
| - @param inObject: the object to convert.
|
| -
|
| - @param proto: the protocol we are converting for.
|
| - @type proto: AMP
|
| - """
|
| - return self.toString(inObject)
|
| -
|
| -
|
| - def fromString(self, inString):
|
| - """
|
| - Convert a string to a Python object. Subclasses must implement this.
|
| -
|
| - @param inString: the string to convert.
|
| - @type inString: str
|
| -
|
| - @return: the decoded value from inString
|
| - """
|
| -
|
| -
|
| - def toString(self, inObject):
|
| - """
|
| - Convert a Python object into a string for passing over the network.
|
| -
|
| - @param inObject: an object of the type that this Argument is intended
|
| - to deal with.
|
| -
|
| - @return: the wire encoding of inObject
|
| - @rtype: str
|
| - """
|
| -
|
| -
|
| -
|
| -class Integer(Argument):
|
| - """
|
| - Convert to and from 'int'.
|
| - """
|
| - fromString = int
|
| - def toString(self, inObject):
|
| - return str(int(inObject))
|
| -
|
| -
|
| -
|
| -class String(Argument):
|
| - """
|
| - Don't do any conversion at all; just pass through 'str'.
|
| - """
|
| - def toString(self, inObject):
|
| - return inObject
|
| -
|
| -
|
| - def fromString(self, inString):
|
| - return inString
|
| -
|
| -
|
| -
|
| -class Float(Argument):
|
| - """
|
| - Encode floating-point values on the wire as their repr.
|
| - """
|
| - fromString = float
|
| - toString = repr
|
| -
|
| -
|
| -
|
| -class Boolean(Argument):
|
| - """
|
| - Encode True or False as "True" or "False" on the wire.
|
| - """
|
| - def fromString(self, inString):
|
| - if inString == 'True':
|
| - return True
|
| - elif inString == 'False':
|
| - return False
|
| - else:
|
| - raise TypeError("Bad boolean value: %r" % (inString,))
|
| -
|
| -
|
| - def toString(self, inObject):
|
| - if inObject:
|
| - return 'True'
|
| - else:
|
| - return 'False'
|
| -
|
| -
|
| -
|
| -class Unicode(String):
|
| - """
|
| - Encode a unicode string on the wire as UTF-8.
|
| - """
|
| -
|
| - def toString(self, inObject):
|
| - # assert isinstance(inObject, unicode)
|
| - return String.toString(self, inObject.encode('utf-8'))
|
| -
|
| -
|
| - def fromString(self, inString):
|
| - # assert isinstance(inString, str)
|
| - return String.fromString(self, inString).decode('utf-8')
|
| -
|
| -
|
| -
|
| -class Path(Unicode):
|
| - """
|
| - Encode and decode L{filepath.FilePath} instances as paths on the wire.
|
| -
|
| - This is really intended for use with subprocess communication tools:
|
| - exchanging pathnames on different machines over a network is not generally
|
| - meaningful, but neither is it disallowed; you can use this to communicate
|
| - about NFS paths, for example.
|
| - """
|
| - def fromString(self, inString):
|
| - return filepath.FilePath(Unicode.fromString(self, inString))
|
| -
|
| -
|
| - def toString(self, inObject):
|
| - return Unicode.toString(self, inObject.path)
|
| -
|
| -
|
| -
|
| -class AmpList(Argument):
|
| - """
|
| - Convert a list of dictionaries into a list of AMP boxes on the wire.
|
| -
|
| - For example, if you want to pass::
|
| -
|
| - [{'a': 7, 'b': u'hello'}, {'a': 9, 'b': u'goodbye'}]
|
| -
|
| - You might use an AmpList like this in your arguments or response list::
|
| -
|
| - AmpList([('a', Integer()),
|
| - ('b', Unicode())])
|
| - """
|
| - def __init__(self, subargs):
|
| - """
|
| - Create an AmpList.
|
| -
|
| - @param subargs: a list of 2-tuples of ('name', argument) describing the
|
| - schema of the dictionaries in the sequence of amp boxes.
|
| - """
|
| - self.subargs = subargs
|
| -
|
| -
|
| - def fromStringProto(self, inString, proto):
|
| - boxes = parseString(inString)
|
| - values = [_stringsToObjects(box, self.subargs, proto)
|
| - for box in boxes]
|
| - return values
|
| -
|
| -
|
| - def toStringProto(self, inObject, proto):
|
| - return ''.join([_objectsToStrings(
|
| - objects, self.subargs, Box(), proto
|
| - ).serialize() for objects in inObject])
|
| -
|
| -class Command:
|
| - """
|
| - Subclass me to specify an AMP Command.
|
| -
|
| - @cvar arguments: A list of 2-tuples of (name, Argument-subclass-instance),
|
| - specifying the names and values of the parameters which are required for
|
| - this command.
|
| -
|
| - @cvar response: A list like L{arguments}, but instead used for the return
|
| - value.
|
| -
|
| - @cvar errors: A mapping of subclasses of L{Exception} to wire-protocol tags
|
| - for errors represented as L{str}s. Responders which raise keys from this
|
| - dictionary will have the error translated to the corresponding tag on the
|
| - wire. Invokers which receive Deferreds from invoking this command with
|
| - L{AMP.callRemote} will potentially receive Failures with keys from this
|
| - mapping as their value. This mapping is inherited; if you declare a
|
| - command which handles C{FooError} as 'FOO_ERROR', then subclass it and
|
| - specify C{BarError} as 'BAR_ERROR', responders to the subclass may raise
|
| - either C{FooError} or C{BarError}, and invokers must be able to deal with
|
| - either of those exceptions.
|
| -
|
| - @cvar fatalErrors: like 'errors', but errors in this list will always
|
| - terminate the connection, despite being of a recognizable error type.
|
| -
|
| - @cvar commandType: The type of Box used to issue commands; useful only for
|
| - protocol-modifying behavior like startTLS or protocol switching. Defaults
|
| - to a plain vanilla L{Box}.
|
| -
|
| - @cvar responseType: The type of Box used to respond to this command; only
|
| - useful for protocol-modifying behavior like startTLS or protocol switching.
|
| - Defaults to a plain vanilla L{Box}.
|
| -
|
| - @ivar requiresAnswer: a boolean; defaults to True. Set it to False on your
|
| - subclass if you want callRemote to return None. Note: this is a hint only
|
| - to the client side of the protocol. The return-type of a command responder
|
| - method must always be a dictionary adhering to the contract specified by
|
| - L{response}, because clients are always free to request a response if they
|
| - want one.
|
| - """
|
| -
|
| - class __metaclass__(type):
|
| - """
|
| - Metaclass hack to establish reverse-mappings for 'errors' and
|
| - 'fatalErrors' as class vars.
|
| - """
|
| - def __new__(cls, name, bases, attrs):
|
| - re = attrs['reverseErrors'] = {}
|
| - er = attrs['allErrors'] = {}
|
| - if 'commandName' not in attrs:
|
| - attrs['commandName'] = name
|
| - newtype = type.__new__(cls, name, bases, attrs)
|
| - errors = {}
|
| - fatalErrors = {}
|
| - accumulateClassDict(newtype, 'errors', errors)
|
| - accumulateClassDict(newtype, 'fatalErrors', fatalErrors)
|
| - for v, k in errors.iteritems():
|
| - re[k] = v
|
| - er[v] = k
|
| - for v, k in fatalErrors.iteritems():
|
| - re[k] = v
|
| - er[v] = k
|
| - return newtype
|
| -
|
| - arguments = []
|
| - response = []
|
| - extra = []
|
| - errors = {}
|
| - fatalErrors = {}
|
| -
|
| - commandType = Box
|
| - responseType = Box
|
| -
|
| - requiresAnswer = True
|
| -
|
| -
|
| - def __init__(self, **kw):
|
| - """
|
| - Create an instance of this command with specified values for its
|
| - parameters.
|
| -
|
| - @param kw: a dict containing an appropriate value for each name
|
| - specified in the L{arguments} attribute of my class.
|
| -
|
| - @raise InvalidSignature: if you forgot any required arguments.
|
| - """
|
| - self.structured = kw
|
| - givenArgs = kw.keys()
|
| - forgotten = []
|
| - for name, arg in self.arguments:
|
| - pythonName = _wireNameToPythonIdentifier(name)
|
| - if pythonName not in givenArgs and not arg.optional:
|
| - forgotten.append(pythonName)
|
| - if forgotten:
|
| - raise InvalidSignature("forgot %s for %s" % (
|
| - ', '.join(forgotten), self.commandName))
|
| - forgotten = []
|
| -
|
| -
|
| - def makeResponse(cls, objects, proto):
|
| - """
|
| - Serialize a mapping of arguments using this L{Command}'s
|
| - response schema.
|
| -
|
| - @param objects: a dict with keys matching the names specified in
|
| - self.response, having values of the types that the Argument objects in
|
| - self.response can format.
|
| -
|
| - @param proto: an L{AMP}.
|
| -
|
| - @return: an L{AmpBox}.
|
| - """
|
| - return _objectsToStrings(objects, cls.response, cls.responseType(),
|
| - proto)
|
| - makeResponse = classmethod(makeResponse)
|
| -
|
| -
|
| - def makeArguments(cls, objects, proto):
|
| - """
|
| - Serialize a mapping of arguments using this L{Command}'s
|
| - argument schema.
|
| -
|
| - @param objects: a dict with keys similar to the names specified in
|
| - self.arguments, having values of the types that the Argument objects in
|
| - self.arguments can parse.
|
| -
|
| - @param proto: an L{AMP}.
|
| -
|
| - @return: An instance of this L{Command}'s C{commandType}.
|
| - """
|
| - return _objectsToStrings(objects, cls.arguments, cls.commandType(),
|
| - proto)
|
| - makeArguments = classmethod(makeArguments)
|
| -
|
| -
|
| - def parseResponse(cls, box, protocol):
|
| - """
|
| - Parse a mapping of serialized arguments using this
|
| - L{Command}'s response schema.
|
| -
|
| - @param box: A mapping of response-argument names to the
|
| - serialized forms of those arguments.
|
| - @param protocol: The L{AMP} protocol.
|
| -
|
| - @return: A mapping of response-argument names to the parsed
|
| - forms.
|
| - """
|
| - return _stringsToObjects(box, cls.response, protocol)
|
| - parseResponse = classmethod(parseResponse)
|
| -
|
| -
|
| - def parseArguments(cls, box, protocol):
|
| - """
|
| - Parse a mapping of serialized arguments using this
|
| - L{Command}'s argument schema.
|
| -
|
| - @param box: A mapping of argument names to the seralized forms
|
| - of those arguments.
|
| - @param protocol: The L{AMP} protocol.
|
| -
|
| - @return: A mapping of argument names to the parsed forms.
|
| - """
|
| - return _stringsToObjects(box, cls.arguments, protocol)
|
| - parseArguments = classmethod(parseArguments)
|
| -
|
| -
|
| - def responder(cls, methodfunc):
|
| - """
|
| - Declare a method to be a responder for a particular command.
|
| -
|
| - This is a decorator.
|
| -
|
| - Use like so::
|
| -
|
| - class MyCommand(Command):
|
| - arguments = [('a', ...), ('b', ...)]
|
| -
|
| - class MyProto(AMP):
|
| - def myFunMethod(self, a, b):
|
| - ...
|
| - MyCommand.responder(myFunMethod)
|
| -
|
| - Notes: Although decorator syntax is not used within Twisted, this
|
| - function returns its argument and is therefore safe to use with
|
| - decorator syntax.
|
| -
|
| - This is not thread safe. Don't declare AMP subclasses in other
|
| - threads. Don't declare responders outside the scope of AMP subclasses;
|
| - the behavior is undefined.
|
| -
|
| - @param methodfunc: A function which will later become a method, which
|
| - has a keyword signature compatible with this command's L{argument} list
|
| - and returns a dictionary with a set of keys compatible with this
|
| - command's L{response} list.
|
| -
|
| - @return: the methodfunc parameter.
|
| - """
|
| - CommandLocator._currentClassCommands.append((cls, methodfunc))
|
| - return methodfunc
|
| - responder = classmethod(responder)
|
| -
|
| -
|
| - # Our only instance method
|
| - def _doCommand(self, proto):
|
| - """
|
| - Encode and send this Command to the given protocol.
|
| -
|
| - @param proto: an AMP, representing the connection to send to.
|
| -
|
| - @return: a Deferred which will fire or error appropriately when the
|
| - other side responds to the command (or error if the connection is lost
|
| - before it is responded to).
|
| - """
|
| -
|
| - def _massageError(error):
|
| - error.trap(RemoteAmpError)
|
| - rje = error.value
|
| - errorType = self.reverseErrors.get(rje.errorCode,
|
| - UnknownRemoteError)
|
| - return Failure(errorType(rje.description))
|
| -
|
| - d = proto._sendBoxCommand(self.commandName,
|
| - self.makeArguments(self.structured, proto),
|
| - self.requiresAnswer)
|
| -
|
| - if self.requiresAnswer:
|
| - d.addCallback(self.parseResponse, proto)
|
| - d.addErrback(_massageError)
|
| -
|
| - return d
|
| -
|
| -
|
| -
|
| -class _NoCertificate:
|
| - """
|
| - This is for peers which don't want to use a local certificate. Used by
|
| - AMP because AMP's internal language is all about certificates and this
|
| - duck-types in the appropriate place; this API isn't really stable though,
|
| - so it's not exposed anywhere public.
|
| -
|
| - For clients, it will use ephemeral DH keys, or whatever the default is for
|
| - certificate-less clients in OpenSSL. For servers, it will generate a
|
| - temporary self-signed certificate with garbage values in the DN and use
|
| - that.
|
| - """
|
| -
|
| - def __init__(self, client):
|
| - """
|
| - Create a _NoCertificate which either is or isn't for the client side of
|
| - the connection.
|
| -
|
| - @param client: True if we are a client and should truly have no
|
| - certificate and be anonymous, False if we are a server and actually
|
| - have to generate a temporary certificate.
|
| -
|
| - @type client: bool
|
| - """
|
| - self.client = client
|
| -
|
| -
|
| - def options(self, *authorities):
|
| - """
|
| - Behaves like L{twisted.internet.ssl.PrivateCertificate.options}().
|
| - """
|
| - if not self.client:
|
| - # do some crud with sslverify to generate a temporary self-signed
|
| - # certificate. This is SLOOOWWWWW so it is only in the absolute
|
| - # worst, most naive case.
|
| -
|
| - # We have to do this because OpenSSL will not let both the server
|
| - # and client be anonymous.
|
| - sharedDN = DN(CN='TEMPORARY CERTIFICATE')
|
| - key = KeyPair.generate()
|
| - cr = key.certificateRequest(sharedDN)
|
| - sscrd = key.signCertificateRequest(sharedDN, cr, lambda dn: True, 1)
|
| - cert = key.newCertificate(sscrd)
|
| - return cert.options(*authorities)
|
| - options = dict()
|
| - if authorities:
|
| - options.update(dict(verify=True,
|
| - requireCertificate=True,
|
| - caCerts=[auth.original for auth in authorities]))
|
| - occo = CertificateOptions(**options)
|
| - return occo
|
| -
|
| -
|
| -
|
| -class _TLSBox(AmpBox):
|
| - """
|
| - I am an AmpBox that, upon being sent, initiates a TLS connection.
|
| - """
|
| - __slots__ = []
|
| -
|
| - def _keyprop(k, default):
|
| - return property(lambda self: self.get(k, default))
|
| -
|
| -
|
| - # These properties are described in startTLS
|
| - certificate = _keyprop('tls_localCertificate', _NoCertificate(False))
|
| - verify = _keyprop('tls_verifyAuthorities', None)
|
| -
|
| - def _sendTo(self, proto):
|
| - """
|
| - Send my encoded value to the protocol, then initiate TLS.
|
| - """
|
| - ab = AmpBox(self)
|
| - for k in ['tls_localCertificate',
|
| - 'tls_verifyAuthorities']:
|
| - ab.pop(k, None)
|
| - ab._sendTo(proto)
|
| - proto._startTLS(self.certificate, self.verify)
|
| -
|
| -
|
| -
|
| -class _LocalArgument(String):
|
| - """
|
| - Local arguments are never actually relayed across the wire. This is just a
|
| - shim so that StartTLS can pretend to have some arguments: if arguments
|
| - acquire documentation properties, replace this with something nicer later.
|
| - """
|
| -
|
| - def fromBox(self, name, strings, objects, proto):
|
| - pass
|
| -
|
| -
|
| -
|
| -class StartTLS(Command):
|
| - """
|
| - Use, or subclass, me to implement a command that starts TLS.
|
| -
|
| - Callers of StartTLS may pass several special arguments, which affect the
|
| - TLS negotiation:
|
| -
|
| - - tls_localCertificate: This is a
|
| - twisted.internet.ssl.PrivateCertificate which will be used to secure
|
| - the side of the connection it is returned on.
|
| -
|
| - - tls_verifyAuthorities: This is a list of
|
| - twisted.internet.ssl.Certificate objects that will be used as the
|
| - certificate authorities to verify our peer's certificate.
|
| -
|
| - Each of those special parameters may also be present as a key in the
|
| - response dictionary.
|
| - """
|
| -
|
| - arguments = [("tls_localCertificate", _LocalArgument(optional=True)),
|
| - ("tls_verifyAuthorities", _LocalArgument(optional=True))]
|
| -
|
| - response = [("tls_localCertificate", _LocalArgument(optional=True)),
|
| - ("tls_verifyAuthorities", _LocalArgument(optional=True))]
|
| -
|
| - responseType = _TLSBox
|
| -
|
| - def __init__(self, **kw):
|
| - """
|
| - Create a StartTLS command. (This is private. Use AMP.callRemote.)
|
| -
|
| - @param tls_localCertificate: the PrivateCertificate object to use to
|
| - secure the connection. If it's None, or unspecified, an ephemeral DH
|
| - key is used instead.
|
| -
|
| - @param tls_verifyAuthorities: a list of Certificate objects which
|
| - represent root certificates to verify our peer with.
|
| - """
|
| - self.certificate = kw.pop('tls_localCertificate', _NoCertificate(True))
|
| - self.authorities = kw.pop('tls_verifyAuthorities', None)
|
| - Command.__init__(self, **kw)
|
| -
|
| -
|
| - def _doCommand(self, proto):
|
| - """
|
| - When a StartTLS command is sent, prepare to start TLS, but don't actually
|
| - do it; wait for the acknowledgement, then initiate the TLS handshake.
|
| - """
|
| - d = Command._doCommand(self, proto)
|
| - proto._prepareTLS(self.certificate, self.authorities)
|
| - # XXX before we get back to user code we are going to start TLS...
|
| - def actuallystart(response):
|
| - proto._startTLS(self.certificate, self.authorities)
|
| - return response
|
| - d.addCallback(actuallystart)
|
| - return d
|
| -
|
| -
|
| -
|
| -class ProtocolSwitchCommand(Command):
|
| - """
|
| - Use this command to switch from something Amp-derived to a different
|
| - protocol mid-connection. This can be useful to use amp as the
|
| - connection-startup negotiation phase. Since TLS is a different layer
|
| - entirely, you can use Amp to negotiate the security parameters of your
|
| - connection, then switch to a different protocol, and the connection will
|
| - remain secured.
|
| - """
|
| -
|
| - def __init__(self, _protoToSwitchToFactory, **kw):
|
| - """
|
| - Create a ProtocolSwitchCommand.
|
| -
|
| - @param _protoToSwitchToFactory: a ProtocolFactory which will generate
|
| - the Protocol to switch to.
|
| -
|
| - @param kw: Keyword arguments, encoded and handled normally as
|
| - L{Command} would.
|
| - """
|
| -
|
| - self.protoToSwitchToFactory = _protoToSwitchToFactory
|
| - super(ProtocolSwitchCommand, self).__init__(**kw)
|
| -
|
| -
|
| - def makeResponse(cls, innerProto, proto):
|
| - return _SwitchBox(innerProto)
|
| - makeResponse = classmethod(makeResponse)
|
| -
|
| -
|
| - def _doCommand(self, proto):
|
| - """
|
| - When we emit a ProtocolSwitchCommand, lock the protocol, but don't actually
|
| - switch to the new protocol unless an acknowledgement is received. If
|
| - an error is received, switch back.
|
| - """
|
| - d = super(ProtocolSwitchCommand, self)._doCommand(proto)
|
| - proto._lockForSwitch()
|
| - def switchNow(ign):
|
| - innerProto = self.protoToSwitchToFactory.buildProtocol(
|
| - proto.transport.getPeer())
|
| - proto._switchTo(innerProto, self.protoToSwitchToFactory)
|
| - return ign
|
| - def handle(ign):
|
| - proto._unlockFromSwitch()
|
| - self.protoToSwitchToFactory.clientConnectionFailed(
|
| - None, Failure(CONNECTION_LOST))
|
| - return ign
|
| - return d.addCallbacks(switchNow, handle)
|
| -
|
| -
|
| -
|
| -class BinaryBoxProtocol(StatefulStringProtocol, Int16StringReceiver):
|
| - """
|
| - A protocol for receving L{Box}es - key/value pairs - via length-prefixed
|
| - strings. A box is composed of:
|
| -
|
| - - any number of key-value pairs, described by:
|
| - - a 2-byte network-endian packed key length (of which the first
|
| - byte must be null, and the second must be non-null: i.e. the
|
| - value of the length must be 1-255)
|
| - - a key, comprised of that many bytes
|
| - - a 2-byte network-endian unsigned value length (up to the maximum
|
| - of 65535)
|
| - - a value, comprised of that many bytes
|
| - - 2 null bytes
|
| -
|
| - In other words, an even number of strings prefixed with packed unsigned
|
| - 16-bit integers, and then a 0-length string to indicate the end of the box.
|
| -
|
| - This protocol also implements 2 extra private bits of functionality related
|
| - to the byte boundaries between messages; it can start TLS between two given
|
| - boxes or switch to an entirely different protocol. However, due to some
|
| - tricky elements of the implementation, the public interface to this
|
| - functionality is L{ProtocolSwitchCommand} and L{StartTLS}.
|
| -
|
| - @ivar boxReceiver: an L{IBoxReceiver} provider, whose L{ampBoxReceived}
|
| - method will be invoked for each L{Box} that is received.
|
| - """
|
| -
|
| - implements(IBoxSender)
|
| -
|
| - _justStartedTLS = False
|
| - _startingTLSBuffer = None
|
| - _locked = False
|
| - _currentKey = None
|
| - _currentBox = None
|
| -
|
| - hostCertificate = None
|
| - noPeerCertificate = False # for tests
|
| - innerProtocol = None
|
| - innerProtocolClientFactory = None
|
| -
|
| - _sslVerifyProblems = ()
|
| - # ^ Later this will become a mutable list - we can't get the handle during
|
| - # connection shutdown thanks to the fact that Twisted destroys the socket
|
| - # on our transport before notifying us of a lost connection (which I guess
|
| - # is reasonable - the socket is dead by then) See a few lines below in
|
| - # startTLS for details. --glyph
|
| -
|
| -
|
| - def __init__(self, boxReceiver):
|
| - self.boxReceiver = boxReceiver
|
| -
|
| -
|
| - def _switchTo(self, newProto, clientFactory=None):
|
| - """
|
| - Switch this BinaryBoxProtocol's transport to a new protocol. You need
|
| - to do this 'simultaneously' on both ends of a connection; the easiest
|
| - way to do this is to use a subclass of ProtocolSwitchCommand.
|
| -
|
| - @param newProto: the new protocol instance to switch to.
|
| -
|
| - @param clientFactory: the ClientFactory to send the
|
| - L{clientConnectionLost} notification to.
|
| - """
|
| - # All the data that Int16Receiver has not yet dealt with belongs to our
|
| - # new protocol: luckily it's keeping that in a handy (although
|
| - # ostensibly internal) variable for us:
|
| - newProtoData = self.recvd
|
| - # We're quite possibly in the middle of a 'dataReceived' loop in
|
| - # Int16StringReceiver: let's make sure that the next iteration, the
|
| - # loop will break and not attempt to look at something that isn't a
|
| - # length prefix.
|
| - self.recvd = ''
|
| - # Finally, do the actual work of setting up the protocol and delivering
|
| - # its first chunk of data, if one is available.
|
| - self.innerProtocol = newProto
|
| - self.innerProtocolClientFactory = clientFactory
|
| - newProto.makeConnection(self.transport)
|
| - newProto.dataReceived(newProtoData)
|
| -
|
| -
|
| - def sendBox(self, box):
|
| - """
|
| - Send a amp.Box to my peer.
|
| -
|
| - Note: transport.write is never called outside of this method.
|
| -
|
| - @param box: an AmpBox.
|
| -
|
| - @raise ProtocolSwitched: if the protocol has previously been switched.
|
| -
|
| - @raise ConnectionLost: if the connection has previously been lost.
|
| - """
|
| - if self._locked:
|
| - raise ProtocolSwitched(
|
| - "This connection has switched: no AMP traffic allowed.")
|
| - if self.transport is None:
|
| - raise ConnectionLost()
|
| - if self._startingTLSBuffer is not None:
|
| - self._startingTLSBuffer.append(box)
|
| - else:
|
| - self.transport.write(box.serialize())
|
| -
|
| -
|
| - def makeConnection(self, transport):
|
| - """
|
| - Notify L{boxReceiver} that it is about to receive boxes from this
|
| - protocol by invoking L{startReceivingBoxes}.
|
| - """
|
| - self.boxReceiver.startReceivingBoxes(self)
|
| - Int16StringReceiver.makeConnection(self, transport)
|
| -
|
| -
|
| - def dataReceived(self, data):
|
| - """
|
| - Either parse incoming data as L{AmpBox}es or relay it to our nested
|
| - protocol.
|
| - """
|
| - if self._justStartedTLS:
|
| - self._justStartedTLS = False
|
| - # If we already have an inner protocol, then we don't deliver data to
|
| - # the protocol parser any more; we just hand it off.
|
| - if self.innerProtocol is not None:
|
| - self.innerProtocol.dataReceived(data)
|
| - return
|
| - return Int16StringReceiver.dataReceived(self, data)
|
| -
|
| -
|
| - def connectionLost(self, reason):
|
| - """
|
| - The connection was lost; notify any nested protocol.
|
| - """
|
| - if self.innerProtocol is not None:
|
| - self.innerProtocol.connectionLost(reason)
|
| - if self.innerProtocolClientFactory is not None:
|
| - self.innerProtocolClientFactory.clientConnectionLost(None, reason)
|
| - # XXX this may be a slight oversimplification, but I believe that if
|
| - # there are pending SSL errors, they _are_ the reason that the
|
| - # connection was lost. a totally correct implementation of this would
|
| - # set up a simple state machine to track whether any bytes were
|
| - # received after startTLS was called. --glyph
|
| - problems = self._sslVerifyProblems
|
| - if problems:
|
| - failReason = Failure(problems[0])
|
| - elif self._justStartedTLS:
|
| - # We just started TLS and haven't received any data. This means
|
| - # the other connection didn't like our cert (although they may not
|
| - # have told us why - later Twisted should make 'reason' into a TLS
|
| - # error.)
|
| - failReason = PeerVerifyError(
|
| - "Peer rejected our certificate for an unknown reason.")
|
| - else:
|
| - failReason = reason
|
| - self.boxReceiver.stopReceivingBoxes(failReason)
|
| -
|
| -
|
| -
|
| - def proto_init(self, string):
|
| - """
|
| - String received in the 'init' state.
|
| - """
|
| - self._currentBox = AmpBox()
|
| - return self.proto_key(string)
|
| -
|
| -
|
| - def proto_key(self, string):
|
| - """
|
| - String received in the 'key' state. If the key is empty, a complete
|
| - box has been received.
|
| - """
|
| - if string:
|
| - self._currentKey = string
|
| - return 'value'
|
| - else:
|
| - self.boxReceiver.ampBoxReceived(self._currentBox)
|
| - self._currentBox = None
|
| - return 'init'
|
| -
|
| -
|
| - def proto_value(self, string):
|
| - """
|
| - String received in the 'value' state.
|
| - """
|
| - self._currentBox[self._currentKey] = string
|
| - self._currentKey = None
|
| - return 'key'
|
| -
|
| -
|
| - def _lockForSwitch(self):
|
| - """
|
| - Lock this binary protocol so that no further boxes may be sent. This
|
| - is used when sending a request to switch underlying protocols. You
|
| - probably want to subclass ProtocolSwitchCommand rather than calling
|
| - this directly.
|
| - """
|
| - self._locked = True
|
| -
|
| -
|
| - def _unlockFromSwitch(self):
|
| - """
|
| - Unlock this locked binary protocol so that further boxes may be sent
|
| - again. This is used after an attempt to switch protocols has failed
|
| - for some reason.
|
| - """
|
| - if self.innerProtocol is not None:
|
| - raise ProtocolSwitched("Protocol already switched. Cannot unlock.")
|
| - self._locked = False
|
| -
|
| -
|
| - def _prepareTLS(self, certificate, verifyAuthorities):
|
| - """
|
| - Used by StartTLSCommand to put us into the state where we don't
|
| - actually send things that get sent, instead we buffer them. see
|
| - L{_sendBox}.
|
| - """
|
| - self._startingTLSBuffer = []
|
| - if self.hostCertificate is not None:
|
| - raise OnlyOneTLS(
|
| - "Previously authenticated connection between %s and %s "
|
| - "is trying to re-establish as %s" % (
|
| - self.hostCertificate,
|
| - self.peerCertificate,
|
| - (certificate, verifyAuthorities)))
|
| -
|
| -
|
| - def _startTLS(self, certificate, verifyAuthorities):
|
| - """
|
| - Used by TLSBox to initiate the SSL handshake.
|
| -
|
| - @param certificate: a L{twisted.internet.ssl.PrivateCertificate} for
|
| - use locally.
|
| -
|
| - @param verifyAuthorities: L{twisted.internet.ssl.Certificate} instances
|
| - representing certificate authorities which will verify our peer.
|
| - """
|
| - self.hostCertificate = certificate
|
| - self._justStartedTLS = True
|
| - if verifyAuthorities is None:
|
| - verifyAuthorities = ()
|
| - self.transport.startTLS(certificate.options(*verifyAuthorities))
|
| - # Remember that mutable list that we were just talking about? Here
|
| - # it is. sslverify.py takes care of populating this list as
|
| - # necessary. --glyph
|
| - self._sslVerifyProblems = problemsFromTransport(self.transport)
|
| - stlsb = self._startingTLSBuffer
|
| - if stlsb is not None:
|
| - self._startingTLSBuffer = None
|
| - for box in stlsb:
|
| - self.sendBox(box)
|
| -
|
| -
|
| - def _getPeerCertificate(self):
|
| - if self.noPeerCertificate:
|
| - return None
|
| - return Certificate.peerFromTransport(self.transport)
|
| - peerCertificate = property(_getPeerCertificate)
|
| -
|
| -
|
| - def unhandledError(self, failure):
|
| - """
|
| - The buck stops here. This error was completely unhandled, time to
|
| - terminate the connection.
|
| - """
|
| - log.msg("Amp server or network failure "
|
| - "unhandled by client application:")
|
| - log.err(failure)
|
| - log.msg(
|
| - "Dropping connection! "
|
| - "To avoid, add errbacks to ALL remote commands!")
|
| - if self.transport is not None:
|
| - self.transport.loseConnection()
|
| -
|
| -
|
| - def _defaultStartTLSResponder(self):
|
| - """
|
| - The default TLS responder doesn't specify any certificate or anything.
|
| -
|
| - From a security perspective, it's little better than a plain-text
|
| - connection - but it is still a *bit* better, so it's included for
|
| - convenience.
|
| -
|
| - You probably want to override this by providing your own StartTLS.responder.
|
| - """
|
| - return {}
|
| - StartTLS.responder(_defaultStartTLSResponder)
|
| -
|
| -
|
| -
|
| -class AMP(BinaryBoxProtocol, BoxDispatcher,
|
| - CommandLocator, SimpleStringLocator):
|
| - """
|
| - This protocol is an AMP connection. See the module docstring for protocol
|
| - details.
|
| - """
|
| -
|
| - _ampInitialized = False
|
| -
|
| - def __init__(self, boxReceiver=None, locator=None):
|
| - # For backwards compatibility. When AMP did not separate parsing logic
|
| - # (L{BinaryBoxProtocol}), request-response logic (L{BoxDispatcher}) and
|
| - # command routing (L{CommandLocator}), it did not have a constructor.
|
| - # Now it does, so old subclasses might have defined their own that did
|
| - # not upcall. If this flag isn't set, we'll call the constructor in
|
| - # makeConnection before anything actually happens.
|
| - self._ampInitialized = True
|
| - if boxReceiver is None:
|
| - boxReceiver = self
|
| - if locator is None:
|
| - locator = self
|
| - boxSender = self
|
| - BoxDispatcher.__init__(self, locator)
|
| - BinaryBoxProtocol.__init__(self, boxReceiver)
|
| -
|
| -
|
| - def locateResponder(self, name):
|
| - """
|
| - Unify the implementations of L{CommandLocator} and
|
| - L{SimpleStringLocator} to perform both kinds of dispatch, preferring
|
| - L{CommandLocator}.
|
| - """
|
| - firstResponder = CommandLocator.locateResponder(self, name)
|
| - if firstResponder is not None:
|
| - return firstResponder
|
| - secondResponder = SimpleStringLocator.locateResponder(self, name)
|
| - return secondResponder
|
| -
|
| -
|
| - def __repr__(self):
|
| - """
|
| - A verbose string representation which gives us information about this
|
| - AMP connection.
|
| - """
|
| - return '<%s %s at 0x%x>' % (
|
| - self.__class__.__name__,
|
| - self.innerProtocol, id(self))
|
| -
|
| -
|
| - def makeConnection(self, transport):
|
| - """
|
| - Emit a helpful log message when the connection is made.
|
| - """
|
| - if not self._ampInitialized:
|
| - # See comment in the constructor re: backward compatibility. I
|
| - # should probably emit a deprecation warning here.
|
| - AMP.__init__(self)
|
| - # Save these so we can emit a similar log message in L{connectionLost}.
|
| - self._transportPeer = transport.getPeer()
|
| - self._transportHost = transport.getHost()
|
| - log.msg("%s connection established (HOST:%s PEER:%s)" % (
|
| - self.__class__.__name__,
|
| - self._transportHost,
|
| - self._transportPeer))
|
| - BinaryBoxProtocol.makeConnection(self, transport)
|
| -
|
| -
|
| - def connectionLost(self, reason):
|
| - """
|
| - Emit a helpful log message when the connection is lost.
|
| - """
|
| - log.msg("%s connection lost (HOST:%s PEER:%s)" %
|
| - (self.__class__.__name__,
|
| - self._transportHost,
|
| - self._transportPeer))
|
| - BinaryBoxProtocol.connectionLost(self, reason)
|
| - self.transport = None
|
| -
|
| -
|
| -
|
| -class _ParserHelper:
|
| - """
|
| - A box receiver which records all boxes received.
|
| - """
|
| - def __init__(self):
|
| - self.boxes = []
|
| -
|
| -
|
| - def getPeer(self):
|
| - return 'string'
|
| -
|
| -
|
| - def getHost(self):
|
| - return 'string'
|
| -
|
| - disconnecting = False
|
| -
|
| -
|
| - def startReceivingBoxes(self, sender):
|
| - """
|
| - No initialization is required.
|
| - """
|
| -
|
| -
|
| - def ampBoxReceived(self, box):
|
| - self.boxes.append(box)
|
| -
|
| -
|
| - # Synchronous helpers
|
| - def parse(cls, fileObj):
|
| - """
|
| - Parse some amp data stored in a file.
|
| -
|
| - @param fileObj: a file-like object.
|
| -
|
| - @return: a list of AmpBoxes encoded in the given file.
|
| - """
|
| - parserHelper = cls()
|
| - bbp = BinaryBoxProtocol(boxReceiver=parserHelper)
|
| - bbp.makeConnection(parserHelper)
|
| - bbp.dataReceived(fileObj.read())
|
| - return parserHelper.boxes
|
| - parse = classmethod(parse)
|
| -
|
| -
|
| - def parseString(cls, data):
|
| - """
|
| - Parse some amp data stored in a string.
|
| -
|
| - @param data: a str holding some amp-encoded data.
|
| -
|
| - @return: a list of AmpBoxes encoded in the given string.
|
| - """
|
| - return cls.parse(StringIO(data))
|
| - parseString = classmethod(parseString)
|
| -
|
| -
|
| -
|
| -parse = _ParserHelper.parse
|
| -parseString = _ParserHelper.parseString
|
| -
|
| -def _stringsToObjects(strings, arglist, proto):
|
| - """
|
| - Convert an AmpBox to a dictionary of python objects, converting through a
|
| - given arglist.
|
| -
|
| - @param strings: an AmpBox (or dict of strings)
|
| -
|
| - @param arglist: a list of 2-tuples of strings and Argument objects, as
|
| - described in L{Command.arguments}.
|
| -
|
| - @param proto: an L{AMP} instance.
|
| -
|
| - @return: the converted dictionary mapping names to argument objects.
|
| - """
|
| - objects = {}
|
| - myStrings = strings.copy()
|
| - for argname, argparser in arglist:
|
| - argparser.fromBox(argname, myStrings, objects, proto)
|
| - return objects
|
| -
|
| -
|
| -
|
| -def _objectsToStrings(objects, arglist, strings, proto):
|
| - """
|
| - Convert a dictionary of python objects to an AmpBox, converting through a
|
| - given arglist.
|
| -
|
| - @param objects: a dict mapping names to python objects
|
| -
|
| - @param arglist: a list of 2-tuples of strings and Argument objects, as
|
| - described in L{Command.arguments}.
|
| -
|
| - @param strings: [OUT PARAMETER] An object providing the L{dict}
|
| - interface which will be populated with serialized data.
|
| -
|
| - @param proto: an L{AMP} instance.
|
| -
|
| - @return: The converted dictionary mapping names to encoded argument
|
| - strings (identical to C{strings}).
|
| - """
|
| - myObjects = {}
|
| - for (k, v) in objects.items():
|
| - myObjects[k] = v
|
| -
|
| - for argname, argparser in arglist:
|
| - argparser.toBox(argname, strings, myObjects, proto)
|
| - return strings
|
| -
|
| -
|
|
|