В ходе исследования непонятного бага с битой кодировкой в именах загружаемых файлов мы столкнулись с непредвиденным поведением популярной библиотеки 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, и тогда проблема с нелатинскими буквами уйдёт.

Заключение

Даже стандартный для рынка инструмент может принести сюрпризы, и не каждую ошибку можно решить без глубокого погружения в код. Надеюсь, наш опыт будет полезен русскоязычному сообществу.

Комментарии (5)