From cc3550eeeff011f98361b8318447faf57bbac036 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 27 Apr 2024 13:42:21 +1000 Subject: [PATCH] examples/network: Add example of HTTPS client using non-blocking socket. Non-blocking SSL streams can be difficult to get right, so provide a working example, of a HTTPS client. Signed-off-by: Damien George --- examples/network/https_client_nonblocking.py | 75 ++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 examples/network/https_client_nonblocking.py diff --git a/examples/network/https_client_nonblocking.py b/examples/network/https_client_nonblocking.py new file mode 100644 index 0000000000..6e6b7f37bc --- /dev/null +++ b/examples/network/https_client_nonblocking.py @@ -0,0 +1,75 @@ +# Example of a HTTPS client working with non-blocking sockets. +# +# Non-blocking SSL streams works differently in MicroPython compared to CPython. In +# CPython a write to an SSLSocket may raise ssl.SSLWantReadError. In MicroPython an +# SSLSocket behaves like a normal socket/stream and can be polled for reading/writing. + +from errno import EINPROGRESS +import select +import socket +import ssl + + +def connect_nonblocking(sock, addr): + sock.setblocking(False) + try: + sock.connect(addr) + except OSError as er: + if er.errno != EINPROGRESS: + raise er + + +def write_nonblocking(poller, sock, data): + poller.register(sock, select.POLLOUT) + while data: + poller.poll() + n = sock.write(data) + print("Wrote:", n) + if n is not None: + data = data[n:] + + +def read_nonblocking(poller, sock, n): + poller.register(sock, select.POLLIN) + poller.poll() + data = sock.read(n) + print("Read:", len(data)) + return data + + +def main(url): + # Split the given URL into components. + proto, _, host, path = url.split(b"/", 3) + assert proto == b"https:" + + # Note: this getaddrinfo() call is blocking! + ai = socket.getaddrinfo(host, 443)[0] + addr = ai[-1] + print("Connect address:", addr) + + # Create a TCP socket and connect to the server in non-blocking mode. + sock = socket.socket(ai[0], ai[1], ai[2]) + connect_nonblocking(sock, addr) + + # Wrap the TCP socket in an SSL stream in non-blocking mode. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + sock = ctx.wrap_socket(sock, server_hostname=host, do_handshake_on_connect=False) + sock.setblocking(False) + + # Create an object to poll the SSL stream for readability/writability. + poller = select.poll() + + # Send the HTTP request on the SSL stream. + request = b"GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host) + write_nonblocking(poller, sock, request) + + # Receive the HTTP response from the SSL stream. + response = read_nonblocking(poller, sock, 1000) + for line in response.split(b"\n"): + print(line) + + # Close the SSL stream. This will also close the underlying TCP socket. + sock.close() + + +main(b"https://micropython.org/ks/test.html")