Skip to content

Commit c583e0f

Browse files
committed
Update files to chunk
1 parent ad7aaca commit c583e0f

File tree

2 files changed

+96
-68
lines changed

2 files changed

+96
-68
lines changed

adafruit_requests.py

Lines changed: 94 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646

4747
from adafruit_connection_manager import get_connection_manager
4848

49+
SEEK_END = 2
50+
4951
if not sys.implementation.name == "circuitpython":
5052
from types import TracebackType
5153
from typing import Any, Dict, Optional, Type
@@ -344,14 +346,6 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt
344346
self.close()
345347

346348

347-
def _generate_boundary_str():
348-
hex_characters = "0123456789abcdef"
349-
_boundary = ""
350-
for _ in range(32):
351-
_boundary += random.choice(hex_characters)
352-
return _boundary
353-
354-
355349
class Session:
356350
"""HTTP session that shares sockets and ssl context."""
357351

@@ -366,6 +360,60 @@ def __init__(
366360
self._session_id = session_id
367361
self._last_response = None
368362

363+
def _build_boundary_data(self, files: dict):
364+
boundary_string = self._build_boundary_string()
365+
content_length = 0
366+
boundary_objects = []
367+
368+
for field_name, field_values in files.items():
369+
file_name = field_values[0]
370+
file_data = field_values[1]
371+
372+
boundary_data = f"--{boundary_string}\r\n"
373+
boundary_data += f'Content-Disposition: form-data; name="{field_name}"; '
374+
if file_name is not None:
375+
boundary_data += f'filename="{file_name}"'
376+
boundary_data += "\r\n"
377+
if len(field_values) >= 3:
378+
file_content_type = field_values[2]
379+
boundary_data += f"Content-Type: {file_content_type}\r\n"
380+
if len(field_values) >= 4:
381+
file_headers = field_values[3]
382+
for file_header_key, file_header_value in file_headers.items():
383+
boundary_data += f"{file_header_key}: {file_header_value}\r\n"
384+
boundary_data += "\r\n"
385+
386+
content_length += len(boundary_data)
387+
boundary_objects.append(boundary_data)
388+
389+
if file_name is not None:
390+
file_data.seek(0, SEEK_END)
391+
content_length += file_data.tell()
392+
file_data.seek(0)
393+
boundary_objects.append(file_data)
394+
boundary_data = ""
395+
else:
396+
boundary_data = file_data
397+
398+
boundary_data += "\r\n"
399+
content_length += len(boundary_data)
400+
boundary_objects.append(boundary_data)
401+
402+
boundary_data = f"--{boundary_string}--"
403+
404+
content_length += len(boundary_data)
405+
boundary_objects.append(boundary_data)
406+
407+
return boundary_string, content_length, boundary_objects
408+
409+
@staticmethod
410+
def _build_boundary_string():
411+
hex_characters = "0123456789abcdef"
412+
_boundary = ""
413+
for _ in range(32):
414+
_boundary += random.choice(hex_characters)
415+
return _boundary
416+
369417
@staticmethod
370418
def _check_headers(headers: Dict[str, str]):
371419
if not isinstance(headers, dict):
@@ -399,10 +447,31 @@ def _send(socket: SocketType, data: bytes):
399447
# Not EAGAIN; that was already handled.
400448
raise OSError(errno.EIO)
401449
total_sent += sent
450+
return total_sent
402451

403452
def _send_as_bytes(self, socket: SocketType, data: str):
404453
return self._send(socket, bytes(data, "utf-8"))
405454

455+
def _send_boundary_objects(self, socket: SocketType, boundary_objects: Any):
456+
for boundary_object in boundary_objects:
457+
if isinstance(boundary_object, str):
458+
self._send_as_bytes(socket, boundary_object)
459+
else:
460+
chunk_size = 32
461+
if hasattr(boundary_object, "readinto"):
462+
b = bytearray(chunk_size)
463+
while True:
464+
size = boundary_object.readinto(b)
465+
if size == 0:
466+
break
467+
self._send(socket, b[:size])
468+
else:
469+
while True:
470+
b = boundary_object.read(chunk_size)
471+
if len(b) == 0:
472+
break
473+
self._send(socket, b)
474+
406475
def _send_header(self, socket, header, value):
407476
if value is None:
408477
return
@@ -440,6 +509,7 @@ def _send_request( # pylint: disable=too-many-arguments
440509

441510
# If data is sent and it's a dict, set content type header and convert to string
442511
if data and isinstance(data, dict):
512+
assert files is None
443513
content_type_header = "application/x-www-form-urlencoded"
444514
_post_data = ""
445515
for k in data:
@@ -451,8 +521,18 @@ def _send_request( # pylint: disable=too-many-arguments
451521
if data and isinstance(data, str):
452522
data = bytes(data, "utf-8")
453523

454-
if data is None:
455-
data = b""
524+
# If files are send, build data to send and calculate length
525+
content_length = 0
526+
boundary_objects = None
527+
if files and isinstance(files, dict):
528+
boundary_string, content_length, boundary_objects = (
529+
self._build_boundary_data(files)
530+
)
531+
content_type_header = f"multipart/form-data; boundary={boundary_string}"
532+
else:
533+
if data is None:
534+
data = b""
535+
content_length = len(data)
456536

457537
self._send_as_bytes(socket, method)
458538
self._send(socket, b" /")
@@ -461,60 +541,6 @@ def _send_request( # pylint: disable=too-many-arguments
461541

462542
# create lower-case supplied header list
463543
supplied_headers = {header.lower() for header in headers}
464-
boundary_str = None
465-
466-
# pylint: disable=too-many-nested-blocks
467-
if files is not None and isinstance(files, dict):
468-
boundary_str = _generate_boundary_str()
469-
content_type_header = f"multipart/form-data; boundary={boundary_str}"
470-
471-
for fieldname in files.keys():
472-
if not fieldname.endswith("-name"):
473-
if files[fieldname][0] is not None:
474-
file_content = files[fieldname][1].read()
475-
476-
data += b"--" + boundary_str.encode() + b"\r\n"
477-
data += (
478-
b'Content-Disposition: form-data; name="'
479-
+ fieldname.encode()
480-
+ b'"; filename="'
481-
+ files[fieldname][0].encode()
482-
+ b'"\r\n'
483-
)
484-
if len(files[fieldname]) >= 3:
485-
data += (
486-
b"Content-Type: "
487-
+ files[fieldname][2].encode()
488-
+ b"\r\n"
489-
)
490-
if len(files[fieldname]) >= 4:
491-
for custom_header_key in files[fieldname][3].keys():
492-
data += (
493-
custom_header_key.encode()
494-
+ b": "
495-
+ files[fieldname][3][custom_header_key].encode()
496-
+ b"\r\n"
497-
)
498-
data += b"\r\n"
499-
data += file_content + b"\r\n"
500-
else:
501-
# filename is None
502-
data += b"--" + boundary_str.encode() + b"\r\n"
503-
data += (
504-
b'Content-Disposition: form-data; name="'
505-
+ fieldname.encode()
506-
+ b'"; \r\n'
507-
)
508-
if len(files[fieldname]) >= 3:
509-
data += (
510-
b"Content-Type: "
511-
+ files[fieldname][2].encode()
512-
+ b"\r\n"
513-
)
514-
data += b"\r\n"
515-
data += files[fieldname][1].encode() + b"\r\n"
516-
517-
data += b"--" + boundary_str.encode() + b"--"
518544

519545
# Send headers
520546
if not "host" in supplied_headers:
@@ -523,8 +549,8 @@ def _send_request( # pylint: disable=too-many-arguments
523549
self._send_header(socket, "User-Agent", "Adafruit CircuitPython")
524550
if content_type_header and not "content-type" in supplied_headers:
525551
self._send_header(socket, "Content-Type", content_type_header)
526-
if data and not "content-length" in supplied_headers:
527-
self._send_header(socket, "Content-Length", str(len(data)))
552+
if (data or files) and not "content-length" in supplied_headers:
553+
self._send_header(socket, "Content-Length", str(content_length))
528554
# Iterate over keys to avoid tuple alloc
529555
for header in headers:
530556
self._send_header(socket, header, headers[header])
@@ -533,6 +559,8 @@ def _send_request( # pylint: disable=too-many-arguments
533559
# Send data
534560
if data:
535561
self._send(socket, bytes(data))
562+
elif boundary_objects:
563+
self._send_boundary_objects(socket, boundary_objects)
536564

537565
# pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals
538566
def request(

examples/wifi/expanded/requests_wifi_file_upload.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
1313
requests = adafruit_requests.Session(pool, ssl_context)
1414

15-
with open("raspi_snip.png", "rb") as file_handle:
15+
with open("requests_wifi_file_upload_image.png", "rb") as file_handle:
1616
files = {
1717
"file": (
18-
"raspi_snip.png",
18+
"requests_wifi_file_upload_image.png",
1919
file_handle,
2020
"image/png",
2121
{"CustomHeader": "BlinkaRocks"},

0 commit comments

Comments
 (0)