Как опция запроса stream = True передает данные по одному блоку за раз? - PullRequest
1 голос
/ 21 февраля 2020

Я использую следующий код для проверки количества секунд, в течение которых HTTP-соединение может оставаться активным:

    start_time = time.time()
    try:
      r = requests.get(BIG_FILE_URL, stream=True)
      total_length = r.headers['Content-length']
      for chunk in r.iter_content(chunk_size=CHUNK_SIZE): 
        time.sleep(1)
    # ... except and more logic to report total time and percentage downloaded

Чтобы быть уверенным, Python не просто загружает все сразу и создает генератор Я использовал tcpdump. Он отправляет один пакет в секунду (приблизительно), но я не нашел, что заставляет сервер отправлять один блок за раз и как это делает библиотека запросов.

Я проверил несколько вопросов SOF и посмотрел в документации библиотеки запросов, но все ресурсы объясняют, как использовать библиотеку для загрузки больших файлов, и ни один из них не объясняет внутреннюю часть параметра stream = True.

Мой вопрос: что в Заголовки протокола tcp или HTTP-запроса заставляют сервер отправлять один блок за раз, а не весь файл сразу?

Ответы [ 2 ]

2 голосов
/ 25 февраля 2020

Это не объясняется в документации пользователя. Просматривая исходный код requests, я обнаружил, что если мы установим stream=True в requests.get(...), то в заголовках HTTP будет установлен headers['Transfer-Encoding'] = 'chunked'. Таким образом, указывается кодирование передачи Chunked. В кодировании с частичной передачей поток данных делится на серию неперекрывающихся «кусков». Чанки отправляются сервером независимо друг от друга. Надеюсь, что это отвечает на вопрос.

1 голос
/ 02 марта 2020

Этот вопрос вызвал у меня любопытство, поэтому я решил go спуститься в эту исследовательскую кроличью нору. Вот некоторые из моих (открытых для исправлений!) Выводов:

* Связь между клиентом и сервером стандартизирована моделью взаимодействия открытых систем ( OSI ).

* The передача данных обрабатывается уровнем 4 - транспортным уровнем. TCP / IP всегда разбивает данные на пакеты. Максимальная длина IP-пакета составляет ок. 65,5 Кбайт .

Что теперь удерживает Python от рекомбинации всех этих пакетов в исходный файл перед его возвратом?

Метод запросов iter_content имеет вложенный генератор, который оборачивает метод генератора urllib3: class urllib3.response.HTTPResponse(...).stream(...)

Параметр 'chunk_size', похоже, устанавливает буфер для того, сколько данных считывается из открытого сокета в память, прежде чем они записываются в файловую систему.

Вот копия полезного метода iter_content: </p> <pre><code>def iter_content(self, chunk_size=1, decode_unicode=False): """Iterates over the response data. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. The chunk size is the number of bytes it should read into memory. This is not necessarily the length of each item returned as decoding can take place. chunk_size must be of type int or None. A value of None will function differently depending on the value of `stream`. stream=True will read data as it arrives in whatever size the chunks are received. If stream=False, data is returned as a single chunk. If decode_unicode is True, content will be decoded using the best available encoding based on the response. """ def generate(): # Special case for urllib3. if hasattr(self.raw, 'stream'): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk except ProtocolError as e: raise ChunkedEncodingError(e) except DecodeError as e: raise ContentDecodingError(e) except ReadTimeoutError as e: raise ConnectionError(e) else: # Standard file-like object. while True: chunk = self.raw.read(chunk_size) if not chunk: break yield chunk self._content_consumed = True if self._content_consumed and isinstance(self._content, bool): raise StreamConsumedError() elif chunk_size is not None and not isinstance(chunk_size, int): raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) # simulate reading small chunks of the content reused_chunks = iter_slices(self._content, chunk_size) stream_chunks = generate() chunks = reused_chunks if self._content_consumed else stream_chunks if decode_unicode: chunks = stream_decode_response_unicode(chunks, self) return chunks

...