39
39
import errno
40
40
import sys
41
41
42
+ import json as json_module
43
+
42
44
if sys .implementation .name == "circuitpython" :
43
45
44
46
def cast (_t , value ):
45
47
"""No-op shim for the typing.cast() function which is not available in CircuitPython."""
46
48
return value
47
49
48
-
49
50
else :
50
51
from ssl import SSLContext
51
52
from types import ModuleType , TracebackType
@@ -148,16 +149,6 @@ def TLS_MODE(self) -> int: # pylint: disable=invalid-name
148
149
SSLContextType = Union [SSLContext , "_FakeSSLContext" ]
149
150
150
151
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
152
class _RawResponse :
162
153
def __init__ (self , response : "Response" ) -> None :
163
154
self ._response = response
@@ -177,10 +168,6 @@ def readinto(self, buf: bytearray) -> int:
177
168
return self ._response ._readinto (buf ) # pylint: disable=protected-access
178
169
179
170
180
- class _SendFailed (Exception ):
181
- """Custom exception to abort sending a request."""
182
-
183
-
184
171
class OutOfRetries (Exception ):
185
172
"""Raised when requests has retried to make a request unsuccessfully."""
186
173
@@ -240,56 +227,25 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int:
240
227
return read_size
241
228
return cast ("SupportsRecvInto" , self .socket ).recv_into (buf , size )
242
229
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 :
230
+ def _readto (self , stop : bytes ) -> bytearray :
261
231
buf = self ._receive_buffer
262
232
end = self ._received_length
263
233
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 )
234
+ i = buf .find (stop , 0 , end )
277
235
if i >= 0 :
236
+ # Stop was found. Return everything up to but not including stop.
278
237
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
238
+ new_start = i + len (stop )
239
+ # Remove everything up to and including stop from the buffer.
240
+ new_end = end - new_start
241
+ buf [:new_end ] = buf [new_start :end ]
242
+ self ._received_length = new_end
285
243
return result
286
244
287
- # Not found so load more.
288
-
245
+ # Not found so load more bytes.
289
246
# If our buffer is full, then make it bigger to load more.
290
247
if end == len (buf ):
291
- new_size = len (buf ) + 32
292
- new_buf = bytearray (new_size )
248
+ new_buf = bytearray (len (buf ) + 32 )
293
249
new_buf [: len (buf )] = buf
294
250
buf = new_buf
295
251
self ._receive_buffer = buf
@@ -300,8 +256,6 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes:
300
256
return buf [:end ]
301
257
end += read
302
258
303
- return b""
304
-
305
259
def _read_from_buffer (
306
260
self , buf : Optional [bytearray ] = None , nbytes : Optional [int ] = None
307
261
) -> int :
@@ -333,7 +287,7 @@ def _readinto(self, buf: bytearray) -> int:
333
287
# Consume trailing \r\n for chunks 2+
334
288
if self ._remaining == 0 :
335
289
self ._throw_away (2 )
336
- chunk_header = _buffer_split0 (self ._readto (b"\r \n " ), b";" )
290
+ chunk_header = bytes (self ._readto (b"\r \n " )). split ( b";" , 1 )[ 0 ]
337
291
http_chunk_size = int (bytes (chunk_header ), 16 )
338
292
if http_chunk_size == 0 :
339
293
self ._chunked = False
@@ -374,7 +328,7 @@ def close(self) -> None:
374
328
self ._throw_away (self ._remaining )
375
329
elif self ._chunked :
376
330
while True :
377
- chunk_header = _buffer_split0 (self ._readto (b"\r \n " ), b";" )
331
+ chunk_header = bytes (self ._readto (b"\r \n " )). split ( b";" , 1 )[ 0 ]
378
332
chunk_size = int (bytes (chunk_header ), 16 )
379
333
if chunk_size == 0 :
380
334
break
@@ -392,11 +346,10 @@ def _parse_headers(self) -> None:
392
346
Expects first line of HTTP request/response to have been read already.
393
347
"""
394
348
while True :
395
- title = self ._readto (b": " , b"\r \n " )
396
- if not title :
349
+ header = self ._readto (b"\r \n " )
350
+ if not header :
397
351
break
398
-
399
- content = self ._readto (b"\r \n " )
352
+ title , content = bytes (header ).split (b": " , 1 )
400
353
if title and content :
401
354
# enforce that all headers are lowercase
402
355
title = str (title , "utf-8" ).lower ()
@@ -407,6 +360,17 @@ def _parse_headers(self) -> None:
407
360
self ._chunked = content .strip ().lower () == "chunked"
408
361
self ._headers [title ] = content
409
362
363
+ def _validate_not_gzip (self ) -> None :
364
+ """gzip encoding is not supported. Raise an exception if found."""
365
+ if (
366
+ "content-encoding" in self .headers
367
+ and self .headers ["content-encoding" ] == "gzip"
368
+ ):
369
+ raise ValueError (
370
+ "Content-encoding is gzip, data cannot be accessed as json or text. "
371
+ "Use content property to access raw bytes."
372
+ )
373
+
410
374
@property
411
375
def headers (self ) -> Dict [str , str ]:
412
376
"""
@@ -435,22 +399,13 @@ def text(self) -> str:
435
399
return self ._cached
436
400
raise RuntimeError ("Cannot access text after getting content or json" )
437
401
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
- )
402
+ self ._validate_not_gzip ()
403
+
446
404
self ._cached = str (self .content , self .encoding )
447
405
return self ._cached
448
406
449
407
def json (self ) -> Any :
450
408
"""The HTTP content, parsed into a json dictionary"""
451
- # pylint: disable=import-outside-toplevel
452
- import json
453
-
454
409
# The cached JSON will be a list or dictionary.
455
410
if self ._cached :
456
411
if isinstance (self ._cached , (list , dict )):
@@ -459,20 +414,9 @@ def json(self) -> Any:
459
414
if not self ._raw :
460
415
self ._raw = _RawResponse (self )
461
416
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 ())
417
+ self ._validate_not_gzip ()
418
+
419
+ obj = json_module .load (self ._raw )
476
420
if not self ._cached :
477
421
self ._cached = obj
478
422
self .close ()
@@ -599,12 +543,19 @@ def _send(socket: SocketType, data: bytes):
599
543
# ESP32SPI sockets raise a RuntimeError when unable to send.
600
544
try :
601
545
sent = socket .send (data [total_sent :])
602
- except RuntimeError :
603
- sent = 0
546
+ except OSError as exc :
547
+ if exc .errno == errno .EAGAIN :
548
+ # Can't send right now (e.g., no buffer space), try again.
549
+ continue
550
+ # Some worse error.
551
+ raise
552
+ except RuntimeError as exc :
553
+ raise OSError (errno .EIO ) from exc
604
554
if sent is None :
605
555
sent = len (data )
606
556
if sent == 0 :
607
- raise _SendFailed ()
557
+ # Not EAGAIN; that was already handled.
558
+ raise OSError (errno .EIO )
608
559
total_sent += sent
609
560
610
561
def _send_request (
@@ -636,11 +587,6 @@ def _send_request(
636
587
self ._send (socket , b"\r \n " )
637
588
if json is not None :
638
589
assert data is None
639
- # pylint: disable=import-outside-toplevel
640
- try :
641
- import json as json_module
642
- except ImportError :
643
- import ujson as json_module
644
590
data = json_module .dumps (json )
645
591
self ._send (socket , b"Content-Type: application/json\r \n " )
646
592
if data :
@@ -711,7 +657,7 @@ def request(
711
657
ok = True
712
658
try :
713
659
self ._send_request (socket , host , method , path , headers , data , json )
714
- except ( _SendFailed , OSError ) :
660
+ except OSError :
715
661
ok = False
716
662
if ok :
717
663
# Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work
0 commit comments