Всем привет! Как следует из названия статьи, речь пойдет о HTTP-клиентах для IntelliJ IDEA. Да, опять). В последнее время было несколько публикаций на эту тему, и мы бы хотели подробно рассказать о нашем взгляде на эту проблему и нашей реализации. А также поговорить о плюсах и минусах текущих решений для IDEA. Ранее мы уже рассказывали о нашем плагине для Spring - о том как у нас реализована поддержка Dependency Injection в частности, теперь настала очередь HTTP client'а.
Проблема
Как известно, в IDEA Ultimate "был" отдельный плагин HTTP Client, который предоставлял кастомный DSL (Domain Specific Language) для написания HTTP запросов и имел свой механизм для их исполнения. В настоящее время также стали появляться похожие инструменты от других разработчиков плагинов для IDEA. Почти все они так или иначе основаны на подобном подходе – создании кастомного DSL. Рассмотрим более подробно плюсы и минусы такого подхода. Как и в любом решении, здесь есть компромиссы: то, что даёт нам гибкость, одновременно требует дополнительных усилий.
Плюсы:
у нас есть полный контроль над выполнением HTTP-запросов;
соответственно, мы можем реализовывать практически любые фичи, как на уровне DSL, так и на уровне интеграции с IDE;
теоретически кастомный DSL можно рассматривать отдельно от IDEA и использовать в девопс практиках и в тестировании, как это произошло, например, с DSL от IDEA Ultimate.
Минусы:
реализовывать свой DSL – это достаточно трудоёмкое занятие;
пользователю нужно во все это погружаться – учить новый “мини” язык и разбираться, как это работает;
также нужно реализовывать поддержку в IDE, как на уровне редактора, так и на уровне работы с выходными данными запроса - UI.
Наш взгляд на эту проблему
В нашем плагине мы уже умели генерировать OpenApi файлы для Spring Controllers (кнопка Run напротив имени класса) и далее открывать Swagger UI в IDEA для созданных файлов.
Созданные файлы в таком виде могли использоваться для получения OpenApi документации по проекту прямо из IDEA без запуска проекта и для возможности протестировать приложение локально через сваггер. Но для последнего пункта, нужно чтобы приложение было запущено локально, а эту проблему можно решить, просто добавив в зависимости проекта спринг-стартер: springdoc-openapi-starter-webmvc-ui и в целом для тестирования текущего приложения это было не очень полезно.
Но нам пришла в голову идея - а почему бы не сделать из этого механизма свой HTTP клиент на минималках, который можно применять для вызова внешних сервисов, используя OpenApi, Swagger UI и аннотации Spring Web в качестве DSL для написания запросов. Это может быть полезно, когда есть задача по интеграции с каким нибудь сторонним HTTP сервисом. И хочется сначала “пощупать” его на примерах, сделав тестовые обращения к нему, чтобы понять как оно работает и какой результат возвращает.
Более подробно по шагам это процесс можно представить вот так:
с помощью хорошо знакомых Spring Web аннотаций пользователь на любом удобном ему языке Java\Kotlin описывает REST метод;
генерируем OpenApi схему, на основании написанного метода;
запускаем Swagger UI прямо в IDEA по файлу из предыдущего пункта;
и далее пользователь в уже хорошо знакомом ему инструменте, с готовым UI, сможет выполнять HTTP запросы.
Плюсы:
не надо делать свой UI - используем Swagger UI;
не надо делать свой DSL - используем Spring Web Annotations & OpenApi;
используем готовые проверенные решения, которые знают большинство разработчиков;
относительная простая реализация.
Минусы:
решение менее гибкое и менее расширяемое, т.к используем уже готовые компоненты. Соответственно имеем только то что имеем и кастомизировать это очень трудно.
узкоспециализированное решение для Spring (требует Spring Web в зависимостях, но в планах есть добавить поддержку JAX-RS, чтобы быть более платформо-независимыми)
нельзя строить сложные сценарии, состоящие из последовательности запросов, когда один вызов, является отправной точкой для другого;
используемые решения тоже не лишены недостатков и имеют свои проблемы (например CORS в Swagger UI).
Как это работает
Чтобы лучше понять, что получилось и как этим пользоваться, рассмотрим это на примере интеграции с одним из сервисов погоды - https://api.openweathermap.org
Допустим мы хотим проверить что возвращает нам сервис -https://api.openweathermap.org/data/2.5/weather.
Для этого просто возьмем и создадим метод на основе Spring аннотаций. Заметим что реализация этого метода нам не важна, как в целом и возвращаемый тип. Т.к. HTTP это текстовый протокол, то в качестве выходного типа можно указать строку или вообще ничего не указывать и оставить void. Swagger UI сам, на основании заголовков ответа, сможет понять, как отобразить результат запроса, в самом общем случае это просто текст.
Метод можно создать абсолютно в любом классе, наш плагин умеет распознавать методы, помеченные Spring Web аннотациями и если там обнаруживаем абсолютный урл, то это значит что мы можем попытаться выполнить такой метод сразу и напротив его имени появляется соответствующий маркер.
Если нажать на него, то откроется хорошо знакомый всем Swagger UI:
Где мы через интерфейс Swagger UI сможем выполнить запрос:
Также тут мы сразу получаем запрос в curl формате и можем это использовать, чтобы выполнить его через командную строку или как-то его модифицировать, сохранив себе в блокнотик. После выполнения запроса, получаем результат:
Т.к. наши генераторы OpenApi файлов по коду, могут отличатся от оригинальных и давать другой результат, то вы всегда можете отредактировать OpenApi вручную, если чувствуете в себе силы или завести баг. Чтобы перейти в режим редактирования, можно нажать “переключалку” вверху справа в окне Swagger. Кстати для OpenApi файлов, мы поддерживаем различные подсказки и авто-дополнения, так что редактирование OpenApi - это на самом деле не так страшно, как может показаться).
Также если у нас несколько сервисов, имеют общий URL, то такие методы можно сгруппировать в классы:
Если мы используем Kotlin, то можем создавать тестовые HTTP методы прямо в Kotlin файле, минуя создание лишних классов, в результате это будет выглядеть почти как Ultimate HTTP Client):
Если вы используете Feign для создания клиента, то в таком случае, можно сразу попробовать вызвать удаленный сервис:
Для обычных Spring контроллеров, мы генерируем OpenApi файл с тегом servers: localhost:8080, чтобы пользователь смог протестировать свое приложение локально, хотя это не совсем актуальный кейс как мы писали в начале статьи, или сможет сам указать нужный host в теге server OpenApi файла, чтобы проверить как работает сервис например на тестовом контуре.
Благодаря тому что мы используем Swagger UI, нам не пришлось делать свой интерфейс для работы с результатами запросов. Вот так например, выглядит HTTP запрос который скачивает наш плагин с GitHub:
Вот так выглядит результат, если пытаемся загрузить картинку:
Генераторы кода
Чтобы меньше “набирать” код, мы сделали удобные генераторы, для создания Spring Web методов прямо из Url или Curl (тут опять может пригодиться Curl запрос, который генерирует Swagger). Данные функции доступны из меню Alt+Insert или (Command ⌘)+N на Mac, когда мы находимся внутри Java класса или Kotlin файла:
Результат:
Или вариант с Curl:
Многие пользователи для тестирования HTTP запросов предпочитают создавать обычные методы, где используя привычный им HTTP клиент, непосредственно в Java/Kotlin пишут код для вызова сервиса. Для таких случаев мы также поддержали конвертация из Url/Curl сразу в java.net.http.HttpClient в меню Alt+Insert это опция “HttpClient method”. Вызвав которую для примера выше, получим код:
По итогу у нас есть следующие конверторы (генераторы кода):
из URL/CURL в Spring Web method
из URL/CURL в нативный Java клиент - java.net.http.HttpClient
Проблемы и детали реализации
IntelliJ IDEA включает в себя Embedded Browser (Java Chromium Embedded Framework - JCEF). Поэтому с тем чтобы запустить Swagger UI прямо в IDE не возникло никаких проблем. Просто добавили Standalone Swagger UI в ресурсы плагина и используем его как превью-генератор для OpenApi файлов. Посмотреть детали нашей реализации можно в репозитории на GitHub - OpenApiCefBrowser.
Но возникла другая проблема, связанная с тем, что мы выполняем веб запросы из браузера - это CORS. Кстати, на эту тему есть хорошая статья на Habr. Чтобы обойти эту проблему, пришлось написать свой request handler. Для JCEF клиента мы можем задать свой обработчик запросов. Где делегируем исполнение запросов Java HttpClient, а результат отдаем в браузер. Детали реализации можно посмотреть опять же в нашем репозитории.
Также для загрузки файлов, необходимо было реализовать CefDownloadHandler - пример нашей реализации можно посмотреть тут.
Для более удобной работы с "эндпоинтами" мы доработали наше Explyt Endpoints Tool Window. Где постарались улучшить дизайн и отображаем все возможные URL'ы что нашли в проекте и предоставляем навигацию к ним, а также поиск и фильтрацию. Если там чего то не хватает на ваш взгляд, то можете смело заводить issue на доработку, мы всегда рады обратной связи и предложениям.
Заключение
Мы постарались упростить процесс тестирования Rest API, надеемся так оно и вышло. Также мы продолжаем работать над нашими генераторами схем OpenAPi по коду проекта, чтобы сделать их лучше. Скачать последнюю версию плагина с HTTP клиентом можно тут или напрямую с GitHub Releases.
Приглашаем вас попробовать наш плагин, а также делиться своими отзывами и предложениями. Ваша обратная связь поможет нам сделать инструмент более полезным и удобным. Также напоминаем, что плагин имеет открытый исходный код, который доступен на GitHub.
Будем благодарны за ваши вопросы и идеи: GitHub Issues, Telegram-чат, а также личные сообщения в нашем профиле на Habr.
dev-priporov
Мне кажется не стоит изобразить что-то новое, достаточно просто сделать копию плагина, совместимую по dsl. У меня например в каждом проекте .http файлы с данными, заново все переносит куда-то как-то не хочется
grisha9 Автор
Отчасти я могу с вами согласиться, но это все таки немного разные истории. Мы прежде всего Spring плагин и изначально наш клиент “родился” из задачи генерации OpenApi файлов по Spring Controllers. Это в целом достаточно распространенная задача и есть множество её реализаций, а далее прикрутить к этому делу Swagger UI не составило никаких проблем и мы за очень дешево получили полнофункциональный веб клиент с готовым UI, приложив минимум усилий.
А реализовывать полноценную поддержку idea http языка - его парсинг, выполнение, UI для работы с результатами запрсов… это задача совсем другого калибра.