kopia lustrzana https://github.com/micropython/micropython-lib
aiohttp: Add new aiohttp package.
Implement `aiohttp` with `ClientSession`, websockets and `SSLContext` support. Only client is implemented and API is mostly compatible with CPython `aiohttp`. Signed-off-by: Carlos Gil <carlosgilglez@gmail.com>pull/778/head v1.22.0
rodzic
57ce3ba95c
commit
7cdf708815
|
@ -0,0 +1,32 @@
|
||||||
|
aiohttp is an HTTP client module for MicroPython asyncio module,
|
||||||
|
with API mostly compatible with CPython [aiohttp](https://github.com/aio-libs/aiohttp)
|
||||||
|
module.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Only client is implemented.
|
||||||
|
|
||||||
|
See `examples/client.py`
|
||||||
|
```py
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get('http://micropython.org') as response:
|
||||||
|
|
||||||
|
print("Status:", response.status)
|
||||||
|
print("Content-Type:", response.headers['Content-Type'])
|
||||||
|
|
||||||
|
html = await response.text()
|
||||||
|
print("Body:", html[:15], "...")
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
```
|
||||||
|
$ micropython examples/client.py
|
||||||
|
Status: 200
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Body: <!DOCTYPE html> ...
|
||||||
|
|
||||||
|
```
|
|
@ -0,0 +1,264 @@
|
||||||
|
# MicroPython aiohttp library
|
||||||
|
# MIT license; Copyright (c) 2023 Carlos Gil
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json as _json
|
||||||
|
from .aiohttp_ws import (
|
||||||
|
_WSRequestContextManager,
|
||||||
|
ClientWebSocketResponse,
|
||||||
|
WebSocketClient,
|
||||||
|
WSMsgType,
|
||||||
|
)
|
||||||
|
|
||||||
|
HttpVersion10 = "HTTP/1.0"
|
||||||
|
HttpVersion11 = "HTTP/1.1"
|
||||||
|
|
||||||
|
|
||||||
|
class ClientResponse:
|
||||||
|
def __init__(self, reader):
|
||||||
|
self.content = reader
|
||||||
|
|
||||||
|
def _decode(self, data):
|
||||||
|
c_encoding = self.headers.get("Content-Encoding")
|
||||||
|
if c_encoding in ("gzip", "deflate", "gzip,deflate"):
|
||||||
|
try:
|
||||||
|
import deflate, io
|
||||||
|
|
||||||
|
if c_encoding == "deflate":
|
||||||
|
with deflate.DeflateIO(io.BytesIO(data), deflate.ZLIB) as d:
|
||||||
|
return d.read()
|
||||||
|
elif c_encoding == "gzip":
|
||||||
|
with deflate.DeflateIO(io.BytesIO(data), deflate.GZIP, 15) as d:
|
||||||
|
return d.read()
|
||||||
|
except ImportError:
|
||||||
|
print("WARNING: deflate module required")
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def read(self, sz=-1):
|
||||||
|
return self._decode(await self.content.read(sz))
|
||||||
|
|
||||||
|
async def text(self, encoding="utf-8"):
|
||||||
|
return (await self.read(sz=-1)).decode(encoding)
|
||||||
|
|
||||||
|
async def json(self):
|
||||||
|
return _json.loads(await self.read())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<ClientResponse %d %s>" % (self.status, self.headers)
|
||||||
|
|
||||||
|
|
||||||
|
class ChunkedClientResponse(ClientResponse):
|
||||||
|
def __init__(self, reader):
|
||||||
|
self.content = reader
|
||||||
|
self.chunk_size = 0
|
||||||
|
|
||||||
|
async def read(self, sz=4 * 1024 * 1024):
|
||||||
|
if self.chunk_size == 0:
|
||||||
|
l = await self.content.readline()
|
||||||
|
l = l.split(b";", 1)[0]
|
||||||
|
self.chunk_size = int(l, 16)
|
||||||
|
if self.chunk_size == 0:
|
||||||
|
# End of message
|
||||||
|
sep = await self.content.read(2)
|
||||||
|
assert sep == b"\r\n"
|
||||||
|
return b""
|
||||||
|
data = await self.content.read(min(sz, self.chunk_size))
|
||||||
|
self.chunk_size -= len(data)
|
||||||
|
if self.chunk_size == 0:
|
||||||
|
sep = await self.content.read(2)
|
||||||
|
assert sep == b"\r\n"
|
||||||
|
return self._decode(data)
|
||||||
|
|
||||||
|
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="", headers={}, version=HttpVersion10):
|
||||||
|
self._reader = None
|
||||||
|
self._base_url = base_url
|
||||||
|
self._base_headers = {"Connection": "close", "User-Agent": "compat"}
|
||||||
|
self._base_headers.update(**headers)
|
||||||
|
self._http_version = version
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args):
|
||||||
|
return await asyncio.sleep(0)
|
||||||
|
|
||||||
|
# TODO: Implement timeouts
|
||||||
|
|
||||||
|
async def _request(self, method, url, data=None, json=None, ssl=None, params=None, headers={}):
|
||||||
|
redir_cnt = 0
|
||||||
|
redir_url = None
|
||||||
|
while redir_cnt < 2:
|
||||||
|
reader = await self.request_raw(method, url, data, json, ssl, params, headers)
|
||||||
|
_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
|
||||||
|
resp.url = url
|
||||||
|
if params:
|
||||||
|
resp.url += "?" + "&".join(f"{k}={params[k]}" for k in sorted(params))
|
||||||
|
try:
|
||||||
|
resp.headers = {
|
||||||
|
val.split(":", 1)[0]: val.split(":", 1)[-1].strip()
|
||||||
|
for val in [hed.decode().strip() for hed in _headers]
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._reader = reader
|
||||||
|
return resp
|
||||||
|
|
||||||
|
async def request_raw(
|
||||||
|
self,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data=None,
|
||||||
|
json=None,
|
||||||
|
ssl=None,
|
||||||
|
params=None,
|
||||||
|
headers={},
|
||||||
|
is_handshake=False,
|
||||||
|
version=None,
|
||||||
|
):
|
||||||
|
if json and isinstance(json, dict):
|
||||||
|
data = _json.dumps(json)
|
||||||
|
if data is not None and method == "GET":
|
||||||
|
method = "POST"
|
||||||
|
if params:
|
||||||
|
url += "?" + "&".join(f"{k}={params[k]}" for k in sorted(params))
|
||||||
|
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 version is None:
|
||||||
|
version = self._http_version
|
||||||
|
if "Host" not in headers:
|
||||||
|
headers.update(Host=host)
|
||||||
|
if not data:
|
||||||
|
query = "%s /%s %s\r\n%s\r\n" % (
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
version,
|
||||||
|
"\r\n".join(f"{k}: {v}" for k, v in headers.items()) + "\r\n" if headers else "",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
headers.update(**{"Content-Length": len(str(data))})
|
||||||
|
if json:
|
||||||
|
headers.update(**{"Content-Type": "application/json"})
|
||||||
|
query = """%s /%s %s\r\n%s\r\n%s\r\n\r\n""" % (
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
version,
|
||||||
|
"\r\n".join(f"{k}: {v}" for k, v in headers.items()) + "\r\n",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
if not is_handshake:
|
||||||
|
await writer.awrite(query.encode("latin-1"))
|
||||||
|
return reader
|
||||||
|
else:
|
||||||
|
await writer.awrite(query.encode())
|
||||||
|
return reader, writer
|
||||||
|
|
||||||
|
def request(self, method, url, data=None, json=None, ssl=None, params=None, headers={}):
|
||||||
|
return _RequestContextManager(
|
||||||
|
self,
|
||||||
|
self._request(
|
||||||
|
method,
|
||||||
|
self._base_url + url,
|
||||||
|
data=data,
|
||||||
|
json=json,
|
||||||
|
ssl=ssl,
|
||||||
|
params=params,
|
||||||
|
headers=dict(**self._base_headers, **headers),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, url, **kwargs):
|
||||||
|
return self.request("GET", url, **kwargs)
|
||||||
|
|
||||||
|
def post(self, url, **kwargs):
|
||||||
|
return self.request("POST", url, **kwargs)
|
||||||
|
|
||||||
|
def put(self, url, **kwargs):
|
||||||
|
return self.request("PUT", url, **kwargs)
|
||||||
|
|
||||||
|
def patch(self, url, **kwargs):
|
||||||
|
return self.request("PATCH", url, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, url, **kwargs):
|
||||||
|
return self.request("DELETE", url, **kwargs)
|
||||||
|
|
||||||
|
def head(self, url, **kwargs):
|
||||||
|
return self.request("HEAD", url, **kwargs)
|
||||||
|
|
||||||
|
def options(self, url, **kwargs):
|
||||||
|
return self.request("OPTIONS", url, **kwargs)
|
||||||
|
|
||||||
|
def ws_connect(self, url, ssl=None):
|
||||||
|
return _WSRequestContextManager(self, self._ws_connect(url, ssl=ssl))
|
||||||
|
|
||||||
|
async def _ws_connect(self, url, ssl=None):
|
||||||
|
ws_client = WebSocketClient(None)
|
||||||
|
await ws_client.connect(url, ssl=ssl, handshake_request=self.request_raw)
|
||||||
|
self._reader = ws_client.reader
|
||||||
|
return ClientWebSocketResponse(ws_client)
|
|
@ -0,0 +1,269 @@
|
||||||
|
# MicroPython aiohttp library
|
||||||
|
# MIT license; Copyright (c) 2023 Carlos Gil
|
||||||
|
# adapted from https://github.com/danni/uwebsockets
|
||||||
|
# and https://github.com/miguelgrinberg/microdot/blob/main/src/microdot_asyncio_websocket.py
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
import json as _json
|
||||||
|
import binascii
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
URL_RE = re.compile(r"(wss|ws)://([A-Za-z0-9-\.]+)(?:\:([0-9]+))?(/.+)?")
|
||||||
|
URI = namedtuple("URI", ("protocol", "hostname", "port", "path")) # noqa: PYI024
|
||||||
|
|
||||||
|
|
||||||
|
def urlparse(uri):
|
||||||
|
"""Parse ws:// URLs"""
|
||||||
|
match = URL_RE.match(uri)
|
||||||
|
if match:
|
||||||
|
protocol = match.group(1)
|
||||||
|
host = match.group(2)
|
||||||
|
port = match.group(3)
|
||||||
|
path = match.group(4)
|
||||||
|
|
||||||
|
if protocol == "wss":
|
||||||
|
if port is None:
|
||||||
|
port = 443
|
||||||
|
elif protocol == "ws":
|
||||||
|
if port is None:
|
||||||
|
port = 80
|
||||||
|
else:
|
||||||
|
raise ValueError("Scheme {} is invalid".format(protocol))
|
||||||
|
|
||||||
|
return URI(protocol, host, int(port), path)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketMessage:
|
||||||
|
def __init__(self, opcode, data):
|
||||||
|
self.type = opcode
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
|
||||||
|
class WSMsgType:
|
||||||
|
TEXT = 1
|
||||||
|
BINARY = 2
|
||||||
|
ERROR = 258
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketClient:
|
||||||
|
CONT = 0
|
||||||
|
TEXT = 1
|
||||||
|
BINARY = 2
|
||||||
|
CLOSE = 8
|
||||||
|
PING = 9
|
||||||
|
PONG = 10
|
||||||
|
|
||||||
|
def __init__(self, params):
|
||||||
|
self.params = params
|
||||||
|
self.closed = False
|
||||||
|
self.reader = None
|
||||||
|
self.writer = None
|
||||||
|
|
||||||
|
async def connect(self, uri, ssl=None, handshake_request=None):
|
||||||
|
uri = urlparse(uri)
|
||||||
|
assert uri
|
||||||
|
if uri.protocol == "wss":
|
||||||
|
if not ssl:
|
||||||
|
ssl = True
|
||||||
|
await self.handshake(uri, ssl, handshake_request)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_frame_header(cls, header):
|
||||||
|
byte1, byte2 = struct.unpack("!BB", header)
|
||||||
|
|
||||||
|
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
||||||
|
fin = bool(byte1 & 0x80)
|
||||||
|
opcode = byte1 & 0x0F
|
||||||
|
|
||||||
|
# Byte 2: MASK(1) LENGTH(7)
|
||||||
|
mask = bool(byte2 & (1 << 7))
|
||||||
|
length = byte2 & 0x7F
|
||||||
|
|
||||||
|
return fin, opcode, mask, length
|
||||||
|
|
||||||
|
def _process_websocket_frame(self, opcode, payload):
|
||||||
|
if opcode == self.TEXT:
|
||||||
|
payload = payload.decode()
|
||||||
|
elif opcode == self.BINARY:
|
||||||
|
pass
|
||||||
|
elif opcode == self.CLOSE:
|
||||||
|
# raise OSError(32, "Websocket connection closed")
|
||||||
|
return opcode, payload
|
||||||
|
elif opcode == self.PING:
|
||||||
|
return self.PONG, payload
|
||||||
|
elif opcode == self.PONG: # pragma: no branch
|
||||||
|
return None, None
|
||||||
|
return None, payload
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _encode_websocket_frame(cls, opcode, payload):
|
||||||
|
if opcode == cls.TEXT:
|
||||||
|
payload = payload.encode()
|
||||||
|
|
||||||
|
length = len(payload)
|
||||||
|
fin = mask = True
|
||||||
|
|
||||||
|
# Frame header
|
||||||
|
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
||||||
|
byte1 = 0x80 if fin else 0
|
||||||
|
byte1 |= opcode
|
||||||
|
|
||||||
|
# Byte 2: MASK(1) LENGTH(7)
|
||||||
|
byte2 = 0x80 if mask else 0
|
||||||
|
|
||||||
|
if length < 126: # 126 is magic value to use 2-byte length header
|
||||||
|
byte2 |= length
|
||||||
|
frame = struct.pack("!BB", byte1, byte2)
|
||||||
|
|
||||||
|
elif length < (1 << 16): # Length fits in 2-bytes
|
||||||
|
byte2 |= 126 # Magic code
|
||||||
|
frame = struct.pack("!BBH", byte1, byte2, length)
|
||||||
|
|
||||||
|
elif length < (1 << 64):
|
||||||
|
byte2 |= 127 # Magic code
|
||||||
|
frame = struct.pack("!BBQ", byte1, byte2, length)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
# Mask is 4 bytes
|
||||||
|
mask_bits = struct.pack("!I", random.getrandbits(32))
|
||||||
|
frame += mask_bits
|
||||||
|
payload = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(payload))
|
||||||
|
return frame + payload
|
||||||
|
|
||||||
|
async def handshake(self, uri, ssl, req):
|
||||||
|
headers = {}
|
||||||
|
_http_proto = "http" if uri.protocol != "wss" else "https"
|
||||||
|
url = f"{_http_proto}://{uri.hostname}:{uri.port}{uri.path or '/'}"
|
||||||
|
key = binascii.b2a_base64(bytes(random.getrandbits(8) for _ in range(16)))[:-1]
|
||||||
|
headers["Host"] = f"{uri.hostname}:{uri.port}"
|
||||||
|
headers["Connection"] = "Upgrade"
|
||||||
|
headers["Upgrade"] = "websocket"
|
||||||
|
headers["Sec-WebSocket-Key"] = key
|
||||||
|
headers["Sec-WebSocket-Version"] = "13"
|
||||||
|
headers["Origin"] = f"{_http_proto}://{uri.hostname}:{uri.port}"
|
||||||
|
|
||||||
|
self.reader, self.writer = await req(
|
||||||
|
"GET",
|
||||||
|
url,
|
||||||
|
ssl=ssl,
|
||||||
|
headers=headers,
|
||||||
|
is_handshake=True,
|
||||||
|
version="HTTP/1.1",
|
||||||
|
)
|
||||||
|
|
||||||
|
header = await self.reader.readline()
|
||||||
|
header = header[:-2]
|
||||||
|
assert header.startswith(b"HTTP/1.1 101 "), header
|
||||||
|
|
||||||
|
while header:
|
||||||
|
header = await self.reader.readline()
|
||||||
|
header = header[:-2]
|
||||||
|
|
||||||
|
async def receive(self):
|
||||||
|
while True:
|
||||||
|
opcode, payload = await self._read_frame()
|
||||||
|
send_opcode, data = self._process_websocket_frame(opcode, payload)
|
||||||
|
if send_opcode: # pragma: no cover
|
||||||
|
await self.send(data, send_opcode)
|
||||||
|
if opcode == self.CLOSE:
|
||||||
|
self.closed = True
|
||||||
|
return opcode, data
|
||||||
|
elif data: # pragma: no branch
|
||||||
|
return opcode, data
|
||||||
|
|
||||||
|
async def send(self, data, opcode=None):
|
||||||
|
frame = self._encode_websocket_frame(
|
||||||
|
opcode or (self.TEXT if isinstance(data, str) else self.BINARY), data
|
||||||
|
)
|
||||||
|
self.writer.write(frame)
|
||||||
|
await self.writer.drain()
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
if not self.closed: # pragma: no cover
|
||||||
|
self.closed = True
|
||||||
|
await self.send(b"", self.CLOSE)
|
||||||
|
|
||||||
|
async def _read_frame(self):
|
||||||
|
header = await self.reader.read(2)
|
||||||
|
if len(header) != 2: # pragma: no cover
|
||||||
|
# raise OSError(32, "Websocket connection closed")
|
||||||
|
opcode = self.CLOSE
|
||||||
|
payload = b""
|
||||||
|
return opcode, payload
|
||||||
|
fin, opcode, has_mask, length = self._parse_frame_header(header)
|
||||||
|
if length == 126: # Magic number, length header is 2 bytes
|
||||||
|
(length,) = struct.unpack("!H", await self.reader.read(2))
|
||||||
|
elif length == 127: # Magic number, length header is 8 bytes
|
||||||
|
(length,) = struct.unpack("!Q", await self.reader.read(8))
|
||||||
|
|
||||||
|
if has_mask: # pragma: no cover
|
||||||
|
mask = await self.reader.read(4)
|
||||||
|
payload = await self.reader.read(length)
|
||||||
|
if has_mask: # pragma: no cover
|
||||||
|
payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload))
|
||||||
|
return opcode, payload
|
||||||
|
|
||||||
|
|
||||||
|
class ClientWebSocketResponse:
|
||||||
|
def __init__(self, wsclient):
|
||||||
|
self.ws = wsclient
|
||||||
|
|
||||||
|
def __aiter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __anext__(self):
|
||||||
|
msg = WebSocketMessage(*await self.ws.receive())
|
||||||
|
# print(msg.data, msg.type) # DEBUG
|
||||||
|
if (not msg.data and msg.type == self.ws.CLOSE) or self.ws.closed:
|
||||||
|
raise StopAsyncIteration
|
||||||
|
return msg
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
await self.ws.close()
|
||||||
|
|
||||||
|
async def send_str(self, data):
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError("data argument must be str (%r)" % type(data))
|
||||||
|
await self.ws.send(data)
|
||||||
|
|
||||||
|
async def send_bytes(self, data):
|
||||||
|
if not isinstance(data, (bytes, bytearray, memoryview)):
|
||||||
|
raise TypeError("data argument must be byte-ish (%r)" % type(data))
|
||||||
|
await self.ws.send(data)
|
||||||
|
|
||||||
|
async def send_json(self, data):
|
||||||
|
await self.send_str(_json.dumps(data))
|
||||||
|
|
||||||
|
async def receive_str(self):
|
||||||
|
msg = WebSocketMessage(*await self.ws.receive())
|
||||||
|
if msg.type != self.ws.TEXT:
|
||||||
|
raise TypeError(f"Received message {msg.type}:{msg.data!r} is not str")
|
||||||
|
return msg.data
|
||||||
|
|
||||||
|
async def receive_bytes(self):
|
||||||
|
msg = WebSocketMessage(*await self.ws.receive())
|
||||||
|
if msg.type != self.ws.BINARY:
|
||||||
|
raise TypeError(f"Received message {msg.type}:{msg.data!r} is not bytes")
|
||||||
|
return msg.data
|
||||||
|
|
||||||
|
async def receive_json(self):
|
||||||
|
data = await self.receive_str()
|
||||||
|
return _json.loads(data)
|
||||||
|
|
||||||
|
|
||||||
|
class _WSRequestContextManager:
|
||||||
|
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)
|
|
@ -0,0 +1,18 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get("http://micropython.org") as response:
|
||||||
|
print("Status:", response.status)
|
||||||
|
print("Content-Type:", response.headers["Content-Type"])
|
||||||
|
|
||||||
|
html = await response.text()
|
||||||
|
print("Body:", html[:15], "...")
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,20 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
headers = {"Accept-Encoding": "gzip,deflate"}
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession(headers=headers, version=aiohttp.HttpVersion11) as session:
|
||||||
|
async with session.get("http://micropython.org") as response:
|
||||||
|
print("Status:", response.status)
|
||||||
|
print("Content-Type:", response.headers["Content-Type"])
|
||||||
|
print(response.headers)
|
||||||
|
html = await response.text()
|
||||||
|
print(html)
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,29 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
URL = sys.argv.pop()
|
||||||
|
|
||||||
|
if not URL.startswith("http"):
|
||||||
|
URL = "http://micropython.org"
|
||||||
|
|
||||||
|
print(URL)
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch(client):
|
||||||
|
async with client.get(URL) 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)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,18 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
headers = {"Authorization": "Basic bG9naW46cGFzcw=="}
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession(headers=headers) as session:
|
||||||
|
async with session.get("http://httpbin.org/headers") as r:
|
||||||
|
json_body = await r.json()
|
||||||
|
print(json_body)
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,25 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import 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}")
|
||||||
|
async with session.post("/post", json={"foo": "bar"}) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
rpost = await resp.text()
|
||||||
|
print(f"POST: {rpost}")
|
||||||
|
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())
|
|
@ -0,0 +1,20 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
params = {"key1": "value1", "key2": "value2"}
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get("http://httpbin.org/get", params=params) as response:
|
||||||
|
expect = "http://httpbin.org/get?key1=value1&key2=value2"
|
||||||
|
assert str(response.url) == expect, f"{response.url} != {expect}"
|
||||||
|
html = await response.text()
|
||||||
|
print(html)
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,44 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
URL = sys.argv[1] # expects a websocket echo server
|
||||||
|
except Exception:
|
||||||
|
URL = "ws://echo.websocket.events"
|
||||||
|
|
||||||
|
|
||||||
|
sslctx = False
|
||||||
|
|
||||||
|
if URL.startswith("wss:"):
|
||||||
|
try:
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
sslctx.verify_mode = ssl.CERT_NONE
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def ws_test_echo(session):
|
||||||
|
async with session.ws_connect(URL, ssl=sslctx) as ws:
|
||||||
|
await ws.send_str("hello world!\r\n")
|
||||||
|
async for msg in ws:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
print(msg.data)
|
||||||
|
|
||||||
|
if "close" in msg.data:
|
||||||
|
break
|
||||||
|
await ws.send_str("close\r\n")
|
||||||
|
await ws.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await ws_test_echo(session)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,53 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
URL = sys.argv[1] # expects a websocket echo server
|
||||||
|
READ_BANNER = False
|
||||||
|
except Exception:
|
||||||
|
URL = "ws://echo.websocket.events"
|
||||||
|
READ_BANNER = True
|
||||||
|
|
||||||
|
|
||||||
|
sslctx = False
|
||||||
|
|
||||||
|
if URL.startswith("wss:"):
|
||||||
|
try:
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
sslctx.verify_mode = ssl.CERT_NONE
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def ws_test_echo(session):
|
||||||
|
async with session.ws_connect(URL, ssl=sslctx) as ws:
|
||||||
|
if READ_BANNER:
|
||||||
|
print(await ws.receive_str())
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await ws.send_str(f"{input('>>> ')}\r\n")
|
||||||
|
|
||||||
|
async for msg in ws:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
print(msg.data, end="")
|
||||||
|
break
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
finally:
|
||||||
|
await ws.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await ws_test_echo(session)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,7 @@
|
||||||
|
metadata(
|
||||||
|
description="HTTP client module for MicroPython asyncio module",
|
||||||
|
version="0.0.1",
|
||||||
|
pypi="aiohttp",
|
||||||
|
)
|
||||||
|
|
||||||
|
package("aiohttp")
|
Ładowanie…
Reference in New Issue