The websockets Library, Without asyncio · practice
Connect as a Client
Your library server has so far talked only to the hand-built Chapter 5 client. This lesson writes the other side with the library, echo_once(url, texts, *, connect=connect), and then points that one client at both servers: yours from Lesson 2 and the Chapter 5 server.
A URL instead of an address
The library’s client takes a ws://127.0.0.1:50123/echo the way ws means wss would add 127.0.0.1 and 50123 are the host and the port to connect to, and /echo is the path.
From those parts the library builds the upgrade request you built by hand in Chapter 2 with build_upgrade_request: a GET request line with the path as its target, a Host header, a fresh Sec-WebSocket-Key and Sec-WebSocket-Version: 13. When the 101 arrives, it checks the Sec-WebSocket-Accept check_accept_response did.
For ws://127.0.0.1:50123/echo, what request line does the library send?
Bounded on every side
connect, from websockets.sync.client, opens the TCP connection, runs the send, recv and close your handler used. A client waits in three places, and this course bounds each with WAIT, 2 seconds:
open_timeout=WAITbounds the handshake. The library’s own default is 10 seconds.recv(timeout=WAIT)bounds each echo, as in your handler.close_timeout=WAITbounds the closing handshake. Its default is also 10 seconds.
A bound turns a wait into an connect raises TimeoutError with the message timed out while waiting for handshake response once open_timeout runs out. In a local test with open_timeout=0.5 against a listener that never answered, that took about half a second.
One more connect: proxy=None. By default the library reads proxy settings from the environment, such as the HTTP_PROXY 127.0.0.1. In a local test with HTTP_PROXY set to a port where nothing listened, a connection meant for a proxy=None, the same connection reached the server. Whether a machine sets such a variable is not something a loopback program should depend on, so every connect in this course passes proxy=None.
Always use connect(...) in a with statement. The version you pinned in Lesson 1 issues a DeprecationWarning when connect is called any other way.
Why does every connect in this course pass proxy=None?
Leaving the block
A client’s conversation sits inside the with block:
with connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) as connection:
connection.send("hello")
When the block ends, on any path, the client sends a close close_timeout, for the server’s answering close and for the server to close the TCP connection. That is the closing handshake you wrote in Chapter 5 with close_frame and wait_for_close, now run for you.
What happens when the with connect(...) block ends?
Your task
Write echo_once(url, texts, *, connect=connect) in echo_library.py. Open the connection with connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) in a with statement. For each text, send it and read its echo with connection.recv(timeout=WAIT), appending the echo to a connect=connect is the same kind of seam as serve=serve: Submit passes a stand-in.
Before you press Run
Run prints two of the blocks from Lesson 2 this time, each starting with connection handler failed, one for each conversation your Lesson 2 handler serves. The first comes from the Lesson 2 section, as before. The second comes from the first Lesson 3 section: your client leaves its with block while your handler waits in recv, so the handler meets a close again, this time with no reason. That block ends with:
websockets.exceptions.ConnectionClosedOK: received 1000 (OK); then sent 1000 (OK)
Both are expected, Submit still accepts them, and Lesson 4 removes them.
Press Run. After the Lesson 2 section, a finished version, run on the site, printed:
Lesson 3: your library client, your library server
['hello', 'café ✓', 'third message']
Lesson 3: your library client, the Chapter 5 server
{'client': ['hello', 'café ✓', 'third message'], 'Chapter 5 server': [{'messages': 3, 'code': 1000}]}
The first section is the library on both ends. The second is Lesson 1’s interop check again, with your own client this time: the Chapter 5 server reports three messages and a 1000 close. Then press Submit.
Both conversations end with a
Task
Write echo_once(url, texts, *, connect=connect) in echo_library.py:
open the connection with
connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None)in awithstatement;for each text, send it with
connection.sendand read its echo withconnection.recv(timeout=WAIT), appending the echo to a; return the list.
Press Run: the two Lesson 3 sections should print ['hello', 'café ✓', 'third message'] and the same echoes with the Chapter 5 server’s report [{'messages': 3, 'code': 1000}]. Two connection handler failed blocks appear on standard error, as the lesson describes.
Then press Submit. The check never opens a echo_once a stand-in connect and records how it is called: the url passed on unchanged, open_timeout and close_timeout each set and at most 2 seconds, and proxy=None. The stand-in connections answer texts you have not seen, and one of them answers each text in capitals, so the list you return must hold what recv returned, in order. Every recv needs a timeout of at most 2 seconds, one recv per text, and the connection must be used in a with statement. Sending all texts before reading all echoes is fine.