Antonio Dourado
Antonio Dourado

Reputation: 379

twisted autobahn websocket being initialized twice with wss

I have some websocket protocols implemented with Twisted, they work fine when I connect using "ws", but when I enable secure websockets, the __init__ method is called twice. To more specific, it is called once, then the connection apparently fails, with connectionLost being called, then it the __init__ is called again, and this time the connection stays open.

The code bellow exemplifies it. When I connect with wss, the log line in the __init__ of the websocket protocol is called twice, but this doesn't happen with plain websockets.

import socket from datetime import datetime from twisted.internet import reactor

from twisted.internet.ssl import DefaultOpenSSLContextFactory

from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory, listenWS
import txaio

txaio.use_twisted()


CERT_KEY = "certificate.key"
CERT_PATH = "certificate.crt"


def log(msg):
    print("{}: {}".format(str(datetime.now()), msg))


class TestProtocol(WebSocketServerProtocol):
    def __init__(self):
        super(TestProtocol, self).__init__()
        log("Test protocol init")

    def connectionLost(self, reason):
        WebSocketServerProtocol.connectionLost(self, reason)
        log("Connection closed: Reason is {}".format(reason))


class TestProtocolFactory(WebSocketServerFactory):
    protocol = TestProtocol


def init_websocket_protocol(factory_cls, port):
    try:
        key, crt = CERT_KEY, CERT_PATH
        context_factory = DefaultOpenSSLContextFactory(key, crt)
        connection_string = "wss://localhost:{}".format(str(port))
        factory = factory_cls(connection_string)
        listenWS(factory, contextFactory=context_factory)
        log("Port {} bound to test websocket server".format(str(port)))
    except socket.error as e:
        log("Server was unable to bind to a new port: ".format(str(e)))


def main():
    init_websocket_protocol(TestProtocolFactory, 9000)
    reactor.run()


if __name__ == '__main__':
    main()

Upvotes: 1

Views: 676

Answers (1)

meejah
meejah

Reputation: 316

The recommend API these days is to use endpoints. Also, twisted.internet.ssl.CertificateOptions is the preferred API for TLS connections. So with those changes your code above would look like this:

from datetime import datetime
from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory
from twisted.internet.ssl import CertificateOptions, PrivateCertificate, Certificate, KeyPair
from twisted.internet.endpoints import SSL4ServerEndpoint
from twisted.internet.task import react
from OpenSSL import crypto


CERT_KEY = "certificate.key"
CERT_PATH = "certificate.crt"


def log(msg):
    print("{}: {}".format(str(datetime.now()), msg))


class TestProtocol(WebSocketServerProtocol):
    def __init__(self):
        super(TestProtocol, self).__init__()
        log("Test protocol init")

    def connectionLost(self, reason):
        WebSocketServerProtocol.connectionLost(self, reason)
        log("Connection closed: Reason is {}".format(reason))



class TestProtocolFactory(WebSocketServerFactory):
    protocol = TestProtocol


def init_websocket_protocol(reactor, port):
    with open(CERT_KEY) as key_file, open(CERT_PATH) as cert_file:
        key = KeyPair.load(key_file.read(), crypto.FILETYPE_PEM).original
        cert = Certificate.loadPEM(cert_file.read()).original
    ctx = CertificateOptions(
        privateKey=key,
        certificate=cert,
    )
    return SSL4ServerEndpoint(reactor, port, ctx)


def main(reactor):
    ep = init_websocket_protocol(reactor, 9000)
    ep.listen(TestProtocolFactory())
    reactor.run()


if __name__ == '__main__':
    react(main)

When I run this code and point Firefox at it, it connects once. What does the browser-side code you're using look like?

Upvotes: 2

Related Questions