Repository: encode/uvicorn

Issue:
Update 'websockets' to 8.x
I realize that https://github.com/encode/uvicorn/pull/396 was done because the tests appear to be failing with `websockets>=8.0`, but that release also fixed a bunch of DeprecationWarnings that we've been seeing in [httpx builds on 3.8](https://travis-ci.org/encode/httpx/jobs/574949180).

Since it's only a `DeprecationWarning`, and only on 3.8 ([3.7 and below is fine](https://travis-ci.org/encode/httpx/jobs/574949179)), I definitely would agree that this is a low priority issue.

I dug into the tests to try and figure out what exactly was going wrong, but the `AttributeError`s I looked at looks like the main issue is two missing functions in [websockets_impl.py](https://github.com/encode/uvicorn/blob/master/uvicorn/protocols/websockets/websockets_impl.py)

`15 AttributeError: 'Server' object has no attribute 'is_serving'` --> https://github.com/aaugustin/websockets/blob/8.0.2/src/websockets/server.py#L642-L668
` 9 AttributeError: 'WebSocketProtocol' object has no attribute 'transfer_data_task'` --> https://github.com/aaugustin/websockets/blob/8.0.2/src/websockets/protocol.py#L276-L313


Discussion:
Hi. I would propose to upgrade to 8.x fast, because of this PR: https://github.com/aaugustin/websockets/pull/627 Please consider the related issues.

Is it save to use uvicorn with 8.x in the meanwhile?
I'd be very happy to see a PR updating us to `8.x`, and resolving any issues that occur as a result.

> Is it save to use uvicorn with 8.x in the meanwhile

I've no idea if it works with 8.x, or what breaking API changes there are from 7.x -> 8.x.

The simplest way to indentify any issues would be to issue a pull request updating the requirements, and find out what test cases break, if any.
I'm way out of my depth for websockets, but I'm good at research. Here's some references to look at.

Official Changelog:
https://websockets.readthedocs.io/en/stable/changelog.html
Changes to server.py:
https://github.com/aaugustin/websockets/compare/7.0...8.0.2#diff-e3ed1d3aa2effce2f4ca42e705d7924c
Changes to protocol.py:
https://github.com/aaugustin/websockets/compare/7.0...8.0.2#diff-5e2d99fc26f0a3387bd0ff4241906a9b

Pytest Results (6.53s):
```
     148 passed
      15 failed
         - tests/protocols/test_websocket.py:103 test_accept_connection[WebSocketProtocol]
         - tests/protocols/test_websocket.py:140 test_headers[WebSocketProtocol]
         - tests/protocols/test_websocket.py:160 test_path_and_raw_path[WebSocketProtocol]
         - tests/protocols/test_websocket.py:180 test_send_text_data_to_client[WebSocketProtocol]
         - tests/protocols/test_websocket.py:198 test_send_binary_data_to_client[WebSocketProtocol]
         - tests/protocols/test_websocket.py:216 test_send_and_close_connection[WebSocketProtocol]
         - tests/protocols/test_websocket.py:242 test_send_text_data_to_server[WebSocketProtocol]
         - tests/protocols/test_websocket.py:264 test_send_binary_data_to_server[WebSocketProtocol]
         - tests/protocols/test_websocket.py:286 test_send_after_protocol_close[WebSocketProtocol]
         - tests/protocols/test_websocket.py:354 test_duplicate_handshake[WebSocketProtocol]
         - tests/protocols/test_websocket.py:376 test_asgi_return_value[WebSocketProtocol]
         - tests/protocols/test_websocket.py:399 test_app_close[WebSocketProtocol]
         - tests/protocols/test_websocket.py:425 test_client_close[WebSocketProtocol]
         - tests/protocols/test_websocket.py:448 test_subprotocols[proto1-WebSocketProtocol]
         - tests/protocols/test_websocket.py:448 test_subprotocols[proto2-WebSocketProtocol]
```

And a summary of the errors given:
```
  15 AttributeError: 'Server' object has no attribute 'is_serving'
   9 AttributeError: 'WebSocketProtocol' object has no attribute 'transfer_data_task'
   1 ERROR    asyncio:streams.py:496 Task was destroyed but it is pending!
  14 ERROR    uvicorn:websockets_impl.py:151 Exception in ASGI application
   1 ERROR    uvicorn:websockets_impl.py:166 ASGI callable should return None, but returned '123'.
  14 INFO     uvicorn:websockets_impl.py:178 ('127.0.0.1', XXXXX) - "WebSocket /" [accepted]
   1 INFO     uvicorn:websockets_impl.py:178 ('127.0.0.1', XXXXX) - "WebSocket /one/two" [accepted]
  15 WARNING  websockets.server:server.py:173 Error in opening handshake
```

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -43,7 +43,7 @@ def get_packages(package):
 requirements = [
     "click==7.*",
     "h11==0.8.*",
-    "websockets==7.*",
+    "websockets==8.*",
     "httptools==0.0.13 ;" + env_marker,
     "uvloop==0.* ;" + env_marker,
 ]
diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py
--- a/uvicorn/protocols/websockets/websockets_impl.py
+++ b/uvicorn/protocols/websockets/websockets_impl.py
@@ -16,6 +16,9 @@ def register(self, ws):
     def unregister(self, ws):
         pass
 
+    def is_serving(self):
+        return not self.closing
+
 
 class WebSocketProtocol(websockets.WebSocketServerProtocol):
     def __init__(self, config, server_state, _loop=None):
@@ -46,10 +49,11 @@ def __init__(self, config, server_state, _loop=None):
         self.initial_response = None
         self.connect_sent = False
         self.accepted_subprotocol = None
+        self.transfer_data_task = None
 
-        server = Server()
+        self.ws_server = Server()
 
-        super().__init__(ws_handler=self.ws_handler, ws_server=server)
+        super().__init__(ws_handler=self.ws_handler, ws_server=self.ws_server)
 
     def connection_made(self, transport):
         self.connections.add(self)
@@ -65,6 +69,7 @@ def connection_lost(self, exc):
         super().connection_lost(exc)
 
     def shutdown(self):
+        self.ws_server.closing = True
         self.transport.close()
 
     def on_task_complete(self, task):


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,4 @@
 import pytest
-import tempfile
 
 
 CERTIFICATE = b"""-----BEGIN CERTIFICATE-----