Когда хочется Орать — опыт использования AWS SDK for Java 2.0

Время от времени я сталкиваюсь с разнообразыми API и SDK, от которых хочется Орать и Рыдать. Не знаю, c чем это связано. Возможно, разработка таких API — это не основной бизнес компаний. Возможно, авторы этих продуктов не занимаются Dogfooding-ом. В любом случае, сегодня у меня уже нет сил терпеть.

Итак, есть всеми известный облачный Амазон. У него есть куча разных сервисов — наверное, сейчас уже больше 50 штук. У большинства сервисов есть API. У API есть несколько готовых клиентов — SDK под разные языки, либо консольная программка на питончике — CLI (кстати, ничего так). Но, конечно же, ничего вам не мешает взять и написать свой клиент, используя сырой HTTP-based REST.

Долгое время у амазона был неплохо работающий SDK для джавы. Его мажорнарная версия — 1. Под эту версию написано куча документации. В интернете масса туториалов и постов. А весь stackoverflow завален ответами на самые частотные вопросы. В общем, бери и используй, как говорится.

Время шло, всё развивалось. Вот и наш джавовый SDK рос. Так и появилась вторая версия SDKhttps://aws.amazon.com/blogs/developer/aws-sdk-for-java-2-x-released/. Случилось это замечательное событие 19 ноября, 2018 года — это важно для дальнейшего рассказа.

Сначала о хорошем. Хоть я сейчас и возбужден из-за качества и консистентности нового API, надо признать, что SDK — это очень большой продукт. Сделать сразу всё, при этом, идеально — это непосильная задача.

Во второй версии java SDK для aws, как и во всем мире, сместился фокус с синхронного API на реактивное, асинхронное API. Вы будете работать с джавовыми CompletableFuture. Это, на самом деле, очень здорово. Ведь асинхронный, реактивный код — это то, как мы можем легко масштабироваться вертикально.

Кроме того, в новой версии SDK большинство API, что я видел, помечено аннотацией @ThreadSafe — приятно, что за тебя разработчики подумали и сняли одну из головных болей: можно смело брать SDK и использовать его в многопоточной среде. К слову говоря, Thread safety достигнуто благодаря иммутабельности всех классов API (не переживайте — изменять эти объекты вы всё же сможете: везде используются Билдеры, а также реализована функциональность мутации объектов).

Ладно, теперь перейдем к минусам. Совсем глобальных проблем несколько. Во-первых, куча документации на официальном сайте Амазона всё еще даёт примеры и объяснения про SDK первой версии. Ну, вы чего — уже прошло 2 месяца после релиза, не надо так.

Вторая глобальная проблема нового API — это утрата множества существующих функций из старого SDK. Например, почему-то не реализованы S3 URL presigners — эта штука, которая позволяет вам на бэкенде сгенерировать временную ссылку, с помощью которой можно скачать или загрузить файл с мобильного приложения изи сайта.

К слову говоря, именно при работе с S3 я и сгорел. Причем, надо заметить, что проблема, с которой я столкнулся, актульна в целом для API сервиса S3. Версия джавового SDK тут не причем.

Задача — сгенерировать на сервере временную ссылку, на которую клиент бэкенда сможет загрузить файл. Ограничения — ссылка должна быть временной, одноразовой, и Амазон не должен позволять загрузить больше информации, чем указал сервер.

Рассмотрим случай, когда у меня нет ограничения на максимальный размер загружаемого файла. То есть, для нас нормально, что клиент загрузит файл размером в 5 ГБ. Тогда решение этой задачи с точки зрения API S3 довольно простое — нужно просто сгенерировать Presigned PUT URL. Но вот незадача:

  1. В документации вы найдете код для первой версии SDK — https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObjectJavaSDK.html.
  2. Связанный пункт — во второй версии SDK, как я уже писал выше, просто не реализовна эта функциональность.

Однако не стоит боятся. Почти у всех проблем в мире есть workaround-ы. Поступим следующим образом. В амазоновском SDK есть возможность загрузить файл на S3 прямо из Джавы. А еще есть возможность подсунуть перехватчик запроса. Так вот, давайте напишем тупой перехватчик запроса, который ничего не будет делать, и попытаемся отправить запрос. В результате, SDK сформирует нужный URL, но не сделает отправку файла. Мы возьмем этот URL и отдадим клиенту. Именно на этом принципе реализована библиотека (вдумайтесь, есть целая библиотека-костыль, которая фиксит красоту SDK) — https://github.com/aaronanderson/aws-java-sdk-v2-utils/blob/master/src/main/java/S3Presigner.java.

Но, не стоит радоваться раньше времени. У нас цель — установить максимальный размер файла, который разрешено загрузить. Для решения этой задачи существует только один механизм — использование Post Policy. Если вкратце, то нужно:

  1. Сформировать руками Post Policy json-документ, в котором будет куча полей. В частности, максимальный размер файла — content-length-range.
  2. Посчитать подпись этого документа, используя AWS4-HMAC-SHA256
  3. На клиенте эмулировать «браузер», и отправить таким образом файл, используя POST-метод.

Думаете, что в джавовом SDK есть функциональность создания подписи и формирования Policy-документа? Ха-ха! Конечно же, нет. Вы должны руками собирать этот чёртов документ. Руками, используя криптогарфию вашего языка, считать подпись. Руками эмулировтаь браузер. Ну, либо взять очередную сторонню либу, в которой есть эта реализация — https://github.com/minio/minio-java/blob/master/examples/PresignedPostPolicy.java. Я так долго искал это решение, что даже попытался задать вопрос на SO — но, как обычно, сам ответил на свой вопрос.

Я честно не понимаю: неужели разработчики Амазона не рассматривают такой очевидный кейс — загрузка файла на временный URL с ограничением размера? Вроде бы, это не какой-то эзотерический пример, а вполне реальная задача. Не понятно, почему сторонние разработчики должны писать такой низкоуровневый код, если вы предоставляете полноценное SDK.

Ладно, про S3 поговорили. Выше я писал о том, как прекрасна стала вторая версия SDK для джавы в разрезе асинхронности и реактивности. Но вы же не думаете, что разработчики амазона сами написали весь сетевой код? Конечно же, нет! Там под копотом Netty. Как вы думаете, что произойдет, если у вас проект на Spring Boot 2, и вы используете WebFlux? А там, по умолчанию, тоже Нетти. Как говорится, Вот тебе, бабка, и Юрьев день! Приехали.

Категории: Программирование