0%

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 , not a host and a . Read ws://127.0.0.1:50123/echo the way with Python taught you to take a URL apart. The scheme ws means over plain ; wss would add , which this course does not use. 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 against its key, the job your 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 , and returns a connection with the same send, recv and close your handler used. A client waits in three places, and this course bounds each with WAIT, 2 seconds:

  • open_timeout=WAIT bounds the handshake. The library’s own default is 10 seconds.

  • recv(timeout=WAIT) bounds each echo, as in your handler.

  • close_timeout=WAIT bounds the closing handshake. Its default is also 10 seconds.

A bound turns a wait into an . If a server accepts the TCP connection but never answers the upgrade, 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 goes on every connect: proxy=None. By default the library reads proxy settings from the environment, such as the HTTP_PROXY , and follows them even for 127.0.0.1. In a local test with HTTP_PROXY set to a port where nothing listened, a connection meant for a server went to that proxy address instead and failed at once; with 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 with 1000 and an empty reason, then waits, at most 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 . Return the list after the block, so the connection is closed by then. Sending every text first and reading the echoes afterwards gives the same list here; one send, then one read, keeps each echo next to the text it answers. 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 on the server side even though nothing went wrong. The next lesson reads how a connection ended, and makes the handler end quietly.

Task

Write echo_once(url, texts, *, connect=connect) in echo_library.py:

  1. open the connection with connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) in a with statement;

  2. for each text, send it with connection.send and read its echo with connection.recv(timeout=WAIT), appending the echo to a ;

  3. 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 . It passes 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.