def test_renderIProtocol(self): """ If the protocol returned by C{lookupProtocol} isn't a C{WebSocketsProtocol}, L{WebSocketsResource} wraps it automatically with L{WebSocketsProtocolWrapper}. """ def lookupProtocol(names, otherRequest): return AccumulatingProtocol(), None self.resource = WebSocketsResource(lookupProtocol) request = DummyRequest(b"/") request.requestHeaders = Headers({ b"user-agent": [b"user-agent"], b"host": [b"host"], }) transport = StringTransportWithDisconnection() transport.protocol = Protocol() request.transport = transport self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertIsInstance(transport.protocol, WebSocketsProtocolWrapper) self.assertIsInstance(transport.protocol.wrappedProtocol, AccumulatingProtocol)
def setUp(self): super(WebSocketsResourceTest, self).setUp() class SavingEchoFactory(Factory): def buildProtocol(oself, addr): return self.echoProtocol factory = SavingEchoFactory() self.echoProtocol = WebSocketsProtocol(SavingEchoReceiver()) self.resource = WebSocketsResource(lookupProtocolForFactory(factory))
def test_renderProtocol(self): """ If protocols are specified via the C{Sec-WebSocket-Protocol} header, L{WebSocketsResource} passes them to its C{lookupProtocol} argument, which can decide which protocol to return, and which is accepted. """ def lookupProtocol(names, otherRequest): self.assertEqual([b"foo", b"bar"], names) self.assertIs(request, otherRequest) return self.echoProtocol, b"bar" self.resource = WebSocketsResource(lookupProtocol) request = DummyRequest(b"/") request.requestHeaders = Headers( { b"sec-websocket-protocol": [b"foo", b"bar"], b"user-agent": [b"user-agent"], b"host": [b"host"], } ) transport = StringTransportWithDisconnection() transport.protocol = Protocol() request.transport = transport self.update_headers( request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13", }, ) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertEqual( { b"Connection": [b"Upgrade"], b"Upgrade": [b"WebSocket"], b"Sec-Websocket-Protocol": [b"bar"], b"Sec-Websocket-Accept": [b"oYBv54i42V5dw6KnZqOFroecUTc="], }, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }, ) self.assertEqual([b""], request.written) self.assertEqual(101, request.responseCode)
def installApplication(self, application): """Install the WSGI application into the Twisted site. It's installed as a child with path "MAAS". This matches the default front-end configuration (i.e. Apache) so that there's no need to force script names. """ # Setup resources to process paths that twisted handles. metadata = Resource() metadata.putChild(b'status', StatusHandlerResource(self.status_worker)) maas = Resource() maas.putChild(b'metadata', metadata) maas.putChild(b'static', File(settings.STATIC_ROOT)) maas.putChild( b'ws', WebSocketsResource(lookupProtocolForFactory(self.websocket))) root = Resource() root.putChild(b'', Redirect(b"MAAS/")) root.putChild(b'MAAS', maas) # Setup the resources to process paths that django handles. underlay_maas = ResourceOverlay( WSGIResource(reactor, self.threadpool, application)) underlay_root = Resource() underlay_root.putChild(b'MAAS', underlay_maas) underlay_site = Site(underlay_root, logFormatter=reducedWebLogFormatter) underlay_site.requestFactory = CleanPathRequest # Setup the main resource as the twisted handler and the underlay # resource as the django handler. self.site.resource = root self.site.underlay = underlay_site
def installApplication(self, application): """Install the WSGI application into the Twisted site. It's installed as a child with path "MAAS". This matches the default front-end configuration (i.e. Apache) so that there's no need to force script names. """ # Setup resources to process paths that twisted handles. metadata = Resource() metadata.putChild(b"status", StatusHandlerResource(self.status_worker)) maas = Resource() maas.putChild(b"metadata", metadata) maas.putChild( b"ws", WebSocketsResource(lookupProtocolForFactory(self.websocket)) ) # /MAAS/r/{path} and /MAAS/l/{path} are all resolved by the new MAAS UI # react app, and legacy angularjs app respectively. # If any paths do not match then its routed to index.html in the new # UI code as it uses HTML 5 routing. maas.putChild(b"r", DefaultFallbackFile(settings.STATIC_ROOT)) maas.putChild(b"l", DefaultFallbackFile(settings.STATIC_ROOT)) # Redirect /MAAS to react app maas.putChild(b"", Redirect(b"/MAAS/r/")) # Setup static resources maas.putChild( b"assets", NoListingFile(os.path.join(settings.STATIC_ROOT, "assets")), ) # Setup static docs maas.putChild( b"docs", DocsFallbackFile(os.path.join(settings.STATIC_ROOT, "docs")), ) root = Resource() root.putChild(b"", Redirect(b"MAAS/")) root.putChild(b"MAAS", maas) # Setup the resources to process paths that django handles. underlay_maas = ResourceOverlay( WSGIResource(reactor, self.threadpool, application) ) underlay_root = Resource() underlay_root.putChild(b"MAAS", underlay_maas) underlay_site = Site( underlay_root, logFormatter=reducedWebLogFormatter ) underlay_site.requestFactory = CleanPathRequest # Setup the main resource as the twisted handler and the underlay # resource as the django handler. self.site.resource = root self.site.underlay = underlay_site
class WebSocketsResourceTest(MAASTestCase): """ Tests for L{WebSocketsResource}. """ def setUp(self): super(WebSocketsResourceTest, self).setUp() class SavingEchoFactory(Factory): def buildProtocol(oself, addr): return self.echoProtocol factory = SavingEchoFactory() self.echoProtocol = WebSocketsProtocol(SavingEchoReceiver()) self.resource = WebSocketsResource(lookupProtocolForFactory(factory)) def assertRequestFail(self, request): """ Helper method checking that the provided C{request} fails with a I{400} request code, without data or headers. @param request: The request to render. @type request: L{DummyRequest} """ result = self.resource.render(request) self.assertEqual(b"", result) self.assertEqual({}, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }) self.assertEqual([], request.written) self.assertEqual(400, request.responseCode) def update_headers(self, request, headers): for name, value in headers.items(): request.requestHeaders.addRawHeader(name=name, value=value) def test_getChildWithDefault(self): """ L{WebSocketsResource.getChildWithDefault} raises a C{RuntimeError} when called. """ self.assertRaises(RuntimeError, self.resource.getChildWithDefault, b"foo", DummyRequest(b"/")) def test_putChild(self): """ L{WebSocketsResource.putChild} raises C{RuntimeError} when called. """ self.assertRaises(RuntimeError, self.resource.putChild, b"foo", Resource()) def test_IResource(self): """ L{WebSocketsResource} implements L{IResource}. """ self.assertTrue(verifyObject(IResource, self.resource)) def test_render(self): """ When rendering a request, L{WebSocketsResource} uses the C{Sec-WebSocket-Key} header to generate a C{Sec-WebSocket-Accept} value. It creates a L{WebSocketsProtocol} instance connected to the protocol provided by the user factory. """ request = DummyRequest(b"/") request.requestHeaders = Headers({ b"user-agent": [b"user-agent"], b"host": [b"host"], }) transport = StringTransportWithDisconnection() transport.protocol = Protocol() request.transport = transport self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertEqual( { b"Connection": [b"Upgrade"], b"Upgrade": [b"WebSocket"], b"Sec-Websocket-Accept": [b"oYBv54i42V5dw6KnZqOFroecUTc="] }, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }) self.assertEqual([b""], request.written) self.assertEqual(101, request.responseCode) self.assertIdentical(None, request.transport) self.assertIsInstance(transport.protocol._receiver, SavingEchoReceiver) self.assertEqual(request.getHeader(b"cookie"), transport.cookies) self.assertEqual(request.uri, transport.uri) def test_renderProtocol(self): """ If protocols are specified via the C{Sec-WebSocket-Protocol} header, L{WebSocketsResource} passes them to its C{lookupProtocol} argument, which can decide which protocol to return, and which is accepted. """ def lookupProtocol(names, otherRequest): self.assertEqual([b"foo", b"bar"], names) self.assertIdentical(request, otherRequest) return self.echoProtocol, b"bar" self.resource = WebSocketsResource(lookupProtocol) request = DummyRequest(b"/") request.requestHeaders = Headers({ b"sec-websocket-protocol": [b"foo", b"bar"], b"user-agent": [b"user-agent"], b"host": [b"host"], }) transport = StringTransportWithDisconnection() transport.protocol = Protocol() request.transport = transport self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertEqual( { b"Connection": [b"Upgrade"], b"Upgrade": [b"WebSocket"], b"Sec-Websocket-Protocol": [b"bar"], b"Sec-Websocket-Accept": [b"oYBv54i42V5dw6KnZqOFroecUTc="] }, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }) self.assertEqual([b""], request.written) self.assertEqual(101, request.responseCode) def test_renderWrongUpgrade(self): """ If the C{Upgrade} header contains an invalid value, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"upgrade": b"wrong", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderNoUpgrade(self): """ If the C{Upgrade} header is not set, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderPOST(self): """ If the method is not C{GET}, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") request.method = b"POST" self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderWrongConnection(self): """ If the C{Connection} header contains an invalid value, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Wrong", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderNoConnection(self): """ If the C{Connection} header is not set, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"upgrade": b"Websocket", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderNoKey(self): """ If the C{Sec-WebSocket-Key} header is not set, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-version": b"13" }) self.assertRequestFail(request) def test_renderWrongVersion(self): """ If the value of the C{Sec-WebSocket-Version} is not 13, L{WebSocketsResource} returns a failed request. """ request = DummyRequest(b"/") self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"11" }) result = self.resource.render(request) self.assertEqual(b"", result) self.assertEqual( ['13'], request.responseHeaders.getRawHeaders("sec-websocket-version")) self.assertEqual([], request.written) self.assertEqual(400, request.responseCode) def test_renderNoProtocol(self): """ If the underlying factory doesn't return any protocol, L{WebSocketsResource} returns a failed request with a C{502} code. """ request = DummyRequest(b"/") request.requestHeaders = Headers({ b"user-agent": [b"user-agent"], b"host": [b"host"], }) request.transport = StringTransportWithDisconnection() self.echoProtocol = None self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(b"", result) self.assertEqual({}, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }) self.assertEqual([], request.written) self.assertEqual(502, request.responseCode) def test_renderSecureRequest(self): """ When the rendered request is over HTTPS, L{WebSocketsResource} wraps the protocol of the C{TLSMemoryBIOProtocol} instance. """ request = DummyRequest(b"/") request.requestHeaders = Headers({ b"user-agent": [b"user-agent"], b"host": [b"host"], }) transport = StringTransportWithDisconnection() secureProtocol = TLSMemoryBIOProtocol(Factory(), Protocol()) transport.protocol = secureProtocol request.transport = transport self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertEqual( { b"Connection": [b"Upgrade"], b"Upgrade": [b"WebSocket"], b"Sec-Websocket-Accept": [b"oYBv54i42V5dw6KnZqOFroecUTc="] }, { name: value for name, value in request.responseHeaders.getAllRawHeaders() }) self.assertEqual([b""], request.written) self.assertEqual(101, request.responseCode) self.assertIdentical(None, request.transport) self.assertIsInstance(transport.protocol.wrappedProtocol, WebSocketsProtocol) self.assertIsInstance(transport.protocol.wrappedProtocol._receiver, SavingEchoReceiver) def test_renderRealRequest(self): """ The request managed by L{WebSocketsResource.render} doesn't contain unnecessary HTTP headers like I{Content-Type}. """ channel = DummyChannel() channel.transport = StringTransportWithDisconnection() channel.transport.protocol = channel request = Request(channel, False) headers = { b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13", b"user-agent": b"user-agent", b"client": b"client", b"host": b"host" } for key, value in headers.items(): request.requestHeaders.setRawHeaders(key, [value]) request.method = b"GET" request.clientproto = b"HTTP/1.1" request.client = IPv6Address('TCP', 'fe80::1', '80') result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertEqual( [(b"Connection", [b"Upgrade"]), (b"Sec-Websocket-Accept", [b"oYBv54i42V5dw6KnZqOFroecUTc="]), (b"Upgrade", [b"WebSocket"])], sorted(request.responseHeaders.getAllRawHeaders())) self.assertThat( channel.transport.value(), StartsWith(b"HTTP/1.1 101 Switching Protocols\r\n" b"Transfer-Encoding: chunked\r\n")) self.assertEqual(101, request.code) self.assertIdentical(None, request.transport) def test_renderIProtocol(self): """ If the protocol returned by C{lookupProtocol} isn't a C{WebSocketsProtocol}, L{WebSocketsResource} wraps it automatically with L{WebSocketsProtocolWrapper}. """ def lookupProtocol(names, otherRequest): return AccumulatingProtocol(), None self.resource = WebSocketsResource(lookupProtocol) request = DummyRequest(b"/") request.requestHeaders = Headers({ b"user-agent": [b"user-agent"], b"host": [b"host"], }) transport = StringTransportWithDisconnection() transport.protocol = Protocol() request.transport = transport self.update_headers(request, headers={ b"upgrade": b"Websocket", b"connection": b"Upgrade", b"sec-websocket-key": b"secure", b"sec-websocket-version": b"13" }) result = self.resource.render(request) self.assertEqual(NOT_DONE_YET, result) self.assertIsInstance(transport.protocol, WebSocketsProtocolWrapper) self.assertIsInstance(transport.protocol.wrappedProtocol, AccumulatingProtocol)