Philipp Hörist pushed to branch master at gajim / python-nbxmpp

Commits:

2 changed files:

Changes:

  • nbxmpp/http.py
    ... ... @@ -50,7 +50,7 @@ HTTP_METHODS_T = Literal[
    50 50
     CHUNK_SIZE = 32768
    
    51 51
     DEFAULT_USER_AGENT = f'nbxmpp/{nbxmpp.__version__}'
    
    52 52
     SIGNAL_ACTIONS = GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION
    
    53
    -
    
    53
    +MIN_SOUP_3_4 = Soup.check_version(3, 4, 0)
    
    54 54
     
    
    55 55
     class HTTPLogAdapter(logging.LoggerAdapter):
    
    56 56
         def process(self, msg: str, kwargs: Any) -> tuple[str, Any]:
    
    ... ... @@ -278,6 +278,10 @@ class HTTPRequest(GObject.GObject):
    278 278
             self._message.connect('got-body', self._on_got_body)
    
    279 279
             self._message.connect('restarted', self._on_restarted)
    
    280 280
             self._message.connect('finished', self._on_finished)
    
    281
    +        self._message.connect('got-headers', self._on_got_headers)
    
    282
    +
    
    283
    +        if MIN_SOUP_3_4:
    
    284
    +            self._message.connect('got-body-data', self._on_got_body_data)
    
    281 285
     
    
    282 286
             soup_session = self._session.get_soup_session()
    
    283 287
             soup_session.send_async(self._message,
    
    ... ... @@ -360,11 +364,9 @@ class HTTPRequest(GObject.GObject):
    360 364
                 self._finish_read()
    
    361 365
                 return
    
    362 366
     
    
    363
    -        length = len(bytes_)
    
    364
    -        self._received_size += length
    
    365
    -        if self._received_size > self._response_content_length:
    
    366
    -            self._finish_read(HTTPRequestError.CONTENT_OVERFLOW)
    
    367
    -            return
    
    367
    +        if not MIN_SOUP_3_4:
    
    368
    +            self._received_size += len(bytes_)
    
    369
    +            self._check_content_overflow()
    
    368 370
     
    
    369 371
             if self._output_stream is None:
    
    370 372
                 self._response_body_data += bytes_
    
    ... ... @@ -383,11 +385,8 @@ class HTTPRequest(GObject.GObject):
    383 385
     
    
    384 386
             self._read_async()
    
    385 387
     
    
    386
    -        if (self._emit_response_progress and
    
    387
    -                self._message.get_method() == 'GET'):
    
    388
    -            self.emit('response-progress',
    
    389
    -                      self._received_size / self._response_content_length)
    
    390
    -
    
    388
    +        if not MIN_SOUP_3_4:
    
    389
    +            self._emit_progress()
    
    391 390
     
    
    392 391
         def _finish_read(self, error: Optional[HTTPRequestError] = None) -> None:
    
    393 392
             self._log.info('Finished reading')
    
    ... ... @@ -404,11 +403,7 @@ class HTTPRequest(GObject.GObject):
    404 403
                                 ) -> None:
    
    405 404
     
    
    406 405
             # Signal is only raised when there is content in the response
    
    407
    -
    
    408 406
             headers = message.get_response_headers()
    
    409
    -
    
    410
    -        self._response_content_length = headers.get_content_length()
    
    411
    -
    
    412 407
             if content_type is None:
    
    413 408
                 # According to the docs, content_type is None when the sniffer
    
    414 409
                 # decides to trust the content-type sent by the server.
    
    ... ... @@ -416,14 +411,19 @@ class HTTPRequest(GObject.GObject):
    416 411
     
    
    417 412
             self._response_content_type = content_type or ''
    
    418 413
     
    
    419
    -        self._log.info('Sniffed: content-type: %s, content-length: %s',
    
    420
    -                       self._response_content_type,
    
    421
    -                       self._response_content_length)
    
    414
    +        self._log.info('Sniffed: content-type: %s',
    
    415
    +                       self._response_content_type)
    
    422 416
     
    
    423 417
             self.emit('content-sniffed',
    
    424 418
                       self._response_content_length,
    
    425 419
                       self._response_content_type)
    
    426 420
     
    
    421
    +    def _on_got_headers(self, message: Soup.Message) -> None:
    
    422
    +        headers = message.get_response_headers()
    
    423
    +        self._response_content_length = headers.get_content_length()
    
    424
    +        self._log.info('Got Headers: content-length: %s',
    
    425
    +                       self._response_content_length)
    
    426
    +
    
    427 427
         def _on_got_body(self, _message: Soup.Message) -> None:
    
    428 428
             # This signal tells us that the full body was received.
    
    429 429
             # The `finished` signal is not a sure indicator if the message body
    
    ... ... @@ -432,11 +432,38 @@ class HTTPRequest(GObject.GObject):
    432 432
             self._log.info('Body received')
    
    433 433
             self._body_received = True
    
    434 434
     
    
    435
    +    def _on_got_body_data(self,
    
    436
    +                          _message: Soup.Message,
    
    437
    +                          chunk_size: int
    
    438
    +                          ) -> None:
    
    439
    +
    
    440
    +        self._received_size += chunk_size
    
    441
    +        self._check_content_overflow()
    
    442
    +
    
    443
    +        status = self._message.get_status()
    
    444
    +        if status in (Soup.Status.OK, Soup.Status.CREATED):
    
    445
    +            self._emit_progress()
    
    446
    +
    
    447
    +    def _emit_progress(self) -> None:
    
    448
    +        if not self._emit_response_progress:
    
    449
    +            return
    
    450
    +
    
    451
    +        if not self._message.get_method() == 'GET':
    
    452
    +            return
    
    453
    +
    
    454
    +        self.emit('response-progress',
    
    455
    +                  self._received_size / self._response_content_length)
    
    456
    +
    
    457
    +    def _check_content_overflow(self) -> None:
    
    458
    +        if self._received_size > self._response_content_length:
    
    459
    +            self._finish_read(HTTPRequestError.CONTENT_OVERFLOW)
    
    460
    +
    
    435 461
         def _on_restarted(self, _message: Soup.Message) -> None:
    
    436 462
             self._log.info('Restarted')
    
    437 463
             self._body_received = False
    
    438 464
             self._response_content_type = ''
    
    439 465
             self._response_content_length = 0
    
    466
    +        self._received_size = 0
    
    440 467
     
    
    441 468
         def _on_finished(self, _message: Soup.Message) -> None:
    
    442 469
             self._log.info('Message finished')
    

  • test/unit/test_http.py
    1
    -from time import time
    
    2 1
     import tempfile
    
    3 2
     import unittest
    
    4
    -from unittest.mock import call
    
    5 3
     from unittest.mock import Mock
    
    6
    -from unittest.mock import patch
    
    7 4
     import os
    
    8 5
     from pathlib import Path
    
    9 6
     
    
    ... ... @@ -124,13 +121,11 @@ class HTTP(unittest.TestCase):
    124 121
             session = HTTPSession()
    
    125 122
             request = session.create_request()
    
    126 123
     
    
    127
    -        def _on_progress(req, progress):
    
    128
    -            self.assertIsInstance(progress, float)
    
    124
    +        def _on_start(req):
    
    129 125
                 req.cancel()
    
    130 126
     
    
    131 127
             callback_mock = Mock()
    
    132
    -        request.connect('starting-response-body', callback_mock.starting)
    
    133
    -        request.connect('response-progress', _on_progress)
    
    128
    +        request.connect('starting-response-body', _on_start)
    
    134 129
             request.connect('finished', callback_mock.finished)
    
    135 130
             request.connect('destroy', lambda *args: mainloop.quit())
    
    136 131
             request.send('GET', LARGE_FILE_URL, timeout=10)
    
    ... ... @@ -141,7 +136,6 @@ class HTTP(unittest.TestCase):
    141 136
             self.assertTrue(request.is_finished())
    
    142 137
             self.assertEqual(request.get_error(), HTTPRequestError.CANCELLED)
    
    143 138
     
    
    144
    -        callback_mock.starting.assert_called()
    
    145 139
             callback_mock.finished.assert_called()
    
    146 140
     
    
    147 141
         def test_download_failed_404(self):