diff --git a/micropython/uaiohttpclient/clientsession_example.py b/micropython/uaiohttpclient/clientsession_example.py new file mode 100644 index 000000000..31e037a8e --- /dev/null +++ b/micropython/uaiohttpclient/clientsession_example.py @@ -0,0 +1,17 @@ +import uaiohttpclient as aiohttp +import asyncio + + +async def fetch(client): + async with client.get("http://micropython.org") as resp: + assert resp.status == 200 + return await resp.text() + + +async def main(): + async with aiohttp.ClientSession() as client: + html = await fetch(client) + print(html) + + +asyncio.run(main()) diff --git a/micropython/uaiohttpclient/uaiohttpclient.py b/micropython/uaiohttpclient/uaiohttpclient.py index 25b2e62a9..377109780 100644 --- a/micropython/uaiohttpclient/uaiohttpclient.py +++ b/micropython/uaiohttpclient/uaiohttpclient.py @@ -8,6 +8,9 @@ def __init__(self, reader): def read(self, sz=-1): return (yield from self.content.read(sz)) + def text(self, sz=-1): + return self.read(sz=sz) + def __repr__(self): return "" % (self.status, self.headers) @@ -40,22 +43,139 @@ def __repr__(self): return "" % (self.status, self.headers) +class _RequestContextManager: + def __init__(self, client, request_co): + self.reqco = request_co + self.client = client + + async def __aenter__(self): + return await self.reqco + + async def __aexit__(self, *args): + await self.client._reader.aclose() + return await asyncio.sleep(0) + + +class ClientSession: + def __init__(self): + self._reader = None + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return await asyncio.sleep(0) + + def request(self, method, url, ssl=None): + return _RequestContextManager(self, self._request(method, url, ssl=ssl)) + + async def _request(self, method, url, ssl=None): + redir_cnt = 0 + redir_url = None + while redir_cnt < 2: + reader = yield from self.request_raw(method, url, ssl) + headers = [] + sline = yield from reader.readline() + sline = sline.split(None, 2) + status = int(sline[1]) + chunked = False + while True: + line = yield from reader.readline() + if not line or line == b"\r\n": + break + headers.append(line) + if line.startswith(b"Transfer-Encoding:"): + if b"chunked" in line: + chunked = True + elif line.startswith(b"Location:"): + url = line.rstrip().split(None, 1)[1].decode("latin-1") + + if 301 <= status <= 303: + redir_cnt += 1 + yield from reader.aclose() + continue + break + + if chunked: + resp = ChunkedClientResponse(reader) + else: + resp = ClientResponse(reader) + resp.status = status + resp.headers = headers + self._reader = reader + return resp + + async def request_raw(self, method, url, ssl=None): + try: + proto, dummy, host, path = url.split("/", 3) + except ValueError: + proto, dummy, host = url.split("/", 2) + path = "" + + if proto == "http:": + port = 80 + elif proto == "https:": + port = 443 + if ssl is None: + ssl = True + else: + raise ValueError("Unsupported protocol: " + proto) + + if ":" in host: + host, port = host.split(":", 1) + port = int(port) + + reader, writer = yield from asyncio.open_connection(host, port, ssl=ssl) + # Use protocol 1.0, because 1.1 always allows to use chunked transfer-encoding + # But explicitly set Connection: close, even though this should be default for 1.0, + # because some servers misbehave w/o it. + query = ( + "%s /%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nUser-Agent: compat\r\n\r\n" + % ( + method, + path, + host, + ) + ) + yield from writer.awrite(query.encode("latin-1")) + # yield from writer.aclose() + return reader + + def get(self, url, ssl=None): + return _RequestContextManager(self, self._request("GET", url, ssl=ssl)) + + def request_raw(method, url): try: proto, dummy, host, path = url.split("/", 3) except ValueError: proto, dummy, host = url.split("/", 2) path = "" - if proto != "http:": + + if proto == "http:": + port = 80 + elif proto == "https:": + port = 443 + else: raise ValueError("Unsupported protocol: " + proto) - reader, writer = yield from asyncio.open_connection(host, 80) + + if ":" in host: + host, port = host.split(":", 1) + port = int(port) + + reader, writer = yield from asyncio.open_connection( + host, port, ssl=proto == "https:" + ) # Use protocol 1.0, because 1.1 always allows to use chunked transfer-encoding # But explicitly set Connection: close, even though this should be default for 1.0, # because some servers misbehave w/o it. - query = "%s /%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nUser-Agent: compat\r\n\r\n" % ( - method, - path, - host, + query = ( + "%s /%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nUser-Agent: compat\r\n\r\n" + % ( + method, + path, + host, + ) ) yield from writer.awrite(query.encode("latin-1")) # yield from writer.aclose()