123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 |
- ###############################################################################
- #
- # The MIT License (MIT)
- #
- # Copyright (c) typedef int GmbH
- #
- # Permission is hereby granted, free of charge, to any person obtaining a copy
- # of this software and associated documentation files (the "Software"), to deal
- # in the Software without restriction, including without limitation the rights
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the Software is
- # furnished to do so, subject to the following conditions:
- #
- # The above copyright notice and this permission notice shall be included in
- # all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- # THE SOFTWARE.
- #
- ###############################################################################
-
- from base64 import b64encode, b64decode
- from typing import Optional
-
- from zope.interface import implementer
-
- import txaio
- txaio.use_twisted()
-
- import twisted.internet.protocol
- from twisted.internet import endpoints
- from twisted.internet.interfaces import ITransport
-
- from twisted.internet.error import ConnectionDone, ConnectionAborted, \
- ConnectionLost
- from twisted.internet.defer import Deferred
- from twisted.python.failure import Failure
- from twisted.internet.protocol import connectionDone
-
- from autobahn.util import public, hltype, hlval
- from autobahn.util import _is_tls_error, _maybe_tls_reason
- from autobahn.wamp import websocket
- from autobahn.wamp.types import TransportDetails
- from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, ConnectionDeny
- from autobahn.websocket import protocol
- from autobahn.websocket.interfaces import IWebSocketClientAgent
- from autobahn.twisted.util import create_transport_details, transport_channel_id
-
- from autobahn.websocket.compress import PerMessageDeflateOffer, \
- PerMessageDeflateOfferAccept, \
- PerMessageDeflateResponse, \
- PerMessageDeflateResponseAccept
-
-
- __all__ = (
- 'create_client_agent',
-
- 'WebSocketAdapterProtocol',
- 'WebSocketServerProtocol',
- 'WebSocketClientProtocol',
- 'WebSocketAdapterFactory',
- 'WebSocketServerFactory',
- 'WebSocketClientFactory',
-
- 'WrappingWebSocketAdapter',
- 'WrappingWebSocketServerProtocol',
- 'WrappingWebSocketClientProtocol',
- 'WrappingWebSocketServerFactory',
- 'WrappingWebSocketClientFactory',
-
- 'listenWS',
- 'connectWS',
-
- 'WampWebSocketServerProtocol',
- 'WampWebSocketServerFactory',
- 'WampWebSocketClientProtocol',
- 'WampWebSocketClientFactory',
- )
-
-
- def create_client_agent(reactor):
- """
- :returns: an instance implementing IWebSocketClientAgent
- """
- return _TwistedWebSocketClientAgent(reactor)
-
-
- def check_transport_config(transport_config):
- """
- raises a ValueError if `transport_config` is invalid
- """
- # XXX move me to "autobahn.websocket.util"
- if not isinstance(transport_config, str):
- raise ValueError(
- "'transport_config' must be a string, found {}".format(type(transport_config))
- )
- # XXX also accept everything Crossbar has in client transport configs? e.g like:
- # { "type": "websocket", "endpoint": {"type": "tcp", "host": "example.com", ...}}
- # XXX what about TLS options? (the above point would address that too)
- if not transport_config.startswith("ws://") and \
- not transport_config.startswith("wss://"):
- raise ValueError(
- "'transport_config' must start with 'ws://' or 'wss://'"
- )
- return None
-
-
- def check_client_options(options):
- """
- raises a ValueError if `options` is invalid
- """
- # XXX move me to "autobahn.websocket.util"
- if not isinstance(options, dict):
- raise ValueError(
- "'options' must be a dict"
- )
-
- # anything that WebSocketClientFactory accepts (at least)
- valid_keys = [
- "origin",
- "protocols",
- "useragent",
- "headers",
- "proxy",
- ]
- for actual_k in options.keys():
- if actual_k not in valid_keys:
- raise ValueError(
- "'options' may not contain '{}'".format(actual_k)
- )
-
-
- def _endpoint_from_config(reactor, factory, transport_config, options):
- # XXX might want some Crossbar code here? e.g. if we allow
- # "transport_config" to be a dict etc.
-
- # ... passing in the Factory is weird, but that's what parses all
- # the options and the URL currently
-
- if factory.isSecure:
- # create default client SSL context factory when none given
- from twisted.internet import ssl
- context_factory = ssl.optionsForClientTLS(factory.host)
-
- if factory.proxy is not None:
- factory.contextFactory = context_factory
- endpoint = endpoints.HostnameEndpoint(
- reactor,
- factory.proxy['host'],
- factory.proxy['port'],
- # timeout, option?
- )
- else:
- if factory.isSecure:
- from twisted.internet import ssl
- endpoint = endpoints.SSL4ClientEndpoint(
- reactor,
- factory.host,
- factory.port,
- context_factory,
- # timeout, option?
- )
- else:
- endpoint = endpoints.HostnameEndpoint( # XXX right? not TCP4ClientEndpoint
- reactor,
- factory.host,
- factory.port,
- # timeout, option?
- # attemptDelay, option?
- )
- return endpoint
-
-
- class _TwistedWebSocketClientAgent(IWebSocketClientAgent):
- """
- This agent creates connections using Twisted
- """
-
- def __init__(self, reactor):
- self._reactor = reactor
-
- def open(self, transport_config, options, protocol_class=None):
- """
- Open a new connection.
-
- :param dict transport_config: valid transport configuration
-
- :param dict options: additional options for the factory
-
- :param protocol_class: a callable that returns an instance of
- the protocol (WebSocketClientProtocol if the default None
- is passed in)
-
- :returns: a Deferred that fires with an instance of
- `protocol_class` (or WebSocketClientProtocol by default)
- that has successfully shaken hands (completed the
- handshake).
- """
- check_transport_config(transport_config)
- check_client_options(options)
-
- factory = WebSocketClientFactory(
- url=transport_config,
- reactor=self._reactor,
- **options
- )
- factory.protocol = WebSocketClientProtocol if protocol_class is None else protocol_class
- # XXX might want "contextFactory" for TLS ...? (or e.g. CA etc options?)
-
- endpoint = _endpoint_from_config(self._reactor, factory, transport_config, options)
-
- rtn_d = Deferred()
- proto_d = endpoint.connect(factory)
-
- def failed(f):
- rtn_d.errback(f)
-
- def got_proto(proto):
-
- def handshake_completed(arg):
- rtn_d.callback(proto)
- return arg
- proto.is_open.addCallbacks(handshake_completed, failed)
- return proto
- proto_d.addCallbacks(got_proto, failed)
- return rtn_d
-
-
- class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol):
- """
- Adapter class for Twisted WebSocket client and server protocols.
-
- Called from Twisted:
-
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionMade`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionLost`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.dataReceived`
-
- Called from Network-independent Code (WebSocket implementation):
-
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onOpen`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageBegin`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageFrameData`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageFrameEnd`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageEnd`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessage`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onPing`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onPong`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onClose`
-
- FIXME:
-
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._closeConnection`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._create_transport_details`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.registerProducer`
- * :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.unregisterProducer`
- """
-
- log = txaio.make_logger()
-
- peer: Optional[str] = None
- is_server: Optional[bool] = None
-
- def connectionMade(self):
- # Twisted networking framework entry point, called by Twisted
- # when the connection is established (either a client or a server)
-
- # determine preliminary transport details (what is know at this point)
- self._transport_details = create_transport_details(self.transport, self.is_server)
- self._transport_details.channel_framing = TransportDetails.CHANNEL_FRAMING_WEBSOCKET
-
- # backward compatibility
- self.peer = self._transport_details.peer
-
- # try to set "Nagle" option for TCP sockets
- try:
- self.transport.setTcpNoDelay(self.tcpNoDelay)
- except: # don't touch this! does not work: AttributeError, OSError
- # eg Unix Domain sockets throw Errno 22 on this
- pass
-
- # ok, now forward to the networking framework independent code for websocket
- self._connectionMade()
-
- # ok, done!
- self.log.debug('{func} connection established for peer="{peer}"',
- func=hltype(self.connectionMade),
- peer=hlval(self.peer))
-
- def connectionLost(self, reason: Failure = connectionDone):
- # Twisted networking framework entry point, called by Twisted
- # when the connection is lost (either a client or a server)
-
- was_clean = False
- if isinstance(reason.value, ConnectionDone):
- self.log.debug("Connection to/from {peer} was closed cleanly",
- peer=self.peer)
- was_clean = True
-
- elif _is_tls_error(reason.value):
- self.log.error(_maybe_tls_reason(reason.value))
-
- elif isinstance(reason.value, ConnectionAborted):
- self.log.debug("Connection to/from {peer} was aborted locally",
- peer=self.peer)
-
- elif isinstance(reason.value, ConnectionLost):
- message = str(reason.value)
- if hasattr(reason.value, 'message'):
- message = reason.value.message
- self.log.debug(
- "Connection to/from {peer} was lost in a non-clean fashion: {message}",
- peer=self.peer,
- message=message,
- )
-
- # at least: FileDescriptorOverrun, ConnectionFdescWentAway - but maybe others as well?
- else:
- self.log.debug("Connection to/from {peer} lost ({error_type}): {error})",
- peer=self.peer, error_type=type(reason.value), error=reason.value)
-
- # ok, now forward to the networking framework independent code for websocket
- self._connectionLost(reason)
-
- # ok, done!
- if was_clean:
- self.log.debug('{func} connection lost for peer="{peer}", closed cleanly',
- func=hltype(self.connectionLost),
- peer=hlval(self.peer))
- else:
- self.log.debug('{func} connection lost for peer="{peer}", closed with error {reason}',
- func=hltype(self.connectionLost),
- peer=hlval(self.peer),
- reason=reason)
-
- def dataReceived(self, data: bytes):
- self.log.debug('{func} received {data_len} bytes for peer="{peer}"',
- func=hltype(self.dataReceived),
- peer=hlval(self.peer),
- data_len=hlval(len(data)))
-
- # bytes received from Twisted, forward to the networking framework independent code for websocket
- self._dataReceived(data)
-
- def _closeConnection(self, abort=False):
- if abort and hasattr(self.transport, 'abortConnection'):
- self.transport.abortConnection()
- else:
- # e.g. ProcessProtocol lacks abortConnection()
- self.transport.loseConnection()
-
- def _onOpen(self):
- if self._transport_details.is_secure:
- # now that the TLS opening handshake is complete, the actual TLS channel ID
- # will be available. make sure to set it!
- channel_id = {
- 'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
- }
- self._transport_details.channel_id = channel_id
-
- self.onOpen()
-
- def _onMessageBegin(self, isBinary):
- self.onMessageBegin(isBinary)
-
- def _onMessageFrameBegin(self, length):
- self.onMessageFrameBegin(length)
-
- def _onMessageFrameData(self, payload):
- self.onMessageFrameData(payload)
-
- def _onMessageFrameEnd(self):
- self.onMessageFrameEnd()
-
- def _onMessageFrame(self, payload):
- self.onMessageFrame(payload)
-
- def _onMessageEnd(self):
- self.onMessageEnd()
-
- def _onMessage(self, payload, isBinary):
- self.onMessage(payload, isBinary)
-
- def _onPing(self, payload):
- self.onPing(payload)
-
- def _onPong(self, payload):
- self.onPong(payload)
-
- def _onClose(self, wasClean, code, reason):
- self.onClose(wasClean, code, reason)
-
- def registerProducer(self, producer, streaming):
- """
- Register a Twisted producer with this protocol.
-
- :param producer: A Twisted push or pull producer.
- :type producer: object
- :param streaming: Producer type.
- :type streaming: bool
- """
- self.transport.registerProducer(producer, streaming)
-
- def unregisterProducer(self):
- """
- Unregister Twisted producer with this protocol.
- """
- self.transport.unregisterProducer()
-
-
- @public
- class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
- """
- Base class for Twisted-based WebSocket server protocols.
-
- Implements :class:`autobahn.websocket.interfaces.IWebSocketChannel`.
- """
-
- log = txaio.make_logger()
- is_server = True
-
- # def onConnect(self, request: ConnectionRequest) -> Union[Optional[str], Tuple[Optional[str], Dict[str, str]]]:
- # pass
-
-
- @public
- class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
- """
- Base class for Twisted-based WebSocket client protocols.
-
- Implements :class:`autobahn.websocket.interfaces.IWebSocketChannel`.
- """
-
- log = txaio.make_logger()
- is_server = False
-
- def _onConnect(self, response: ConnectionResponse):
- self.log.debug('{meth}(response={response})', meth=hltype(self._onConnect), response=response)
- return self.onConnect(response)
-
- def startTLS(self):
- self.log.debug("Starting TLS upgrade")
- self.transport.startTLS(self.factory.contextFactory)
-
-
- class WebSocketAdapterFactory(object):
- """
- Adapter class for Twisted-based WebSocket client and server factories.
- """
-
-
- @public
- class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory, twisted.internet.protocol.ServerFactory):
- """
- Base class for Twisted-based WebSocket server factories.
-
- Implements :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
- """
-
- log = txaio.make_logger()
-
- def __init__(self, *args, **kwargs):
- """
-
- .. note::
- In addition to all arguments to the constructor of
- :meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
- you can supply a ``reactor`` keyword argument to specify the
- Twisted reactor to be used.
- """
- # lazy import to avoid reactor install upon module import
- reactor = kwargs.pop('reactor', None)
- if reactor is None:
- from twisted.internet import reactor
- self.reactor = reactor
-
- protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
-
-
- @public
- class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory, twisted.internet.protocol.ClientFactory):
- """
- Base class for Twisted-based WebSocket client factories.
-
- Implements :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
- """
-
- log = txaio.make_logger()
-
- def __init__(self, *args, **kwargs):
- """
-
- .. note::
- In addition to all arguments to the constructor of
- :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
- you can supply a ``reactor`` keyword argument to specify the
- Twisted reactor to be used.
- """
- # lazy import to avoid reactor install upon module import
- reactor = kwargs.pop('reactor', None)
- if reactor is None:
- from twisted.internet import reactor
- self.reactor = reactor
-
- protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
- # we must up-call *before* we set up the contextFactory
- # because we need self.host etc to be set properly.
- if self.isSecure and self.proxy is not None:
- # if we have a proxy, then our factory will be used to
- # create the connection after CONNECT and if it's doing
- # TLS it needs a contextFactory
- from twisted.internet import ssl
- self.contextFactory = ssl.optionsForClientTLS(self.host)
- # NOTE: there's thus no way to send in our own
- # context-factory, nor any TLS options.
-
- # Possibly we should allow 'proxy' to contain an actual
- # IStreamClientEndpoint instance instead of configuration for
- # how to make one
-
-
- @implementer(ITransport)
- class WrappingWebSocketAdapter(object):
- """
- An adapter for stream-based transport over WebSocket.
-
- This follows `websockify <https://github.com/kanaka/websockify>`_
- and should be compatible with that.
-
- It uses WebSocket subprotocol negotiation and supports the
- following WebSocket subprotocols:
-
- - ``binary`` (or a compatible subprotocol)
- - ``base64``
-
- Octets are either transmitted as the payload of WebSocket binary
- messages when using the ``binary`` subprotocol (or an alternative
- binary compatible subprotocol), or encoded with Base64 and then
- transmitted as the payload of WebSocket text messages when using
- the ``base64`` subprotocol.
- """
-
- def onConnect(self, requestOrResponse):
- # Negotiate either the 'binary' or the 'base64' WebSocket subprotocol
- if isinstance(requestOrResponse, ConnectionRequest):
- request = requestOrResponse
- for p in request.protocols:
- if p in self.factory._subprotocols:
- self._binaryMode = (p != 'base64')
- return p
- raise ConnectionDeny(ConnectionDeny.NOT_ACCEPTABLE, 'this server only speaks {0} WebSocket subprotocols'.format(self.factory._subprotocols))
- elif isinstance(requestOrResponse, ConnectionResponse):
- response = requestOrResponse
- if response.protocol not in self.factory._subprotocols:
- self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, 'this client only speaks {0} WebSocket subprotocols'.format(self.factory._subprotocols))
- self._binaryMode = (response.protocol != 'base64')
- else:
- # should not arrive here
- raise Exception("logic error")
-
- def onOpen(self):
- self._proto.connectionMade()
-
- def onMessage(self, payload, isBinary):
- if isBinary != self._binaryMode:
- self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_UNSUPPORTED_DATA, 'message payload type does not match the negotiated subprotocol')
- else:
- if not isBinary:
- try:
- payload = b64decode(payload)
- except Exception as e:
- self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, 'message payload base64 decoding error: {0}'.format(e))
- self._proto.dataReceived(payload)
-
- # noinspection PyUnusedLocal
- def onClose(self, wasClean, code, reason):
- self._proto.connectionLost(None)
-
- def write(self, data):
- # part of ITransport
- assert(type(data) == bytes)
- if self._binaryMode:
- self.sendMessage(data, isBinary=True)
- else:
- data = b64encode(data)
- self.sendMessage(data, isBinary=False)
-
- def writeSequence(self, data):
- # part of ITransport
- for d in data:
- self.write(d)
-
- def loseConnection(self):
- # part of ITransport
- self.sendClose()
-
- def getPeer(self):
- # part of ITransport
- return self.transport.getPeer()
-
- def getHost(self):
- # part of ITransport
- return self.transport.getHost()
-
-
- class WrappingWebSocketServerProtocol(WrappingWebSocketAdapter, WebSocketServerProtocol):
- """
- Server protocol for stream-based transport over WebSocket.
- """
-
-
- class WrappingWebSocketClientProtocol(WrappingWebSocketAdapter, WebSocketClientProtocol):
- """
- Client protocol for stream-based transport over WebSocket.
- """
-
-
- class WrappingWebSocketServerFactory(WebSocketServerFactory):
- """
- Wrapping server factory for stream-based transport over WebSocket.
- """
-
- def __init__(self,
- factory,
- url,
- reactor=None,
- enableCompression=True,
- autoFragmentSize=0,
- subprotocol=None):
- """
-
- :param factory: Stream-based factory to be wrapped.
- :type factory: A subclass of ``twisted.internet.protocol.Factory``
- :param url: WebSocket URL of the server this server factory will work for.
- :type url: unicode
- """
- self._factory = factory
- self._subprotocols = ['binary', 'base64']
- if subprotocol:
- self._subprotocols.append(subprotocol)
-
- WebSocketServerFactory.__init__(self,
- url=url,
- reactor=reactor,
- protocols=self._subprotocols)
-
- # automatically fragment outgoing traffic into WebSocket frames
- # of this size
- self.setProtocolOptions(autoFragmentSize=autoFragmentSize)
-
- # play nice and perform WS closing handshake
- self.setProtocolOptions(failByDrop=False)
-
- if enableCompression:
- # Enable WebSocket extension "permessage-deflate".
-
- # Function to accept offers from the client ..
- def accept(offers):
- for offer in offers:
- if isinstance(offer, PerMessageDeflateOffer):
- return PerMessageDeflateOfferAccept(offer)
-
- self.setProtocolOptions(perMessageCompressionAccept=accept)
-
- def buildProtocol(self, addr):
- proto = WrappingWebSocketServerProtocol()
- proto.factory = self
- proto._proto = self._factory.buildProtocol(addr)
- proto._proto.transport = proto
- return proto
-
- def startFactory(self):
- self._factory.startFactory()
- WebSocketServerFactory.startFactory(self)
-
- def stopFactory(self):
- self._factory.stopFactory()
- WebSocketServerFactory.stopFactory(self)
-
-
- class WrappingWebSocketClientFactory(WebSocketClientFactory):
- """
- Wrapping client factory for stream-based transport over WebSocket.
- """
-
- def __init__(self,
- factory,
- url,
- reactor=None,
- enableCompression=True,
- autoFragmentSize=0,
- subprotocol=None):
- """
-
- :param factory: Stream-based factory to be wrapped.
- :type factory: A subclass of ``twisted.internet.protocol.Factory``
- :param url: WebSocket URL of the server this client factory will connect to.
- :type url: unicode
- """
- self._factory = factory
- self._subprotocols = ['binary', 'base64']
- if subprotocol:
- self._subprotocols.append(subprotocol)
-
- WebSocketClientFactory.__init__(self,
- url=url,
- reactor=reactor,
- protocols=self._subprotocols)
-
- # automatically fragment outgoing traffic into WebSocket frames
- # of this size
- self.setProtocolOptions(autoFragmentSize=autoFragmentSize)
-
- # play nice and perform WS closing handshake
- self.setProtocolOptions(failByDrop=False)
-
- if enableCompression:
- # Enable WebSocket extension "permessage-deflate".
-
- # The extensions offered to the server ..
- offers = [PerMessageDeflateOffer()]
- self.setProtocolOptions(perMessageCompressionOffers=offers)
-
- # Function to accept responses from the server ..
- def accept(response):
- if isinstance(response, PerMessageDeflateResponse):
- return PerMessageDeflateResponseAccept(response)
-
- self.setProtocolOptions(perMessageCompressionAccept=accept)
-
- def buildProtocol(self, addr):
- proto = WrappingWebSocketClientProtocol()
- proto.factory = self
- proto._proto = self._factory.buildProtocol(addr)
- proto._proto.transport = proto
- return proto
-
-
- @public
- def connectWS(factory, contextFactory=None, timeout=30, bindAddress=None):
- """
- Establish WebSocket connection to a server. The connection parameters like target
- host, port, resource and others are provided via the factory.
-
- :param factory: The WebSocket protocol factory to be used for creating client protocol instances.
- :type factory: An :class:`autobahn.websocket.WebSocketClientFactory` instance.
-
- :param contextFactory: SSL context factory, required for secure WebSocket connections ("wss").
- :type contextFactory: A `twisted.internet.ssl.ClientContextFactory <http://twistedmatrix.com/documents/current/api/twisted.internet.ssl.ClientContextFactory.html>`_ instance.
-
- :param timeout: Number of seconds to wait before assuming the connection has failed.
- :type timeout: int
-
- :param bindAddress: A (host, port) tuple of local address to bind to, or None.
- :type bindAddress: tuple
-
- :returns: The connector.
- :rtype: An object which implements `twisted.interface.IConnector <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IConnector.html>`_.
- """
- # lazy import to avoid reactor install upon module import
- if hasattr(factory, 'reactor'):
- reactor = factory.reactor
- else:
- from twisted.internet import reactor
-
- if factory.isSecure:
- if contextFactory is None:
- # create default client SSL context factory when none given
- from twisted.internet import ssl
- contextFactory = ssl.ClientContextFactory()
-
- if factory.proxy is not None:
- factory.contextFactory = contextFactory
- conn = reactor.connectTCP(factory.proxy['host'], factory.proxy['port'], factory, timeout, bindAddress)
- else:
- if factory.isSecure:
- conn = reactor.connectSSL(factory.host, factory.port, factory, contextFactory, timeout, bindAddress)
- else:
- conn = reactor.connectTCP(factory.host, factory.port, factory, timeout, bindAddress)
- return conn
-
-
- @public
- def listenWS(factory, contextFactory=None, backlog=50, interface=''):
- """
- Listen for incoming WebSocket connections from clients. The connection parameters like
- listening port and others are provided via the factory.
-
- :param factory: The WebSocket protocol factory to be used for creating server protocol instances.
- :type factory: An :class:`autobahn.websocket.WebSocketServerFactory` instance.
-
- :param contextFactory: SSL context factory, required for secure WebSocket connections ("wss").
- :type contextFactory: A twisted.internet.ssl.ContextFactory.
-
- :param backlog: Size of the listen queue.
- :type backlog: int
-
- :param interface: The interface (derived from hostname given) to bind to, defaults to '' (all).
- :type interface: str
-
- :returns: The listening port.
- :rtype: An object that implements `twisted.interface.IListeningPort <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IListeningPort.html>`_.
- """
- # lazy import to avoid reactor install upon module import
- if hasattr(factory, 'reactor'):
- reactor = factory.reactor
- else:
- from twisted.internet import reactor
-
- if factory.isSecure:
- if contextFactory is None:
- raise Exception("Secure WebSocket listen requested, but no SSL context factory given")
- listener = reactor.listenSSL(factory.port, factory, contextFactory, backlog, interface)
- else:
- listener = reactor.listenTCP(factory.port, factory, backlog, interface)
- return listener
-
-
- @public
- class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
- """
- Twisted-based WAMP-over-WebSocket server protocol.
-
- Implements:
-
- * :class:`autobahn.wamp.interfaces.ITransport`
- """
-
-
- @public
- class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
- """
- Twisted-based WAMP-over-WebSocket server protocol factory.
- """
-
- protocol = WampWebSocketServerProtocol
-
- def __init__(self, factory, *args, **kwargs):
- """
-
- :param factory: A callable that produces instances that implement
- :class:`autobahn.wamp.interfaces.ITransportHandler`
- :type factory: callable
-
- :param serializers: A list of WAMP serializers to use (or ``None``
- for all available serializers).
- :type serializers: list of objects implementing
- :class:`autobahn.wamp.interfaces.ISerializer`
- """
-
- serializers = kwargs.pop('serializers', None)
-
- websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
-
- kwargs['protocols'] = self._protocols
-
- # noinspection PyCallByClass
- WebSocketServerFactory.__init__(self, *args, **kwargs)
-
-
- @public
- class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
- """
- Twisted-based WAMP-over-WebSocket client protocol.
-
- Implements:
-
- * :class:`autobahn.wamp.interfaces.ITransport`
- """
-
-
- @public
- class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
- """
- Twisted-based WAMP-over-WebSocket client protocol factory.
- """
-
- protocol = WampWebSocketClientProtocol
-
- def __init__(self, factory, *args, **kwargs):
- """
-
- :param factory: A callable that produces instances that implement
- :class:`autobahn.wamp.interfaces.ITransportHandler`
- :type factory: callable
-
- :param serializer: The WAMP serializer to use (or ``None`` for
- "best" serializer, chosen as the first serializer available from
- this list: CBOR, MessagePack, UBJSON, JSON).
- :type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
- """
-
- serializers = kwargs.pop('serializers', None)
-
- websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
-
- kwargs['protocols'] = self._protocols
-
- WebSocketClientFactory.__init__(self, *args, **kwargs)
-
- # Reduce the factory logs noise
- self.noisy = False
|