Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

micropython/uaiohttpclient: Add ssl support. #724

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions micropython/uaiohttpclient/clientsession_example.py
Original file line number Diff line number Diff line change
@@ -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())
132 changes: 126 additions & 6 deletions micropython/uaiohttpclient/uaiohttpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<ClientResponse %d %s>" % (self.status, self.headers)

Expand Down Expand Up @@ -40,22 +43,139 @@ def __repr__(self):
return "<ChunkedClientResponse %d %s>" % (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()
Expand Down
Loading