Найти - Пользователи
Полная версия: Bytes в get-запросе aiohttp
Начало » Центр помощи » Bytes в get-запросе aiohttp
1
greeblie
Добрый день.
Писал тут асинхронный торрент-клиент полностью на asyncio и aiohttp и внезапно столкнулся с проблемой. Кратко введу в курс дела: информация о торренте хранится в специальном .torrent файле, который внутри себя содержит utf-8 текст с незатейливо закодированным словарём. Для того, чтобы идентифицировать торрент, клиенты и трекеры берут sha1-хэш от определённой части этого словаря, я это делаю примерно так:
 info_dict = {}  # какой-то словарь
hash = hashlib.sha1(bencoding.encode(info_dict)).digest()  # bytes, длиной 20
Затем производится http get-запрос к трекеру, в числе аргументов которого - этот самый хэш. Штука в том, что передать эти 20 байт нужно без изменений, так, чтобы трекер смог найти нужный торрент.
До этого я уже написал рабочий код, который успешно получал ответ от трекера, но после обновления либы aiohttp с версии 1.0.5 до текущей 1.2.0, оказалось, что код больше не работает.
А именно:
 # составляем словарь с аргументами для запроса - Dict[str, Any]
params = {
    'info_hash': hash,
    'compact': 1,
    # ... и т.д.
}
# открываем (или берём уже имеющуюся) сессию
session = aiohttp.ClientSession(loop=asyncio.get_event_loop())
# делаем запрос
async with session.get(announcer, params=params) as response:
    return await response.read()
И вот тут возникает ошибка: Invalid variable type: mapping value should be str or int, got b'\xba\…..'
Которая ведёт куда-то в дебри ставящегося вместе с aiohttp пакета yarl. Ясно, что байты в качестве значений словаря метод больше не берёт. Пробуем закодировать самостоятельно:
 >>>str(hash)  # конечно, нет
"b'Y;o]G\xd7gb[\x8e\xe7\x1a...'"
>>>hashlib.sha1(bencoding.encode(info_dict)).hexdigest()  # нет  
'593b6f5d47d767625....'
>>>urllib.parse.quote(hash)  # опять нет
'Y%3Bo%5DG%D7gb%5B%8E%E7%...'
>>>urllib.parse.quote(str(hash)[2:-1])  # не-а
>>>hash.decode(????)
>>>yarl.quote(hash)  # нет такого торрента!
Что ж делать-то? Как вообще отправить сырые байты через http?
py.user.next
greeblie
И вот тут возникает ошибка:
Не понял, где “тут”, полный трейсбек выложи.

greeblie
Которая ведёт куда-то в дебри
Вот это (эти дебри) надо выкладывать, а не перессказывать словами.
greeblie
Пересказывал словами я исключительно для того, чтобы было понятно, что и зачем я вообще делаю)
А вопрос заключался в том, как правильно закодировать байты для передачи через get. Неприятно, конечно, что серьёзная библиотека вдруг перестала принимать bytes в качестве аргументов без каких-либо указаний на то в документации, но, кажется, с этим без модификации её кода ничего не поделать.
Вот трейсбек, тем не менее:
 future: <Task finished coro=<BaseTrackerClient.query() done, defined at D:\Files\BitTorrent\modules\tracker\base_tracker.py:52> exception=TypeError("Invalid variable type: mapping value should be str or int, got b'Y;o]G\\xd7gb[\\x8e\\xe7\\x1a\\xa03\\x92\\x9fuv'",)>
Traceback (most recent call last):
  File "D:\Files\Python\3.5\lib\asyncio\tasks.py", line 239, in _step
    result = coro.send(None)
  File "D:\Files\BitTorrent\modules\tracker\base_tracker.py", line 57, in query
    timeout=timeout)
  File "D:\Files\Python\3.5\lib\asyncio\tasks.py", line 392, in wait_for
    return fut.result()
  File "D:\Files\Python\3.5\lib\asyncio\futures.py", line 274, in result
    raise self._exception
  File "D:\Files\Python\3.5\lib\asyncio\tasks.py", line 239, in _step
    result = coro.send(None)
  File "D:\Files\BitTorrent\modules\tracker\http_tracker.py", line 26, in _make_request
    async with self._session.get(self.announcer, params=data) as response:
  File "D:\Files\Python\3.5\lib\site-packages\aiohttp\client.py", line 540, in __aenter__
    self._resp = yield from self._coro
  File "D:\Files\Python\3.5\lib\site-packages\aiohttp\client.py", line 173, in _request
    proxy=proxy, proxy_auth=proxy_auth, timeout=timeout)
  File "D:\Files\Python\3.5\lib\site-packages\aiohttp\client_reqrep.py", line 79, in __init__
    url2 = url.with_query(params)
  File "D:\Files\Python\3.5\lib\site-packages\yarl\__init__.py", line 678, in with_query
    "should be str or int, got {!r}".format(v))
TypeError: Invalid variable type: mapping value should be str or int, got b'Y;o]G\xd7gb[\x8e\xe7\x1a\xa03\x92\x9fuv'
py.user.next
Раскодировать из байтов в строку можешь так
  
>>> b'Y;o]G\xd7gb[\x8e\xe7\x1a\xa03\x92\x9fuv'.decode('latin1')
'Y;o]G×gb[\x8eç\x1a\xa03\x92\x9fuv'
>>>
greeblie
Да, действительно, не догадался использовать именно latin1. Тем не менее, трекер всё равно возвращает ошибку, жалуясь на неправильный хэш. Изменение кодировки http запроса со стандартной utf-8 на latin1
 async with session.get(announcer, params=params, encoding='latin1') as response:
    ...
тоже не помогает. Как не помогает и quote().
greeblie
Requests, кстати, справляется с без проблем и принимает байты, жаль только, что не асинхронный.
А вот urllib.parse.quote_from_bytes() никакого эффекта не даёт. Ничего уже не понимаю.

UPD:
справился с проблемой с помощью urllib.parse.urlencode(), который кодирует так, как надо.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB