Особенности работы с русской кодировкой при загрузке файлов через aiohttp
В ходе исследования непонятного бага с битой кодировкой в именах загружаемых файлов мы столкнулись с непредвиденным поведением популярной библиотеки aiohttp. Решая эту проблему, мы получили полезный опыт, которым хочу с вами поделиться.
Описание ошибки
Наткнулись на ошибку случайно: в одной из интеграций стояла «маска» на символы в имени файлов, и в какой-то момент сработало оповещение о количестве ошибок в отправленных запросах. Исходные файлы имели имена вида «тест.png», но после загрузки в систему превращались в »%D1%82%D0%B5%D1%81%D1%82.png».
Исправление ошибки
Для решения проблемы пришлось последовательно проверить каждую точку взаимодействия с этим файлом в системе, и в итоге нашли место с превращением файла «тест.pdf» в »%D1%82%D0…».
Для загрузке на Filestorage мы использовали представленный в базовой документации aiohttp способ загрузки (файлы небольшого размера):
url = 'http://httpbin.org/post'
files = {'file': open('тест.png', 'rb')}
await session.post(url, data=files)
Переход на способ загрузки с FormData()
не решал проблему:
url = 'http://httpbin.org/post'
data = FormData()
data.add_field('file',
open('тест.png', 'rb'),
filename='тест.png',
content_type='image/png')
await session.post(url, data=data)
Быстро разобраться в таком поведении библиотеки aiohttp не получилось, поэтому пришлось глубже изучить документацию, провалиться в реализацию post-метода самой библиотеки и изучить похожие вопросы на просторах интернета. Как ни странно, на такую проблему наткнулись коллеги при использовании китайских символов, и способ решения был рабочий:
url = 'http://httpbin.org/post'
# По умолчанию quote_fields=True, что приводит к замещению русских букв на %xx
data = FormData(quote_fields=False)
data.add_field('file',
open('тест.png', 'rb'),
filename='тест.png',
content_type='image/png')
await session.post(url, data=data)
Осталось понять почему библиотека так себя ведёт. Оказалось, всё дело в стандарте RFC 7578: по умолчанию выполняется квотирование значений для 7-битных MIME-заголовков. Если окружающие сервисы поддерживают имена файлов с расширенными ASCII-символами (больше 7 бит), то смело указываем quote_fields=False
, и тогда проблема с нелатинскими буквами уйдёт.
Заключение
Даже стандартный для рынка инструмент может принести сюрпризы, и не каждую ошибку можно решить без глубокого погружения в код. Надеюсь, наш опыт будет полезен русскоязычному сообществу.