Exemplo n.º 1
0
class TestHTTPRequest(DaphneTestCase):
    """
    Tests the HTTP request handling.
    """
    def assert_valid_http_scope(self,
                                scope,
                                method,
                                path,
                                params=None,
                                headers=None,
                                scheme=None):
        """
        Checks that the passed scope is a valid ASGI HTTP scope regarding types
        and some urlencoding things.
        """
        # Check overall keys
        self.assert_key_sets(
            required_keys={
                "type",
                "http_version",
                "method",
                "path",
                "raw_path",
                "query_string",
                "headers",
            },
            optional_keys={"scheme", "root_path", "client", "server"},
            actual_keys=scope.keys(),
        )
        # Check that it is the right type
        self.assertEqual(scope["type"], "http")
        # Method (uppercased unicode string)
        self.assertIsInstance(scope["method"], str)
        self.assertEqual(scope["method"], method.upper())
        # Path
        self.assert_valid_path(scope["path"])
        # HTTP version
        self.assertIn(scope["http_version"], ["1.0", "1.1", "1.2"])
        # Scheme
        self.assertIn(scope["scheme"], ["http", "https"])
        if scheme:
            self.assertEqual(scheme, scope["scheme"])
        # Query string (byte string and still url encoded)
        query_string = scope["query_string"]
        self.assertIsInstance(query_string, bytes)
        if params:
            self.assertEqual(query_string,
                             parse.urlencode(params or []).encode("ascii"))
        # Ordering of header names is not important, but the order of values for a header
        # name is. To assert whether that order is kept, we transform both the request
        # headers and the channel message headers into a dictionary
        # {name: [value1, value2, ...]} and check if they're equal.
        transformed_scope_headers = collections.defaultdict(list)
        for name, value in scope["headers"]:
            transformed_scope_headers[name].append(value)
        transformed_request_headers = collections.defaultdict(list)
        for name, value in headers or []:
            expected_name = name.lower().strip()
            expected_value = value.strip()
            transformed_request_headers[expected_name].append(expected_value)
        for name, value in transformed_request_headers.items():
            self.assertIn(name, transformed_scope_headers)
            self.assertEqual(value, transformed_scope_headers[name])
        # Root path
        self.assertIsInstance(scope.get("root_path", ""), str)
        # Client and server addresses
        client = scope.get("client")
        if client is not None:
            self.assert_valid_address_and_port(client)
        server = scope.get("server")
        if server is not None:
            self.assert_valid_address_and_port(server)

    def assert_valid_http_request_message(self, message, body=None):
        """
        Asserts that a message is a valid http.request message
        """
        # Check overall keys
        self.assert_key_sets(
            required_keys={"type"},
            optional_keys={"body", "more_body"},
            actual_keys=message.keys(),
        )
        # Check that it is the right type
        self.assertEqual(message["type"], "http.request")
        # If there's a body present, check its type
        self.assertIsInstance(message.get("body", b""), bytes)
        if body is not None:
            self.assertEqual(body, message.get("body", b""))

    def test_minimal_request(self):
        """
        Smallest viable example. Mostly verifies that our request building works.
        """
        scope, messages = self.run_daphne_request("GET", "/")
        self.assert_valid_http_scope(scope, "GET", "/")
        self.assert_valid_http_request_message(messages[0], body=b"")

    @given(
        request_path=http_strategies.http_path(),
        request_params=http_strategies.query_params(),
    )
    @settings(max_examples=5, deadline=5000)
    def test_get_request(self, request_path, request_params):
        """
        Tests a typical HTTP GET request, with a path and query parameters
        """
        scope, messages = self.run_daphne_request("GET",
                                                  request_path,
                                                  params=request_params)
        self.assert_valid_http_scope(scope,
                                     "GET",
                                     request_path,
                                     params=request_params)
        self.assert_valid_http_request_message(messages[0], body=b"")

    @given(
        request_path=http_strategies.http_path(),
        chunk_size=integers(min_value=1),
    )
    @settings(max_examples=5, deadline=5000)
    def test_request_body_chunking(self, request_path, chunk_size):
        """
        Tests request body chunking logic.
        """
        body = b"The quick brown fox jumps over the lazy dog"
        _, messages = self.run_daphne_request(
            "POST",
            request_path,
            body=body,
            request_buffer_size=chunk_size,
        )

        # Avoid running those asserts when there's a single "http.disconnect"
        if len(messages) > 1:
            assert messages[0]["body"].decode() == body.decode()[:chunk_size]
            assert not messages[-2]["more_body"]
            assert messages[-1] == {"type": "http.disconnect"}

    @given(
        request_path=http_strategies.http_path(),
        request_body=http_strategies.http_body(),
    )
    @settings(max_examples=5, deadline=5000)
    def test_post_request(self, request_path, request_body):
        """
        Tests a typical HTTP POST request, with a path and body.
        """
        scope, messages = self.run_daphne_request("POST",
                                                  request_path,
                                                  body=request_body)
        self.assert_valid_http_scope(scope, "POST", request_path)
        self.assert_valid_http_request_message(messages[0], body=request_body)

    def test_raw_path(self):
        """
        Tests that /foo%2Fbar produces raw_path and a decoded path
        """
        scope, _ = self.run_daphne_request("GET", "/foo%2Fbar")
        self.assertEqual(scope["path"], "/foo/bar")
        self.assertEqual(scope["raw_path"], b"/foo%2Fbar")

    @given(request_headers=http_strategies.headers())
    @settings(max_examples=5, deadline=5000)
    def test_headers(self, request_headers):
        """
        Tests that HTTP header fields are handled as specified
        """
        request_path = parse.quote("/te st-à/")
        scope, messages = self.run_daphne_request("OPTIONS",
                                                  request_path,
                                                  headers=request_headers)
        self.assert_valid_http_scope(scope,
                                     "OPTIONS",
                                     request_path,
                                     headers=request_headers)
        self.assert_valid_http_request_message(messages[0], body=b"")

    @given(request_headers=http_strategies.headers())
    @settings(max_examples=5, deadline=5000)
    def test_duplicate_headers(self, request_headers):
        """
        Tests that duplicate header values are preserved
        """
        # Make sure there's duplicate headers
        assume(len(request_headers) >= 2)
        header_name = request_headers[0][0]
        duplicated_headers = [(header_name, header[1])
                              for header in request_headers]
        # Run the request
        request_path = parse.quote("/te st-à/")
        scope, messages = self.run_daphne_request("OPTIONS",
                                                  request_path,
                                                  headers=duplicated_headers)
        self.assert_valid_http_scope(scope,
                                     "OPTIONS",
                                     request_path,
                                     headers=duplicated_headers)
        self.assert_valid_http_request_message(messages[0], body=b"")

    @given(
        request_method=http_strategies.http_method(),
        request_path=http_strategies.http_path(),
        request_params=http_strategies.query_params(),
        request_headers=http_strategies.headers(),
        request_body=http_strategies.http_body(),
    )
    @settings(max_examples=2, deadline=5000)
    def test_kitchen_sink(
        self,
        request_method,
        request_path,
        request_params,
        request_headers,
        request_body,
    ):
        """
        Throw everything at Daphne that we dare. The idea is that if a combination
        of method/path/headers/body would break the spec, hypothesis will eventually find it.
        """
        scope, messages = self.run_daphne_request(
            request_method,
            request_path,
            params=request_params,
            headers=request_headers,
            body=request_body,
        )
        self.assert_valid_http_scope(
            scope,
            request_method,
            request_path,
            params=request_params,
            headers=request_headers,
        )
        self.assert_valid_http_request_message(messages[0], body=request_body)

    def test_headers_are_lowercased_and_stripped(self):
        """
        Make sure headers are normalized as the spec says they are.
        """
        headers = [(b"MYCUSTOMHEADER", b"   foobar    ")]
        scope, messages = self.run_daphne_request("GET", "/", headers=headers)
        self.assert_valid_http_scope(scope, "GET", "/", headers=headers)
        self.assert_valid_http_request_message(messages[0], body=b"")
        # Note that Daphne returns a list of tuples here, which is fine, because the spec
        # asks to treat them interchangeably.
        assert [list(x)
                for x in scope["headers"]] == [[b"mycustomheader", b"foobar"]]

    @given(daphne_path=http_strategies.http_path())
    @settings(max_examples=5, deadline=5000)
    def test_root_path_header(self, daphne_path):
        """
        Tests root_path handling.
        """
        # Daphne-Root-Path must be URL encoded when submitting as HTTP header field
        headers = [("Daphne-Root-Path",
                    parse.quote(daphne_path.encode("utf8")))]
        scope, messages = self.run_daphne_request("GET", "/", headers=headers)
        # Daphne-Root-Path is not included in the returned 'headers' section. So we expect
        # empty headers.
        self.assert_valid_http_scope(scope, "GET", "/", headers=[])
        self.assert_valid_http_request_message(messages[0], body=b"")
        # And what we're looking for, root_path being set.
        assert scope["root_path"] == daphne_path

    def test_x_forwarded_for_ignored(self):
        """
        Make sure that, by default, X-Forwarded-For is ignored.
        """
        headers = [[b"X-Forwarded-For", b"10.1.2.3"],
                   [b"X-Forwarded-Port", b"80"]]
        scope, messages = self.run_daphne_request("GET", "/", headers=headers)
        self.assert_valid_http_scope(scope, "GET", "/", headers=headers)
        self.assert_valid_http_request_message(messages[0], body=b"")
        # It should NOT appear in the client scope item
        self.assertNotEqual(scope["client"], ["10.1.2.3", 80])

    def test_x_forwarded_for_parsed(self):
        """
        When X-Forwarded-For is enabled, make sure it is respected.
        """
        headers = [[b"X-Forwarded-For", b"10.1.2.3"],
                   [b"X-Forwarded-Port", b"80"]]
        scope, messages = self.run_daphne_request("GET",
                                                  "/",
                                                  headers=headers,
                                                  xff=True)
        self.assert_valid_http_scope(scope, "GET", "/", headers=headers)
        self.assert_valid_http_request_message(messages[0], body=b"")
        # It should now appear in the client scope item
        self.assertEqual(scope["client"], ["10.1.2.3", 80])

    def test_x_forwarded_for_no_port(self):
        """
        When X-Forwarded-For is enabled but only the host is passed, make sure
        that at least makes it through.
        """
        headers = [[b"X-Forwarded-For", b"10.1.2.3"]]
        scope, messages = self.run_daphne_request("GET",
                                                  "/",
                                                  headers=headers,
                                                  xff=True)
        self.assert_valid_http_scope(scope, "GET", "/", headers=headers)
        self.assert_valid_http_request_message(messages[0], body=b"")
        # It should now appear in the client scope item
        self.assertEqual(scope["client"], ["10.1.2.3", 0])

    def test_bad_requests(self):
        """
        Tests that requests with invalid (non-ASCII) characters fail.
        """
        # Bad path
        response = self.run_daphne_raw(
            b"GET /\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n")
        self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request"))
        # Bad querystring
        response = self.run_daphne_raw(
            b"GET /?\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n")
        self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request"))
Exemplo n.º 2
0
class TestWebsocket(DaphneTestCase):
    """
    Tests WebSocket handshake, send and receive.
    """

    def assert_valid_websocket_scope(
        self, scope, path="/", params=None, headers=None, scheme=None, subprotocols=None
    ):
        """
        Checks that the passed scope is a valid ASGI HTTP scope regarding types
        and some urlencoding things.
        """
        # Check overall keys
        self.assert_key_sets(
            required_keys={"type", "path", "query_string", "headers"},
            optional_keys={"scheme", "root_path", "client", "server", "subprotocols"},
            actual_keys=scope.keys(),
        )
        # Check that it is the right type
        self.assertEqual(scope["type"], "websocket")
        # Path
        self.assert_valid_path(scope["path"], path)
        # Scheme
        self.assertIn(scope.get("scheme", "ws"), ["ws", "wss"])
        if scheme:
            self.assertEqual(scheme, scope["scheme"])
        # Query string (byte string and still url encoded)
        query_string = scope["query_string"]
        self.assertIsInstance(query_string, bytes)
        if params:
            self.assertEqual(
                query_string, parse.urlencode(params or []).encode("ascii")
            )
        # Ordering of header names is not important, but the order of values for a header
        # name is. To assert whether that order is kept, we transform both the request
        # headers and the channel message headers into a dictionary
        # {name: [value1, value2, ...]} and check if they're equal.
        transformed_scope_headers = collections.defaultdict(list)
        for name, value in scope["headers"]:
            transformed_scope_headers.setdefault(name, [])
            # Make sure to split out any headers collapsed with commas
            for bit in value.split(b","):
                if bit.strip():
                    transformed_scope_headers[name].append(bit.strip())
        transformed_request_headers = collections.defaultdict(list)
        for name, value in headers or []:
            expected_name = name.lower().strip().encode("ascii")
            expected_value = value.strip().encode("ascii")
            # Make sure to split out any headers collapsed with commas
            transformed_request_headers.setdefault(expected_name, [])
            for bit in expected_value.split(b","):
                if bit.strip():
                    transformed_request_headers[expected_name].append(bit.strip())
        for name, value in transformed_request_headers.items():
            self.assertIn(name, transformed_scope_headers)
            self.assertEqual(value, transformed_scope_headers[name])
        # Root path
        self.assertIsInstance(scope.get("root_path", ""), str)
        # Client and server addresses
        client = scope.get("client")
        if client is not None:
            self.assert_valid_address_and_port(client)
        server = scope.get("server")
        if server is not None:
            self.assert_valid_address_and_port(server)
        # Subprotocols
        scope_subprotocols = scope.get("subprotocols", [])
        if scope_subprotocols:
            assert all(isinstance(x, str) for x in scope_subprotocols)
        if subprotocols:
            assert sorted(scope_subprotocols) == sorted(subprotocols)

    def assert_valid_websocket_connect_message(self, message):
        """
        Asserts that a message is a valid http.request message
        """
        # Check overall keys
        self.assert_key_sets(
            required_keys={"type"}, optional_keys=set(), actual_keys=message.keys()
        )
        # Check that it is the right type
        self.assertEqual(message["type"], "websocket.connect")

    def test_accept(self):
        """
        Tests we can open and accept a socket.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(test_app)
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope)
            self.assert_valid_websocket_connect_message(messages[0])

    def test_reject(self):
        """
        Tests we can reject a socket and it won't complete the handshake.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.close"}])
            with self.assertRaises(RuntimeError):
                self.websocket_handshake(test_app)

    def test_subprotocols(self):
        """
        Tests that we can ask for subprotocols and then select one.
        """
        subprotocols = ["proto1", "proto2"]
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages(
                [{"type": "websocket.accept", "subprotocol": "proto2"}]
            )
            _, subprotocol = self.websocket_handshake(
                test_app, subprotocols=subprotocols
            )
            # Validate the scope and messages we got
            assert subprotocol == "proto2"
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope, subprotocols=subprotocols)
            self.assert_valid_websocket_connect_message(messages[0])

    def test_xff(self):
        """
        Tests that X-Forwarded-For headers get parsed right
        """
        headers = [["X-Forwarded-For", "10.1.2.3"], ["X-Forwarded-Port", "80"]]
        with DaphneTestingInstance(xff=True) as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(test_app, headers=headers)
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope)
            self.assert_valid_websocket_connect_message(messages[0])
            assert scope["client"] == ["10.1.2.3", 80]

    @given(
        request_path=http_strategies.http_path(),
        request_params=http_strategies.query_params(),
        request_headers=http_strategies.headers(),
    )
    @settings(max_examples=5, deadline=2000)
    def test_http_bits(self, request_path, request_params, request_headers):
        """
        Tests that various HTTP-level bits (query string params, path, headers)
        carry over into the scope.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(
                test_app,
                path=request_path,
                params=request_params,
                headers=request_headers,
            )
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(
                scope, path=request_path, params=request_params, headers=request_headers
            )
            self.assert_valid_websocket_connect_message(messages[0])

    def test_text_frames(self):
        """
        Tests we can send and receive text frames.
        """
        with DaphneTestingInstance() as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Prep frame for it to send
            test_app.add_send_messages(
                [{"type": "websocket.send", "text": "here be dragons 🐉"}]
            )
            # Send it a frame
            self.websocket_send_frame(sock, "what is here? 🌍")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == "here be dragons 🐉"
            # Make sure it got our frame
            _, messages = test_app.get_received()
            assert messages[1] == {
                "type": "websocket.receive",
                "text": "what is here? 🌍",
            }

    def test_binary_frames(self):
        """
        Tests we can send and receive binary frames with things that are very
        much not valid UTF-8.
        """
        with DaphneTestingInstance() as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Prep frame for it to send
            test_app.add_send_messages(
                [{"type": "websocket.send", "bytes": b"here be \xe2 bytes"}]
            )
            # Send it a frame
            self.websocket_send_frame(sock, b"what is here? \xe2")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == b"here be \xe2 bytes"
            # Make sure it got our frame
            _, messages = test_app.get_received()
            assert messages[1] == {
                "type": "websocket.receive",
                "bytes": b"what is here? \xe2",
            }

    def test_http_timeout(self):
        """
        Tests that the HTTP timeout doesn't kick in for WebSockets
        """
        with DaphneTestingInstance(http_timeout=1) as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Wait 2 seconds
            time.sleep(2)
            # Prep frame for it to send
            test_app.add_send_messages([{"type": "websocket.send", "text": "cake"}])
            # Send it a frame
            self.websocket_send_frame(sock, "still alive?")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == "cake"
Exemplo n.º 3
0
class TestWebsocket(DaphneTestCase):
    """
    Tests WebSocket handshake, send and receive.
    """
    def assert_valid_websocket_scope(self,
                                     scope,
                                     path="/",
                                     params=None,
                                     headers=None,
                                     scheme=None,
                                     subprotocols=None):
        """
        Checks that the passed scope is a valid ASGI HTTP scope regarding types
        and some urlencoding things.
        """
        # Check overall keys
        self.assert_key_sets(
            required_keys={
                "asgi",
                "type",
                "path",
                "raw_path",
                "query_string",
                "headers",
            },
            optional_keys={
                "scheme", "root_path", "client", "server", "subprotocols"
            },
            actual_keys=scope.keys(),
        )
        self.assertEqual(scope["asgi"]["version"], "3.0")
        # Check that it is the right type
        self.assertEqual(scope["type"], "websocket")
        # Path
        self.assert_valid_path(scope["path"])
        # Scheme
        self.assertIn(scope.get("scheme", "ws"), ["ws", "wss"])
        if scheme:
            self.assertEqual(scheme, scope["scheme"])
        # Query string (byte string and still url encoded)
        query_string = scope["query_string"]
        self.assertIsInstance(query_string, bytes)
        if params:
            self.assertEqual(query_string,
                             parse.urlencode(params or []).encode("ascii"))
        # Ordering of header names is not important, but the order of values for a header
        # name is. To assert whether that order is kept, we transform both the request
        # headers and the channel message headers into a dictionary
        # {name: [value1, value2, ...]} and check if they're equal.
        transformed_scope_headers = collections.defaultdict(list)
        for name, value in scope["headers"]:
            transformed_scope_headers.setdefault(name, [])
            # Make sure to split out any headers collapsed with commas
            for bit in value.split(b","):
                if bit.strip():
                    transformed_scope_headers[name].append(bit.strip())
        transformed_request_headers = collections.defaultdict(list)
        for name, value in headers or []:
            expected_name = name.lower().strip()
            expected_value = value.strip()
            # Make sure to split out any headers collapsed with commas
            transformed_request_headers.setdefault(expected_name, [])
            for bit in expected_value.split(b","):
                if bit.strip():
                    transformed_request_headers[expected_name].append(
                        bit.strip())
        for name, value in transformed_request_headers.items():
            self.assertIn(name, transformed_scope_headers)
            self.assertEqual(value, transformed_scope_headers[name])
        # Root path
        self.assertIsInstance(scope.get("root_path", ""), str)
        # Client and server addresses
        client = scope.get("client")
        if client is not None:
            self.assert_valid_address_and_port(client)
        server = scope.get("server")
        if server is not None:
            self.assert_valid_address_and_port(server)
        # Subprotocols
        scope_subprotocols = scope.get("subprotocols", [])
        if scope_subprotocols:
            assert all(isinstance(x, str) for x in scope_subprotocols)
        if subprotocols:
            assert sorted(scope_subprotocols) == sorted(subprotocols)

    def assert_valid_websocket_connect_message(self, message):
        """
        Asserts that a message is a valid http.request message
        """
        # Check overall keys
        self.assert_key_sets(required_keys={"type"},
                             optional_keys=set(),
                             actual_keys=message.keys())
        # Check that it is the right type
        self.assertEqual(message["type"], "websocket.connect")

    def test_accept(self):
        """
        Tests we can open and accept a socket.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(test_app)
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope)
            self.assert_valid_websocket_connect_message(messages[0])

    def test_reject(self):
        """
        Tests we can reject a socket and it won't complete the handshake.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.close"}])
            with self.assertRaises(RuntimeError):
                self.websocket_handshake(test_app)

    def test_subprotocols(self):
        """
        Tests that we can ask for subprotocols and then select one.
        """
        subprotocols = ["proto1", "proto2"]
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{
                "type": "websocket.accept",
                "subprotocol": "proto2"
            }])
            _, subprotocol = self.websocket_handshake(
                test_app, subprotocols=subprotocols)
            # Validate the scope and messages we got
            assert subprotocol == "proto2"
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope, subprotocols=subprotocols)
            self.assert_valid_websocket_connect_message(messages[0])

    def test_accept_permessage_deflate_extension(self):
        """
        Tests that permessage-deflate extension is successfuly accepted
        by underlying `autobahn` package.
        """

        headers = [
            (
                b"Sec-WebSocket-Extensions",
                b"permessage-deflate; client_max_window_bits",
            ),
        ]

        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{
                "type": "websocket.accept",
            }])

            sock, subprotocol = self.websocket_handshake(
                test_app,
                headers=headers,
            )
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])

    def test_accept_custom_extension(self):
        """
        Tests that custom headers can be accpeted during handshake.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{
                "type":
                "websocket.accept",
                "headers":
                [(b"Sec-WebSocket-Extensions", b"custom-extension")],
            }])

            sock, subprotocol = self.websocket_handshake(
                test_app,
                headers=[
                    (b"Sec-WebSocket-Extensions", b"custom-extension"),
                ],
            )
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])

    def test_xff(self):
        """
        Tests that X-Forwarded-For headers get parsed right
        """
        headers = [["X-Forwarded-For", "10.1.2.3"], ["X-Forwarded-Port", "80"]]
        with DaphneTestingInstance(xff=True) as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(test_app, headers=headers)
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope)
            self.assert_valid_websocket_connect_message(messages[0])
            assert scope["client"] == ["10.1.2.3", 80]

    @given(
        request_path=http_strategies.http_path(),
        request_params=http_strategies.query_params(),
        request_headers=http_strategies.headers(),
    )
    @settings(max_examples=5, deadline=2000)
    def test_http_bits(self, request_path, request_params, request_headers):
        """
        Tests that various HTTP-level bits (query string params, path, headers)
        carry over into the scope.
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(
                test_app,
                path=parse.quote(request_path),
                params=request_params,
                headers=request_headers,
            )
            # Validate the scope and messages we got
            scope, messages = test_app.get_received()
            self.assert_valid_websocket_scope(scope,
                                              path=request_path,
                                              params=request_params,
                                              headers=request_headers)
            self.assert_valid_websocket_connect_message(messages[0])

    def test_raw_path(self):
        """
        Tests that /foo%2Fbar produces raw_path and a decoded path
        """
        with DaphneTestingInstance() as test_app:
            test_app.add_send_messages([{"type": "websocket.accept"}])
            self.websocket_handshake(test_app, path="/foo%2Fbar")
            # Validate the scope and messages we got
            scope, _ = test_app.get_received()

        self.assertEqual(scope["path"], "/foo/bar")
        self.assertEqual(scope["raw_path"], b"/foo%2Fbar")

    def test_text_frames(self):
        """
        Tests we can send and receive text frames.
        """
        with DaphneTestingInstance() as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Prep frame for it to send
            test_app.add_send_messages([{
                "type": "websocket.send",
                "text": "here be dragons 🐉"
            }])
            # Send it a frame
            self.websocket_send_frame(sock, "what is here? 🌍")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == "here be dragons 🐉"
            # Make sure it got our frame
            _, messages = test_app.get_received()
            assert messages[1] == {
                "type": "websocket.receive",
                "text": "what is here? 🌍",
            }

    def test_binary_frames(self):
        """
        Tests we can send and receive binary frames with things that are very
        much not valid UTF-8.
        """
        with DaphneTestingInstance() as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Prep frame for it to send
            test_app.add_send_messages([{
                "type": "websocket.send",
                "bytes": b"here be \xe2 bytes"
            }])
            # Send it a frame
            self.websocket_send_frame(sock, b"what is here? \xe2")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == b"here be \xe2 bytes"
            # Make sure it got our frame
            _, messages = test_app.get_received()
            assert messages[1] == {
                "type": "websocket.receive",
                "bytes": b"what is here? \xe2",
            }

    def test_http_timeout(self):
        """
        Tests that the HTTP timeout doesn't kick in for WebSockets
        """
        with DaphneTestingInstance(http_timeout=1) as test_app:
            # Connect
            test_app.add_send_messages([{"type": "websocket.accept"}])
            sock, _ = self.websocket_handshake(test_app)
            _, messages = test_app.get_received()
            self.assert_valid_websocket_connect_message(messages[0])
            # Wait 2 seconds
            time.sleep(2)
            # Prep frame for it to send
            test_app.add_send_messages([{
                "type": "websocket.send",
                "text": "cake"
            }])
            # Send it a frame
            self.websocket_send_frame(sock, "still alive?")
            # Receive a frame and make sure it's correct
            assert self.websocket_receive_frame(sock) == "cake"

    def test_application_checker_handles_asyncio_cancellederror(self):
        with CancellingTestingInstance() as app:
            # Connect to the websocket app, it will immediately raise
            # asyncio.CancelledError
            sock, _ = self.websocket_handshake(app)
            # Disconnect from the socket
            sock.close()
            # Wait for application_checker to clean up the applications for
            # disconnected clients, and for the server to be stopped.
            time.sleep(3)
            # Make sure we received either no error, or a ConnectionsNotEmpty
            while not app.process.errors.empty():
                err, _tb = app.process.errors.get()
                if not isinstance(err, ConnectionsNotEmpty):
                    raise err
                self.fail(
                    "Server connections were not cleaned up after an asyncio.CancelledError was raised"
                )