Browse Source

上传文件至 'src/lib'

master
nfa 4 years ago
parent
commit
4f7bc8c62e
  1. BIN
      src/lib/adafruit_dht.mpy
  2. 669
      src/lib/adafruit_requests.py

BIN
src/lib/adafruit_dht.mpy

Binary file not shown.

669
src/lib/adafruit_requests.py

@ -0,0 +1,669 @@
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git"
import errno
# CircuitPython 6.0 does not have the bytearray.split method.
# This function emulates buf.split(needle)[0], which is the functionality
# required.
def _buffer_split0(buf, needle):
index = buf.find(needle)
if index == -1:
return buf
return buf[:index]
class _RawResponse:
def __init__(self, response):
self._response = response
def read(self, size=-1):
"""Read as much as available or up to size and return it in a byte string.
Do NOT use this unless you really need to. Reusing memory with `readinto` is much better.
"""
if size == -1:
return self._response.content
return self._response.socket.recv(size)
def readinto(self, buf):
"""Read as much as available into buf or until it is full. Returns the number of bytes read
into buf."""
return self._response._readinto(buf) # pylint: disable=protected-access
class _SendFailed(Exception):
"""Custom exception to abort sending a request."""
class OutOfRetries(Exception):
"""Raised when requests has retried to make a request unsuccessfully."""
class Response:
"""The response from a request, contains all the headers/content"""
# pylint: disable=too-many-instance-attributes
encoding = None
def __init__(self, sock, session=None):
self.socket = sock
self.encoding = "utf-8"
self._cached = None
self._headers = {}
# _start_index and _receive_buffer are used when parsing headers.
# _receive_buffer will grow by 32 bytes everytime it is too small.
self._received_length = 0
self._receive_buffer = bytearray(32)
self._remaining = None
self._chunked = False
self._backwards_compatible = not hasattr(sock, "recv_into")
http = self._readto(b" ")
if not http:
if session:
session._close_socket(self.socket)
else:
self.socket.close()
raise RuntimeError("Unable to read HTTP response.")
self.status_code = int(bytes(self._readto(b" ")))
self.reason = self._readto(b"\r\n")
self._parse_headers()
self._raw = None
self._session = session
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _recv_into(self, buf, size=0):
if self._backwards_compatible:
size = len(buf) if size == 0 else size
b = self.socket.recv(size)
read_size = len(b)
buf[:read_size] = b
return read_size
return self.socket.recv_into(buf, size)
@staticmethod
def _find(buf, needle, start, end):
if hasattr(buf, "find"):
return buf.find(needle, start, end)
result = -1
i = start
while i < end:
j = 0
while j < len(needle) and i + j < end and buf[i + j] == needle[j]:
j += 1
if j == len(needle):
result = i
break
i += 1
return result
def _readto(self, first, second=b""):
buf = self._receive_buffer
end = self._received_length
while True:
firsti = self._find(buf, first, 0, end)
secondi = -1
if second:
secondi = self._find(buf, second, 0, end)
i = -1
needle_len = 0
if firsti >= 0:
i = firsti
needle_len = len(first)
if secondi >= 0 and (firsti < 0 or secondi < firsti):
i = secondi
needle_len = len(second)
if i >= 0:
result = buf[:i]
new_start = i + needle_len
if i + needle_len <= end:
new_end = end - new_start
buf[:new_end] = buf[new_start:end]
self._received_length = new_end
return result
# Not found so load more.
# If our buffer is full, then make it bigger to load more.
if end == len(buf):
new_size = len(buf) + 32
new_buf = bytearray(new_size)
new_buf[: len(buf)] = buf
buf = new_buf
self._receive_buffer = buf
read = self._recv_into(memoryview(buf)[end:])
if read == 0:
self._received_length = 0
return buf[:end]
end += read
return b""
def _read_from_buffer(self, buf=None, nbytes=None):
if self._received_length == 0:
return 0
read = self._received_length
if nbytes < read:
read = nbytes
membuf = memoryview(self._receive_buffer)
if buf:
buf[:read] = membuf[:read]
if read < self._received_length:
new_end = self._received_length - read
self._receive_buffer[:new_end] = membuf[read : self._received_length]
self._received_length = new_end
else:
self._received_length = 0
return read
def _readinto(self, buf):
if not self.socket:
raise RuntimeError(
"Newer Response closed this one. Use Responses immediately."
)
if not self._remaining:
# Consume the chunk header if need be.
if self._chunked:
# Consume trailing \r\n for chunks 2+
if self._remaining == 0:
self._throw_away(2)
chunk_header = _buffer_split0(self._readto(b"\r\n"), b";")
http_chunk_size = int(bytes(chunk_header), 16)
if http_chunk_size == 0:
self._chunked = False
self._parse_headers()
return 0
self._remaining = http_chunk_size
else:
return 0
nbytes = len(buf)
if nbytes > self._remaining:
nbytes = self._remaining
read = self._read_from_buffer(buf, nbytes)
if read == 0:
read = self._recv_into(buf, nbytes)
self._remaining -= read
return read
def _throw_away(self, nbytes):
nbytes -= self._read_from_buffer(nbytes=nbytes)
buf = self._receive_buffer
for _ in range(nbytes // len(buf)):
self._recv_into(buf)
remaining = nbytes % len(buf)
if remaining:
self._recv_into(buf, remaining)
def close(self):
"""Drain the remaining ESP socket buffers. We assume we already got what we wanted."""
if not self.socket:
return
# Make sure we've read all of our response.
if self._cached is None:
if self._remaining and self._remaining > 0:
self._throw_away(self._remaining)
elif self._chunked:
while True:
chunk_header = _buffer_split0(self._readto(b"\r\n"), b";")
chunk_size = int(bytes(chunk_header), 16)
if chunk_size == 0:
break
self._throw_away(chunk_size + 2)
self._parse_headers()
if self._session:
self._session._free_socket(self.socket) # pylint: disable=protected-access
else:
self.socket.close()
self.socket = None
def _parse_headers(self):
"""
Parses the header portion of an HTTP request/response from the socket.
Expects first line of HTTP request/response to have been read already.
"""
while True:
title = self._readto(b": ", b"\r\n")
if not title:
break
content = self._readto(b"\r\n")
if title and content:
title = str(title, "utf-8")
content = str(content, "utf-8")
# Check len first so we can skip the .lower allocation most of the time.
if (
len(title) == len("content-length")
and title.lower() == "content-length"
):
self._remaining = int(content)
if (
len(title) == len("transfer-encoding")
and title.lower() == "transfer-encoding"
):
self._chunked = content.strip().lower() == "chunked"
self._headers[title] = content
@property
def headers(self):
"""
The response headers. Does not include headers from the trailer until
the content has been read.
"""
return self._headers
@property
def content(self):
"""The HTTP content direct from the socket, as bytes"""
if self._cached is not None:
if isinstance(self._cached, bytes):
return self._cached
raise RuntimeError("Cannot access content after getting text or json")
self._cached = b"".join(self.iter_content(chunk_size=32))
return self._cached
@property
def text(self):
"""The HTTP content, encoded into a string according to the HTTP
header encoding"""
if self._cached is not None:
if isinstance(self._cached, str):
return self._cached
raise RuntimeError("Cannot access text after getting content or json")
self._cached = str(self.content, self.encoding)
return self._cached
def json(self):
"""The HTTP content, parsed into a json dictionary"""
# pylint: disable=import-outside-toplevel
import json
# The cached JSON will be a list or dictionary.
if self._cached:
if isinstance(self._cached, (list, dict)):
return self._cached
raise RuntimeError("Cannot access json after getting text or content")
if not self._raw:
self._raw = _RawResponse(self)
try:
obj = json.load(self._raw)
except OSError:
# <5.3.1 doesn't piecemeal load json from any object with readinto so load the whole
# string.
obj = json.loads(self._raw.read())
if not self._cached:
self._cached = obj
self.close()
return obj
def iter_content(self, chunk_size=1, decode_unicode=False):
"""An iterator that will stream data by only reading 'chunk_size'
bytes and yielding them, when we can't buffer the whole datastream"""
if decode_unicode:
raise NotImplementedError("Unicode not supported")
b = bytearray(chunk_size)
while True:
size = self._readinto(b)
if size == 0:
break
if size < chunk_size:
chunk = bytes(memoryview(b)[:size])
else:
chunk = bytes(b)
yield chunk
self.close()
class Session:
"""HTTP session that shares sockets and ssl context."""
def __init__(self, socket_pool, ssl_context=None):
self._socket_pool = socket_pool
self._ssl_context = ssl_context
# Hang onto open sockets so that we can reuse them.
self._open_sockets = {}
self._socket_free = {}
self._last_response = None
def _free_socket(self, socket):
if socket not in self._open_sockets.values():
raise RuntimeError("Socket not from session")
self._socket_free[socket] = True
def _close_socket(self, sock):
sock.close()
del self._socket_free[sock]
key = None
for k in self._open_sockets:
if self._open_sockets[k] == sock:
key = k
break
if key:
del self._open_sockets[key]
def _free_sockets(self):
free_sockets = []
for sock in self._socket_free:
if self._socket_free[sock]:
free_sockets.append(sock)
for sock in free_sockets:
self._close_socket(sock)
def _get_socket(self, host, port, proto, *, timeout=1):
# pylint: disable=too-many-branches
key = (host, port, proto)
if key in self._open_sockets:
sock = self._open_sockets[key]
if self._socket_free[sock]:
self._socket_free[sock] = False
return sock
if proto == "https:" and not self._ssl_context:
raise RuntimeError(
"ssl_context must be set before using adafruit_requests for https"
)
addr_info = self._socket_pool.getaddrinfo(
host, port, 0, self._socket_pool.SOCK_STREAM
)[0]
retry_count = 0
sock = None
while retry_count < 5 and sock is None:
if retry_count > 0:
if any(self._socket_free.items()):
self._free_sockets()
else:
raise RuntimeError("Sending request failed")
retry_count += 1
try:
sock = self._socket_pool.socket(
addr_info[0], addr_info[1], addr_info[2]
)
except OSError:
continue
except RuntimeError:
continue
connect_host = addr_info[-1][0]
if proto == "https:":
sock = self._ssl_context.wrap_socket(sock, server_hostname=host)
connect_host = host
sock.settimeout(timeout) # socket read timeout
try:
sock.connect((connect_host, port))
except MemoryError:
sock.close()
sock = None
except OSError:
sock.close()
sock = None
if sock is None:
raise RuntimeError("Repeated socket failures")
self._open_sockets[key] = sock
self._socket_free[sock] = False
return sock
@staticmethod
def _send(socket, data):
total_sent = 0
while total_sent < len(data):
# ESP32SPI sockets raise a RuntimeError when unable to send.
try:
sent = socket.send(data[total_sent:])
except RuntimeError:
sent = 0
if sent is None:
sent = len(data)
if sent == 0:
raise _SendFailed()
total_sent += sent
def _send_request(self, socket, host, method, path, headers, data, json):
# pylint: disable=too-many-arguments
self._send(socket, bytes(method, "utf-8"))
self._send(socket, b" /")
self._send(socket, bytes(path, "utf-8"))
self._send(socket, b" HTTP/1.1\r\n")
if "Host" not in headers:
self._send(socket, b"Host: ")
self._send(socket, bytes(host, "utf-8"))
self._send(socket, b"\r\n")
if "User-Agent" not in headers:
self._send(socket, b"User-Agent: Adafruit CircuitPython\r\n")
# Iterate over keys to avoid tuple alloc
for k in headers:
self._send(socket, k.encode())
self._send(socket, b": ")
self._send(socket, headers[k].encode())
self._send(socket, b"\r\n")
if json is not None:
assert data is None
# pylint: disable=import-outside-toplevel
try:
import json as json_module
except ImportError:
import ujson as json_module
data = json_module.dumps(json)
self._send(socket, b"Content-Type: application/json\r\n")
if data:
if isinstance(data, dict):
self._send(
socket, b"Content-Type: application/x-www-form-urlencoded\r\n"
)
_post_data = ""
for k in data:
_post_data = "{}&{}={}".format(_post_data, k, data[k])
data = _post_data[1:]
self._send(socket, b"Content-Length: %d\r\n" % len(data))
self._send(socket, b"\r\n")
if data:
if isinstance(data, bytearray):
self._send(socket, bytes(data))
else:
self._send(socket, bytes(data, "utf-8"))
# pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals
def request(
self, method, url, data=None, json=None, headers=None, stream=False, timeout=60
):
"""Perform an HTTP request to the given url which we will parse to determine
whether to use SSL ('https://') or not. We can also send some provided 'data'
or a json dictionary which we will stringify. 'headers' is optional HTTP headers
sent along. 'stream' will determine if we buffer everything, or whether to only
read only when requested
"""
if not headers:
headers = {}
try:
proto, dummy, host, path = url.split("/", 3)
# replace spaces in path
path = path.replace(" ", "%20")
except ValueError:
proto, dummy, host = url.split("/", 2)
path = ""
if proto == "http:":
port = 80
elif proto == "https:":
port = 443
else:
raise ValueError("Unsupported protocol: " + proto)
if ":" in host:
host, port = host.split(":", 1)
port = int(port)
if self._last_response:
self._last_response.close()
self._last_response = None
# We may fail to send the request if the socket we got is closed already. So, try a second
# time in that case.
retry_count = 0
while retry_count < 2:
retry_count += 1
socket = self._get_socket(host, port, proto, timeout=timeout)
ok = True
try:
self._send_request(socket, host, method, path, headers, data, json)
except (_SendFailed, OSError):
ok = False
if ok:
# Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work
# even when the socket is closed.
if hasattr(socket, "recv"):
result = socket.recv(1)
else:
result = bytearray(1)
try:
socket.recv_into(result)
except OSError:
pass
if result == b"H":
# Things seem to be ok so break with socket set.
break
self._close_socket(socket)
socket = None
if not socket:
raise OutOfRetries("Repeated socket failures")
resp = Response(socket, self) # our response
if "location" in resp.headers and 300 <= resp.status_code <= 399:
raise NotImplementedError("Redirects not yet supported")
self._last_response = resp
return resp
def head(self, url, **kw):
"""Send HTTP HEAD request"""
return self.request("HEAD", url, **kw)
def get(self, url, **kw):
"""Send HTTP GET request"""
return self.request("GET", url, **kw)
def post(self, url, **kw):
"""Send HTTP POST request"""
return self.request("POST", url, **kw)
def put(self, url, **kw):
"""Send HTTP PUT request"""
return self.request("PUT", url, **kw)
def patch(self, url, **kw):
"""Send HTTP PATCH request"""
return self.request("PATCH", url, **kw)
def delete(self, url, **kw):
"""Send HTTP DELETE request"""
return self.request("DELETE", url, **kw)
# Backwards compatible API:
_default_session = None # pylint: disable=invalid-name
class _FakeSSLSocket:
def __init__(self, socket, tls_mode):
self._socket = socket
self._mode = tls_mode
self.settimeout = socket.settimeout
self.send = socket.send
self.recv = socket.recv
self.close = socket.close
def connect(self, address):
"""connect wrapper to add non-standard mode parameter"""
try:
return self._socket.connect(address, self._mode)
except RuntimeError as error:
raise OSError(errno.ENOMEM) from error
class _FakeSSLContext:
def __init__(self, iface):
self._iface = iface
def wrap_socket(self, socket, server_hostname=None):
"""Return the same socket"""
# pylint: disable=unused-argument
return _FakeSSLSocket(socket, self._iface.TLS_MODE)
def set_socket(sock, iface=None):
"""Legacy API for setting the socket and network interface. Use a `Session` instead."""
global _default_session # pylint: disable=global-statement,invalid-name
if not iface:
# pylint: disable=protected-access
_default_session = Session(sock, _FakeSSLContext(sock._the_interface))
else:
_default_session = Session(sock, _FakeSSLContext(iface))
sock.set_interface(iface)
def request(method, url, data=None, json=None, headers=None, stream=False, timeout=0.1):
"""Send HTTP request"""
# pylint: disable=too-many-arguments
_default_session.request(
method,
url,
data=data,
json=json,
headers=headers,
stream=stream,
timeout=timeout,
)
def head(url, **kw):
"""Send HTTP HEAD request"""
return _default_session.request("HEAD", url, **kw)
def get(url, **kw):
"""Send HTTP GET request"""
return _default_session.request("GET", url, **kw)
def post(url, **kw):
"""Send HTTP POST request"""
return _default_session.request("POST", url, **kw)
def put(url, **kw):
"""Send HTTP PUT request"""
return _default_session.request("PUT", url, **kw)
def patch(url, **kw):
"""Send HTTP PATCH request"""
return _default_session.request("PATCH", url, **kw)
def delete(url, **kw):
"""Send HTTP DELETE request"""
return _default_session.request("DELETE", url, **kw)
Loading…
Cancel
Save