@@ -45,7 +45,6 @@ def cast(_t, value):
45
45
"""No-op shim for the typing.cast() function which is not available in CircuitPython."""
46
46
return value
47
47
48
-
49
48
else :
50
49
from ssl import SSLContext
51
50
from types import ModuleType , TracebackType
@@ -148,16 +147,6 @@ def TLS_MODE(self) -> int: # pylint: disable=invalid-name
148
147
SSLContextType = Union [SSLContext , "_FakeSSLContext" ]
149
148
150
149
151
- # CircuitPython 6.0 does not have the bytearray.split method.
152
- # This function emulates buf.split(needle)[0], which is the functionality
153
- # required.
154
- def _buffer_split0 (buf : Union [bytes , bytearray ], needle : Union [bytes , bytearray ]):
155
- index = buf .find (needle )
156
- if index == - 1 :
157
- return buf
158
- return buf [:index ]
159
-
160
-
161
150
class _RawResponse :
162
151
def __init__ (self , response : "Response" ) -> None :
163
152
self ._response = response
@@ -177,10 +166,6 @@ def readinto(self, buf: bytearray) -> int:
177
166
return self ._response ._readinto (buf ) # pylint: disable=protected-access
178
167
179
168
180
- class _SendFailed (Exception ):
181
- """Custom exception to abort sending a request."""
182
-
183
-
184
169
class OutOfRetries (Exception ):
185
170
"""Raised when requests has retried to make a request unsuccessfully."""
186
171
@@ -240,56 +225,25 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int:
240
225
return read_size
241
226
return cast ("SupportsRecvInto" , self .socket ).recv_into (buf , size )
242
227
243
- @staticmethod
244
- def _find (buf : bytes , needle : bytes , start : int , end : int ) -> int :
245
- if hasattr (buf , "find" ):
246
- return buf .find (needle , start , end )
247
- result = - 1
248
- i = start
249
- while i < end :
250
- j = 0
251
- while j < len (needle ) and i + j < end and buf [i + j ] == needle [j ]:
252
- j += 1
253
- if j == len (needle ):
254
- result = i
255
- break
256
- i += 1
257
-
258
- return result
259
-
260
- def _readto (self , first : bytes , second : bytes = b"" ) -> bytes :
228
+ def _readto (self , stop : bytes ) -> bytearray :
261
229
buf = self ._receive_buffer
262
230
end = self ._received_length
263
231
while True :
264
- firsti = self ._find (buf , first , 0 , end )
265
- secondi = - 1
266
- if second :
267
- secondi = self ._find (buf , second , 0 , end )
268
-
269
- i = - 1
270
- needle_len = 0
271
- if firsti >= 0 :
272
- i = firsti
273
- needle_len = len (first )
274
- if secondi >= 0 and (firsti < 0 or secondi < firsti ):
275
- i = secondi
276
- needle_len = len (second )
232
+ i = buf .find (stop , 0 , end )
277
233
if i >= 0 :
234
+ # Stop was found. Return everything up to but not including stop.
278
235
result = buf [:i ]
279
- new_start = i + needle_len
280
-
281
- if i + needle_len <= end :
282
- new_end = end - new_start
283
- buf [:new_end ] = buf [new_start :end ]
284
- self ._received_length = new_end
236
+ new_start = i + len (stop )
237
+ # Remove everything up to and including stop from the buffer.
238
+ new_end = end - new_start
239
+ buf [:new_end ] = buf [new_start :end ]
240
+ self ._received_length = new_end
285
241
return result
286
242
287
- # Not found so load more.
288
-
243
+ # Not found so load more bytes.
289
244
# If our buffer is full, then make it bigger to load more.
290
245
if end == len (buf ):
291
- new_size = len (buf ) + 32
292
- new_buf = bytearray (new_size )
246
+ new_buf = bytearray (len (buf ) + 32 )
293
247
new_buf [: len (buf )] = buf
294
248
buf = new_buf
295
249
self ._receive_buffer = buf
@@ -300,8 +254,6 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes:
300
254
return buf [:end ]
301
255
end += read
302
256
303
- return b""
304
-
305
257
def _read_from_buffer (
306
258
self , buf : Optional [bytearray ] = None , nbytes : Optional [int ] = None
307
259
) -> int :
@@ -333,7 +285,7 @@ def _readinto(self, buf: bytearray) -> int:
333
285
# Consume trailing \r\n for chunks 2+
334
286
if self ._remaining == 0 :
335
287
self ._throw_away (2 )
336
- chunk_header = _buffer_split0 (self ._readto (b"\r \n " ), b";" )
288
+ chunk_header = bytes (self ._readto (b"\r \n " )). split ( b";" , 1 )[ 0 ]
337
289
http_chunk_size = int (bytes (chunk_header ), 16 )
338
290
if http_chunk_size == 0 :
339
291
self ._chunked = False
@@ -374,7 +326,7 @@ def close(self) -> None:
374
326
self ._throw_away (self ._remaining )
375
327
elif self ._chunked :
376
328
while True :
377
- chunk_header = _buffer_split0 (self ._readto (b"\r \n " ), b";" )
329
+ chunk_header = bytes (self ._readto (b"\r \n " )). split ( b";" , 1 )[ 0 ]
378
330
chunk_size = int (bytes (chunk_header ), 16 )
379
331
if chunk_size == 0 :
380
332
break
@@ -392,11 +344,10 @@ def _parse_headers(self) -> None:
392
344
Expects first line of HTTP request/response to have been read already.
393
345
"""
394
346
while True :
395
- title = self ._readto (b": " , b"\r \n " )
396
- if not title :
347
+ header = self ._readto (b"\r \n " )
348
+ if not header :
397
349
break
398
-
399
- content = self ._readto (b"\r \n " )
350
+ title , content = bytes (header ).split (b": " , 1 )
400
351
if title and content :
401
352
# enforce that all headers are lowercase
402
353
title = str (title , "utf-8" ).lower ()
@@ -407,6 +358,17 @@ def _parse_headers(self) -> None:
407
358
self ._chunked = content .strip ().lower () == "chunked"
408
359
self ._headers [title ] = content
409
360
361
+ def _validate_not_gzip (self ) -> None :
362
+ """gzip encoding is not supported. Raise an exception if found."""
363
+ if (
364
+ "content-encoding" in self .headers
365
+ and self .headers ["content-encoding" ] == "gzip"
366
+ ):
367
+ raise ValueError (
368
+ "Content-encoding is gzip, data cannot be accessed as json or text. "
369
+ "Use content property to access raw bytes."
370
+ )
371
+
410
372
@property
411
373
def headers (self ) -> Dict [str , str ]:
412
374
"""
@@ -435,14 +397,8 @@ def text(self) -> str:
435
397
return self ._cached
436
398
raise RuntimeError ("Cannot access text after getting content or json" )
437
399
438
- if (
439
- "content-encoding" in self .headers
440
- and self .headers ["content-encoding" ] == "gzip"
441
- ):
442
- raise ValueError (
443
- "Content-encoding is gzip, data cannot be accessed as json or text. "
444
- "Use content property to access raw bytes."
445
- )
400
+ self ._validate_not_gzip ()
401
+
446
402
self ._cached = str (self .content , self .encoding )
447
403
return self ._cached
448
404
@@ -459,20 +415,9 @@ def json(self) -> Any:
459
415
if not self ._raw :
460
416
self ._raw = _RawResponse (self )
461
417
462
- if (
463
- "content-encoding" in self .headers
464
- and self .headers ["content-encoding" ] == "gzip"
465
- ):
466
- raise ValueError (
467
- "Content-encoding is gzip, data cannot be accessed as json or text. "
468
- "Use content property to access raw bytes."
469
- )
470
- try :
471
- obj = json .load (self ._raw )
472
- except OSError :
473
- # <5.3.1 doesn't piecemeal load json from any object with readinto so load the whole
474
- # string.
475
- obj = json .loads (self ._raw .read ())
418
+ self ._validate_not_gzip ()
419
+
420
+ obj = json .load (self ._raw )
476
421
if not self ._cached :
477
422
self ._cached = obj
478
423
self .close ()
@@ -599,12 +544,19 @@ def _send(socket: SocketType, data: bytes):
599
544
# ESP32SPI sockets raise a RuntimeError when unable to send.
600
545
try :
601
546
sent = socket .send (data [total_sent :])
602
- except RuntimeError :
603
- sent = 0
547
+ except OSError as exc :
548
+ if exc .errno == errno .EAGAIN :
549
+ # Can't send right now (e.g., no buffer space), try again.
550
+ continue
551
+ # Some worse error.
552
+ raise
553
+ except RuntimeError as exc :
554
+ raise OSError (errno .EIO ) from exc
604
555
if sent is None :
605
556
sent = len (data )
606
557
if sent == 0 :
607
- raise _SendFailed ()
558
+ # Not EAGAIN; that was already handled.
559
+ raise OSError (errno .EIO )
608
560
total_sent += sent
609
561
610
562
def _send_request (
@@ -637,11 +589,9 @@ def _send_request(
637
589
if json is not None :
638
590
assert data is None
639
591
# pylint: disable=import-outside-toplevel
640
- try :
641
- import json as json_module
642
- except ImportError :
643
- import ujson as json_module
644
- data = json_module .dumps (json )
592
+ import json
593
+
594
+ data = json .dumps (json )
645
595
self ._send (socket , b"Content-Type: application/json\r \n " )
646
596
if data :
647
597
if isinstance (data , dict ):
@@ -711,7 +661,7 @@ def request(
711
661
ok = True
712
662
try :
713
663
self ._send_request (socket , host , method , path , headers , data , json )
714
- except ( _SendFailed , OSError ) :
664
+ except OSError :
715
665
ok = False
716
666
if ok :
717
667
# Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work
0 commit comments