summaryrefslogtreecommitdiff
path: root/examples/network/https_client_nonblocking.py
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2024-04-27 13:42:21 +1000
committerDamien George <damien@micropython.org>2024-05-13 11:37:00 +1000
commitcc3550eeeff011f98361b8318447faf57bbac036 (patch)
tree73d8fb09d89a11a70b189e177c95f4dbf213e243 /examples/network/https_client_nonblocking.py
parentbd610ff0160f8dad2b4a3592d5351b6029af5caa (diff)
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 <damien@micropython.org>
Diffstat (limited to 'examples/network/https_client_nonblocking.py')
-rw-r--r--examples/network/https_client_nonblocking.py75
1 files changed, 75 insertions, 0 deletions
diff --git a/examples/network/https_client_nonblocking.py b/examples/network/https_client_nonblocking.py
new file mode 100644
index 000000000..6e6b7f37b
--- /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")