Skip to content

Add HTTP requests methods to aiohttp.ClientSession. #752

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

Closed
wants to merge 2 commits 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
18 changes: 18 additions & 0 deletions micropython/uaiohttpclient/clientsession_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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.decode())


if __name__ == "__main__":
asyncio.run(main())
22 changes: 22 additions & 0 deletions micropython/uaiohttpclient/clientsession_methods_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import uaiohttpclient as aiohttp
import asyncio


async def main():
async with aiohttp.ClientSession("http://httpbin.org") as session:
async with session.get("/get") as resp:
assert resp.status == 200
rget = await resp.text()
print(f"GET: {rget.decode()}")
async with session.post("/post", json={"foo": "bar"}) as resp:
assert resp.status == 200
rpost = await resp.text()
print(f"POST: {rpost.decode()}")
async with session.put("/put", data=b"data") as resp:
assert resp.status == 200
rput = await resp.json()
print("PUT: ", rput)


if __name__ == "__main__":
asyncio.run(main())
182 changes: 180 additions & 2 deletions micropython/uaiohttpclient/uaiohttpclient.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uasyncio as asyncio
import json as _json


class ClientResponse:
Expand All @@ -8,6 +9,12 @@ 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)

async def json(self):
return _json.loads(await self.read())

def __repr__(self):
return "<ClientResponse %d %s>" % (self.status, self.headers)

Expand Down Expand Up @@ -40,15 +47,186 @@ 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, base_url=""):
self._reader = None
self._base_url = base_url

async def __aenter__(self):
return self

async def __aexit__(self, *args):
return await asyncio.sleep(0)

def request(self, method, url, data=None, json=None, ssl=None):
return _RequestContextManager(
self,
self._request(method, self._base_url + url, data=data, json=json, ssl=ssl),
)

async def _request(self, method, url, data=None, json=None, ssl=None):
redir_cnt = 0
redir_url = None
while redir_cnt < 2:
reader = await self.request_raw(method, url, data, json, ssl)
headers = []
sline = await reader.readline()
sline = sline.split(None, 2)
status = int(sline[1])
chunked = False
while True:
line = await 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
await 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, data=None, json=None, ssl=None):
if json and isinstance(json, dict):
data = _json.dumps(json)
if data is not None and method == "GET":
method = "POST"
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 = await 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.
if not data:
query = (
"%s /%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nUser-Agent: compat\r\n\r\n"
% (
method,
path,
host,
)
)
else:
query = (
"""%s /%s HTTP/1.0\r\nHost: %s\r\n%sContent-Length: %s\r\n\r\n%s\r\nConnection: close\r\nUser-Agent: compat\r\n\r\n"""
% (
method,
path,
host,
"Content-Type: application/json\r\n" if json else "",
str(len(str(data))),
data,
)
)

await writer.awrite(query.encode("latin-1"))
# yield from writer.aclose()
return reader

def get(self, url, ssl=None):
return _RequestContextManager(self, self._request("GET", self._base_url + url, ssl=ssl))

def post(self, url, data=None, json=None, ssl=None):
return _RequestContextManager(
self,
self._request("POST", self._base_url + url, data=data, json=json, ssl=ssl),
)

def put(self, url, data=None, json=None, ssl=None):
return _RequestContextManager(
self,
self._request("PUT", self._base_url + url, data=data, json=json, ssl=ssl),
)

def patch(self, url, data=None, json=None, ssl=None):
return _RequestContextManager(
self,
self._request("PATCH", self._base_url + url, data=data, json=json, ssl=ssl),
)

def delete(self, url, ssl=None):
return _RequestContextManager(
self,
self._request("DELETE", self._base_url + url, ssl=ssl),
)

def head(self, url, ssl=None):
return _RequestContextManager(
self,
self._request("HEAD", self._base_url + url, ssl=ssl),
)

def options(self, url, ssl=None):
return _RequestContextManager(
self,
self._request("OPTIONS", self._base_url + 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.
Expand Down