|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import usocket |
|
|
|
|
|
|
|
|
class Response: |
|
|
def __init__(self, f): |
|
|
self.raw = f |
|
|
self.encoding = "utf-8" |
|
|
self._cached = None |
|
|
|
|
|
def close(self): |
|
|
if self.raw: |
|
|
self.raw.close() |
|
|
self.raw = None |
|
|
self._cached = None |
|
|
|
|
|
@property |
|
|
def content(self): |
|
|
if self._cached is None: |
|
|
try: |
|
|
self._cached = self.raw.read() |
|
|
finally: |
|
|
self.raw.close() |
|
|
self.raw = None |
|
|
return self._cached |
|
|
|
|
|
@property |
|
|
def text(self): |
|
|
return str(self.content, self.encoding) |
|
|
|
|
|
def json(self): |
|
|
import ujson |
|
|
|
|
|
return ujson.loads(self.content) |
|
|
|
|
|
|
|
|
def request( |
|
|
method, |
|
|
url, |
|
|
data=None, |
|
|
json=None, |
|
|
headers={}, |
|
|
stream=None, |
|
|
auth=None, |
|
|
timeout=None, |
|
|
parse_headers=True, |
|
|
): |
|
|
redirect = None |
|
|
chunked_data = ( |
|
|
data and getattr(data, "__next__", None) and not getattr(data, "__len__", None) |
|
|
) |
|
|
|
|
|
if auth is not None: |
|
|
import ubinascii |
|
|
|
|
|
username, password = auth |
|
|
formated = b"{}:{}".format(username, password) |
|
|
formated = str(ubinascii.b2a_base64(formated)[:-1], "ascii") |
|
|
headers["Authorization"] = "Basic {}".format(formated) |
|
|
|
|
|
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:": |
|
|
import ussl |
|
|
|
|
|
port = 443 |
|
|
else: |
|
|
raise ValueError("Unsupported protocol: " + proto) |
|
|
|
|
|
if ":" in host: |
|
|
host, port = host.split(":", 1) |
|
|
port = int(port) |
|
|
|
|
|
ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) |
|
|
ai = ai[0] |
|
|
|
|
|
resp_d = None |
|
|
if parse_headers is not False: |
|
|
resp_d = {} |
|
|
|
|
|
s = usocket.socket(ai[0], usocket.SOCK_STREAM, ai[2]) |
|
|
|
|
|
if timeout is not None: |
|
|
|
|
|
|
|
|
s.settimeout(timeout) |
|
|
|
|
|
try: |
|
|
s.connect(ai[-1]) |
|
|
if proto == "https:": |
|
|
s = ussl.wrap_socket(s, server_hostname=host) |
|
|
s.write(b"%s /%s HTTP/1.1\r\n" % (method, path)) |
|
|
if "Host" not in headers: |
|
|
s.write(b"Host: %s\r\n" % host) |
|
|
|
|
|
for k in headers: |
|
|
s.write(k) |
|
|
s.write(b": ") |
|
|
s.write(headers[k]) |
|
|
s.write(b"\r\n") |
|
|
if json is not None: |
|
|
assert data is None |
|
|
import ujson |
|
|
|
|
|
data = ujson.dumps(json) |
|
|
s.write(b"Content-Type: application/json\r\n") |
|
|
if data: |
|
|
if chunked_data: |
|
|
s.write(b"Transfer-Encoding: chunked\r\n") |
|
|
else: |
|
|
s.write(b"Content-Length: %d\r\n" % len(data)) |
|
|
s.write(b"Connection: close\r\n\r\n") |
|
|
if data: |
|
|
if chunked_data: |
|
|
for chunk in data: |
|
|
s.write(b"%x\r\n" % len(chunk)) |
|
|
s.write(chunk) |
|
|
s.write(b"\r\n") |
|
|
s.write("0\r\n\r\n") |
|
|
else: |
|
|
s.write(data) |
|
|
|
|
|
l = s.readline() |
|
|
|
|
|
l = l.split(None, 2) |
|
|
if len(l) < 2: |
|
|
|
|
|
raise ValueError("HTTP error: BadStatusLine:\n%s" % l) |
|
|
status = int(l[1]) |
|
|
reason = "" |
|
|
if len(l) > 2: |
|
|
reason = l[2].rstrip() |
|
|
while True: |
|
|
l = s.readline() |
|
|
if not l or l == b"\r\n": |
|
|
break |
|
|
|
|
|
if l.startswith(b"Transfer-Encoding:"): |
|
|
if b"chunked" in l: |
|
|
raise ValueError("Unsupported " + str(l, "utf-8")) |
|
|
elif l.startswith(b"Location:") and not 200 <= status <= 299: |
|
|
if status in [301, 302, 303, 307, 308]: |
|
|
redirect = str(l[10:-2], "utf-8") |
|
|
else: |
|
|
raise NotImplementedError("Redirect %d not yet supported" % status) |
|
|
if parse_headers is False: |
|
|
pass |
|
|
elif parse_headers is True: |
|
|
l = str(l, "utf-8") |
|
|
k, v = l.split(":", 1) |
|
|
resp_d[k] = v.strip() |
|
|
else: |
|
|
parse_headers(l, resp_d) |
|
|
except OSError: |
|
|
s.close() |
|
|
raise |
|
|
|
|
|
if redirect: |
|
|
s.close() |
|
|
if status in [301, 302, 303]: |
|
|
return request("GET", redirect, None, None, headers, stream) |
|
|
else: |
|
|
return request(method, redirect, data, json, headers, stream) |
|
|
else: |
|
|
resp = Response(s) |
|
|
resp.status_code = status |
|
|
resp.reason = reason |
|
|
if resp_d is not None: |
|
|
resp.headers = resp_d |
|
|
return resp |
|
|
|
|
|
|
|
|
def head(url, **kw): |
|
|
return request("HEAD", url, **kw) |
|
|
|
|
|
|
|
|
def get(url, **kw): |
|
|
return request("GET", url, **kw) |
|
|
|
|
|
|
|
|
def post(url, **kw): |
|
|
return request("POST", url, **kw) |
|
|
|
|
|
|
|
|
def put(url, **kw): |
|
|
return request("PUT", url, **kw) |
|
|
|
|
|
|
|
|
def patch(url, **kw): |
|
|
return request("PATCH", url, **kw) |
|
|
|
|
|
|
|
|
def delete(url, **kw): |
|
|
return request("DELETE", url, **kw) |
|
|
|