Skip to content

Commit 5142e0a

Browse files
committed
urequests_2
As a temporary workaround to the issue described in https://github.com/orgs/micropython/discussions/15112, add urequests_2.py to your microcontroller, and change import urequests to import urequests_2 as urequests in the code below. See https://github.com/orgs/micropython/discussions/15112 and micropython/micropython-lib#861 for ongoing updates. The corresponding assignment will also be affected, but this can be addressed using the same workaround.
1 parent 36f22f9 commit 5142e0a

File tree

4 files changed

+236
-1
lines changed

4 files changed

+236
-1
lines changed

lib/urequests_2.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Workaround for the `urequests` module to support HTTP/1.1
2+
# Based on https://github.com/micropython/micropython-lib/blob/e025c843b60e93689f0f991d753010bb5bd6a722/python-ecosys/requests/requests/__init__.py
3+
# See https://github.com/micropython/micropython-lib/pull/861 and https://github.com/orgs/micropython/discussions/15112
4+
# `1.0` replaced with `1.1, i.e.:
5+
# `s.write(b"%s /%s HTTP/1.0\r\n" % (method, path))` changed to `s.write(b"%s /%s HTTP/1.1\r\n" % (method, path))`
6+
import usocket
7+
8+
9+
class Response:
10+
def __init__(self, f):
11+
self.raw = f
12+
self.encoding = "utf-8"
13+
self._cached = None
14+
15+
def close(self):
16+
if self.raw:
17+
self.raw.close()
18+
self.raw = None
19+
self._cached = None
20+
21+
@property
22+
def content(self):
23+
if self._cached is None:
24+
try:
25+
self._cached = self.raw.read()
26+
finally:
27+
self.raw.close()
28+
self.raw = None
29+
return self._cached
30+
31+
@property
32+
def text(self):
33+
return str(self.content, self.encoding)
34+
35+
def json(self):
36+
import ujson
37+
38+
return ujson.loads(self.content)
39+
40+
41+
def request(
42+
method,
43+
url,
44+
data=None,
45+
json=None,
46+
headers={},
47+
stream=None,
48+
auth=None,
49+
timeout=None,
50+
parse_headers=True,
51+
):
52+
redirect = None # redirection url, None means no redirection
53+
chunked_data = (
54+
data and getattr(data, "__next__", None) and not getattr(data, "__len__", None)
55+
)
56+
57+
if auth is not None:
58+
import ubinascii
59+
60+
username, password = auth
61+
formated = b"{}:{}".format(username, password)
62+
formated = str(ubinascii.b2a_base64(formated)[:-1], "ascii")
63+
headers["Authorization"] = "Basic {}".format(formated)
64+
65+
try:
66+
proto, dummy, host, path = url.split("/", 3)
67+
except ValueError:
68+
proto, dummy, host = url.split("/", 2)
69+
path = ""
70+
if proto == "http:":
71+
port = 80
72+
elif proto == "https:":
73+
import ussl
74+
75+
port = 443
76+
else:
77+
raise ValueError("Unsupported protocol: " + proto)
78+
79+
if ":" in host:
80+
host, port = host.split(":", 1)
81+
port = int(port)
82+
83+
ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM)
84+
ai = ai[0]
85+
86+
resp_d = None
87+
if parse_headers is not False:
88+
resp_d = {}
89+
90+
s = usocket.socket(ai[0], usocket.SOCK_STREAM, ai[2])
91+
92+
if timeout is not None:
93+
# Note: settimeout is not supported on all platforms, will raise
94+
# an AttributeError if not available.
95+
s.settimeout(timeout)
96+
97+
try:
98+
s.connect(ai[-1])
99+
if proto == "https:":
100+
s = ussl.wrap_socket(s, server_hostname=host)
101+
s.write(b"%s /%s HTTP/1.1\r\n" % (method, path))
102+
if "Host" not in headers:
103+
s.write(b"Host: %s\r\n" % host)
104+
# Iterate over keys to avoid tuple alloc
105+
for k in headers:
106+
s.write(k)
107+
s.write(b": ")
108+
s.write(headers[k])
109+
s.write(b"\r\n")
110+
if json is not None:
111+
assert data is None
112+
import ujson
113+
114+
data = ujson.dumps(json)
115+
s.write(b"Content-Type: application/json\r\n")
116+
if data:
117+
if chunked_data:
118+
s.write(b"Transfer-Encoding: chunked\r\n")
119+
else:
120+
s.write(b"Content-Length: %d\r\n" % len(data))
121+
s.write(b"Connection: close\r\n\r\n")
122+
if data:
123+
if chunked_data:
124+
for chunk in data:
125+
s.write(b"%x\r\n" % len(chunk))
126+
s.write(chunk)
127+
s.write(b"\r\n")
128+
s.write("0\r\n\r\n")
129+
else:
130+
s.write(data)
131+
132+
l = s.readline()
133+
# print(l)
134+
l = l.split(None, 2)
135+
if len(l) < 2:
136+
# Invalid response
137+
raise ValueError("HTTP error: BadStatusLine:\n%s" % l)
138+
status = int(l[1])
139+
reason = ""
140+
if len(l) > 2:
141+
reason = l[2].rstrip()
142+
while True:
143+
l = s.readline()
144+
if not l or l == b"\r\n":
145+
break
146+
# print(l)
147+
if l.startswith(b"Transfer-Encoding:"):
148+
if b"chunked" in l:
149+
raise ValueError("Unsupported " + str(l, "utf-8"))
150+
elif l.startswith(b"Location:") and not 200 <= status <= 299:
151+
if status in [301, 302, 303, 307, 308]:
152+
redirect = str(l[10:-2], "utf-8")
153+
else:
154+
raise NotImplementedError("Redirect %d not yet supported" % status)
155+
if parse_headers is False:
156+
pass
157+
elif parse_headers is True:
158+
l = str(l, "utf-8")
159+
k, v = l.split(":", 1)
160+
resp_d[k] = v.strip()
161+
else:
162+
parse_headers(l, resp_d)
163+
except OSError:
164+
s.close()
165+
raise
166+
167+
if redirect:
168+
s.close()
169+
if status in [301, 302, 303]:
170+
return request("GET", redirect, None, None, headers, stream)
171+
else:
172+
return request(method, redirect, data, json, headers, stream)
173+
else:
174+
resp = Response(s)
175+
resp.status_code = status
176+
resp.reason = reason
177+
if resp_d is not None:
178+
resp.headers = resp_d
179+
return resp
180+
181+
182+
def head(url, **kw):
183+
return request("HEAD", url, **kw)
184+
185+
186+
def get(url, **kw):
187+
return request("GET", url, **kw)
188+
189+
190+
def post(url, **kw):
191+
return request("POST", url, **kw)
192+
193+
194+
def put(url, **kw):
195+
return request("PUT", url, **kw)
196+
197+
198+
def patch(url, **kw):
199+
return request("PATCH", url, **kw)
200+
201+
202+
def delete(url, **kw):
203+
return request("DELETE", url, **kw)

microcontroller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import ntptime
88
from uio import StringIO
99
from time import time, sleep
10-
import urequests as requests # for MongoDB Data API
10+
import urequests_2 as requests # for MongoDB Data API
1111

1212
# WiFi
1313
from netman import connectWiFi

src/urequests_2/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Mock function that gets installed by requirements.txt"""
2+
3+
from urequests_2._urequests_2 import *

src/urequests_2/_urequests_2.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# urequests_2.py
2+
3+
4+
class Response:
5+
def __init__(self, text="", status_code=200):
6+
self.text = text
7+
self.status_code = status_code
8+
9+
def json(self):
10+
return {}
11+
12+
def close(self):
13+
pass
14+
15+
16+
def get(url, **kwargs):
17+
return Response()
18+
19+
20+
def post(url, **kwargs):
21+
return Response()
22+
23+
24+
def put(url, **kwargs):
25+
return Response()
26+
27+
28+
def delete(url, **kwargs):
29+
return Response()

0 commit comments

Comments
 (0)