Как я уже неоднократно повторял, Amplicode придерживается CDD – Community Driver Development’а. По этой причине мы стараемся по максимумому общаться с комьюнити разработчиков, узнавать у них, что им нравится, что нет, чем пользуются, а чем нет, и почему. И мы далеко не всегда разговариваем только про Amplicode, часто диалог уходит в разговоры о технологиях, вечных вопросах программирования или других инструментах.
Так вот, практически каждый раз, когда мы говорили про IDE, а конкретно про IntelliJ IDEA Ultimate и её самые любимые фичи, мы так или иначе слышали следующие два слова: HTTP клиент.
Мы решили, что нам вполне по силам будем реализовать свой собственный HTTP-клиент и пару недель назад, в последнем мажорном релизе 2024-го года, мы его выпустили!
Сегодня я расскажу, чем наш клиент лучше HTTP-клиента от JetBrains, покажу базовые сценарии его использования, а также немного расскажу о планах на будущее.
Статья также доступна в формате видео на YouTube, VK Видео и RUTUBE, так что можно и смотреть, и читать — как вам удобнее!
Спойлер
В большинстве случаев я буду использовать изображения для демонстрации фрагментов кода. Такой подход позволит мне выделить важные части и более подробно их объяснить. Если вы хотите проверить всё самостоятельно, запустив код, о котором пойдет речь, то найти его можно на GitHub.
Тезисно про HTTP клиент от Amplicode
Итак, тезисно о нашем решении и основных его преимуществах:
Есть глубокая интеграция с IDE
Существует возможность тестировать произвольные сценарии, включая сложные последовательности запросов с обработкой и сохранением результатов
Использовать в команде супер легко – сохраняйте запросы в читаемом формате, располагайте в Git и отслеживайте изменения, а также делитесь ими с коллегами по проекту
А писать
assert
’ы можно на хорошо знакомым многим Kotlin’е
А в будущем также планируем реализовать
Возможность запуска на CI/CD
И дебаг запросов с его пошаговым выполнением
Но на словах и Database Navigator прекраснейшая вещь, однако как доходит до дела – возникают вопросики. Так что давайте проверим наше решение на практике!
Простые GET и POST запросы
Я возьму приложение, которое разрабатывал в статье про CRUD REST API, запущу необходимые сервисы (1) и само приложение (2).
Первым делом я хочу дёрнуть эндпоинт для получения записи по ID
.
@GetMapping("/{id}")
public OwnerDto getOne(@PathVariable Integer id) {
Optional<Owner> ownerOptional = ownerRepository.findById(id);
return ownerMapper.toOwnerDto(ownerOptional.orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"Owner with id `%d` not found".formatted(id))
));
}
Как я уже говорил ранее, наш HTTP-клиент имеет глубокую интеграцию с IDE, а это значит, что нам не нужно будет писать запросы вручную целиком для существующих эндпоинтов контроллера. Вместо этого достаточно нажать на Gutter-иконку напротив нужного эндпоинта и выбрать действие Generate HTTP Request.
Amplicode сгенерировал код запроса, корректно обработал часть запроса с PathVariable
и сразу же предлагает подставить значение через конструкцию pathParams
.
Давайте получим запись с id
“1”.
Отлично! Запрос выполнился успешно! И прямо в IDE мы можем посмотреть, что из себя представлял сам запрос, включая используемые заголовки и тело запроса, а также то, как выглядит ответ.
Аналогичным образом давайте сгенерируем запрос для получения всех записей.
Сам код энпоинта выглядит так:
@GetMapping
public PagedModel<OwnerMinimalDto> getAll(@ModelAttribute OwnerFilter filter,
Pageable pageable) {
Specification<Owner> spec = filter.toSpecification();
Page<Owner> owners = ownerRepository.findAll(spec, pageable);
Page<OwnerMinimalDto> ownerMinimalDtoPage = owners.map(ownerMapper::toOwnerMinimalDto);
return new PagedModel<>(ownerMinimalDtoPage);
}
Воспользуемся уже знакомым нам действием:
Amplicode сгенерировал нам GET-запрос:
В запрос мы можем добавить и параметры запроса.
Схожим с передачей параметров образом, обратимся к методу queryParam
и укажем, что нам нужны все записи, у которых имя начинается с буквы “J”.
Опять же можем изучить запрос и ответ прямо в IDE. Для этого отправляем нам запрос:
И получаем ответ:
Ну и наконец, давайте сгенерим запрос к эндпоинту создания нового владельца животных. Напомню, как выглядит сам эндпоинт:
@PostMapping
public OwnerDto create(@RequestBody @Valid OwnerDto dto) {
if (dto.getId() != null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id must be null");
}
Owner owner = ownerMapper.toEntity(dto);
Owner resultOwner = ownerRepository.save(owner);
return ownerMapper.toOwnerDto(resultOwner);
}
Генерируем HTTP запрос:
Amplicode сразу подготовил всё необходимое для корректного POST запроса:
Остаётся только сформировать тело запроса:
Запустим наш запрос привычным нам способом через Gutter иконку. Получим ответ:
Где посмотреть более сложные примеры?
Думаю, что некоторые из вас сейчас подумали: ”Всё здорово, но в моём приложении существует security, и как мне быть в таком случае? Как передать токен? Или как его сначала получить, а затем использовать в других запросах?”.
Для того, чтобы постараться ответить на большинство вопросов, которые могут возникнуть в процессе использования нашего HTTP-клиента, мы подготовили cookbook, так называемую книгу рецептов, которую в дальнейшем будем пополнять, однако уже сейчас можно посмотреть, как сформировать GET и POST запросы, как может выглядеть процесс получения токена и последующее его использование в других запросах, а также то, как можно написать тесты на эндпоинты с нашим HTTP-клиентом. Действие по просмотру cookbook доступно в правом верхем углу:
По нажатию на интересующий пример открывается соответствующий файл. Здесь представлены примеры запросов с комментариями:
А если вам надо выполнить запрос никак не связанный с вашим Spring Boot приложением, то ничто вас не ограничивает написать произвольный запрос руками или с использованием заготовок из панели Amplicode Designer (1), Generate меню (2) и Editor Toolbar (3).
Ну и наконец, если вдруг вы не знаете, как написать тот или иной запрос с использованием нашего HTTP-клиента – welcome в наш чат в Telegram, подскажем и поможем ?
Кроме того, какой может быть HTTP клиент без возможности брать токены, логины, пароли и другую секурную информацию из переменных окружения? Наш клиент позволяет создавать сколько угодно окружений и переменных для них, а также переключаться между ними в один клик.
Подключаем Spring Security и настраиваем Basic Authentication
Для демонстрации этой возможности давайте подключим базовую HTTP аутентификацию к нашему приложению, благо с Amplicode это решается за пару секунд.
Для добавления Spring Security конфигурации воспользуемся соответствующим действием из панели Amplicode Explorer. Нажимаем ПКМ по Configurations (1) -> выбираем Spring Security Configuration из списка со всеми поддерживаемыми Amplicode конфигурациями (2).
Откроется окно для настройки конфигурации. Все параметры оставим заполненными по умолчанию. Amplicode сгенерирует код класса WebSecurityConfiguration
:
package org.springframework.samples.petclinic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.anyRequest().authenticated());
http.headers(Customizer.withDefaults());
http.sessionManagement(Customizer.withDefaults());
http.formLogin(Customizer.withDefaults());
http.anonymous(Customizer.withDefaults());
http.csrf(AbstractHttpConfigurer::disable);
http.userDetailsService(inMemoryUserDetailsService());
return http.build();
}
public UserDetailsService inMemoryUserDetailsService() {
User.UserBuilder users = User.builder();
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(users.username("admin")
.password("{noop}admin")
.roles("ADMIN")
.build());
userDetailsManager.createUser(users.username("user")
.password("{noop}user")
.roles("USER")
.build());
return userDetailsManager;
}
}
Единственное, что нужно поправить в сгенерированной конфигурации – это сконфигурировать по умолчанию httpBasic
конфигурацию в нашем Security Filter Chain’е. Как это делается я сходу не помню, поэтому пойду искать в Amplicode Designer и настрою HTTP Basic через него.
Отлично, теперь всё готово.
Давайте перезапустим приложение и проверим, что теперь без токена нам данные не отдадут.
Да, всё верно, в ответ получили не владельца с id
1, а 401 ошибку.
Наслаждаемся силой Kotlin DSL
Для тех, кто не в курсе или подзабыл: базовая аутентификация предполагает передачу логина и пароля закодированного в Base64
в заголовке запроса.
То есть, например, первый запрос нам надо поправить следующим образом. Здесь admin
и admin
- это логин и пароль для одного из наших базовых пользователей, которых мы объявили в Security
конфигурации.
Используя множество других HTTP-клиентов, в том числе и HTTP-клиент от JetBrains, нам бы пришлось искать способ, которым мы бы смогли закодировать эту пару значений в Base64
. Вместе с нашим HTTP-клиентом и знанием состава пакета java.util
, мы можем осуществить кодирование прямо тут. Согласитесь, что это просто афигенно!
Запустим код и убедимся, что все работает так, как мы и задумывали:
Единственный важный момент - никто, конечно же, не захочет хранить значения логина и пароля в файле с запросами. Во-первых, это не безопасно, а во-вторых, для разных окружений эти значения могут отличаться. Поэтому давайте создадим файл с переменными окружения:
Выглядит он так:
Назовём его test
и укажем в нём значения для логина, пароля и хоста.
Теперь нам остается только указать, что мы хотим использовать значения из переменной окружения. Сделать это можно следующим образом:
Поправим наши запросы на использование значений из переменных:
И проверим, что всё работает, как и прежде:
Что ж, всё работает как надо!
Используем результаты одного запроса в другом
Давайте продолжим погружаться в наш HTTP-клиент и рассмотрим ещё более продвинутые сценарии использования.
Начнём с конструкции extract
. Благодаря ей мы можем вытаскивать нужные нам значения из ответа. Например, в нашем случае id
будет проставлен базой. Давайте попробуем его вытащить. Для этого обратимся к телу ответа (1), разберем json (2) и возьмём значение из поля с названием “id
” (3).
Но какой смысл что-то извлекать, если позже это нигде не используется, верно? Объявим переменную, в которую сохраним полученное значение (4). И теперь воспользуемся им в GET запросе (5).
Теперь мы можем выполнить POST запрос, значение id
после выполнения запроса сохранится в переменную:
И теперь вызываем GET.
Отлично! Созданная нами ранее запись получена, а это значит, что всё работает как надо.
Пишем тесты с HTTP-клиентом от Amplicode
Наконец, последнее, что я сегодня хотел показать – это возможность писать assert
’ы. Для этого напишем assert
, и далее можем описать, что мы ожидаем.
На данный момент под капотом у нашего клиента используется знакомая многим библиотека для тестирования REST Assured, поэтому те, кому эта библиотечка знакома, смогут довольно легко применить свои знания в нашем HTTP-клиенте.
Например, я хочу проверить, что запрос выполнился успешно, и что firstName
и lastName
у созданной записи действительно совпадают с тем, что я и указывал ранее.
Выполняем запрос:
Всё работает просто великолепно!
Планы на будущее
Несмотря на то, что уже сейчас решение выглядит лично для меня крайне кайфовым и удобным в использовании, на данный момент оно находится в preview. Другими словами, есть вероятность столкнуться с исключениями, багами и изменениями в DSL.
В планах на будущее:
добавить возможность конвертации запросов из
.http
файлов, ведь у многих из вас сохранились подобные файлики после использования IntelliJ IDEA Ultimateулучшить интеграцию с Spring приложением: генерация json для тела запроса, генерация параметров фильтрации и т.д
сделать UX ещё более приятным: сюда я отношу, автодополнения, подсветки, комплишены и т.д
ну и конечно же, стабилизация работы нашего решения
Не смотря на то, что у нас есть довольно чёткое понимание, как развивать HTTP клиент, фидбек от реальных пользователей никогда не будет лишним. Так что приходите в наш Telegram чат и делитесь своими идеями и предложениями.
Подписывайтесь на наши Telegram и YouTube, чтобы не пропустить новые материалы про Amplicode, Spring и связанные с ним технологии!
А если вы хотите попробовать Amplicode в действии – то можете установить его абсолютно бесплатно уже сейчас, как в IntelliJ IDEA/GigaIDE, так и в VS Code.
Комментарии (3)
Rabestro
19.12.2024 18:53Очень подробное описание.
На сколько понимаю, формат запросов собственный. Интересно узнать, почему отказались от формата HTTP? Этот формат сейчас используется в разных IDE. Microsoft Visual Studio, VSCode + httpYac, VSCode + Rest Client, JetBrains HTTP Client.
Когда в командах используются разные IDE, очень удобно иметь один формат.
В любом случае, хорошо, что есть альтернативы.
Beholder
Можно плагин скачать и использовать в стандартной IDEA?
befayer Автор
Добрый день!
Да, всё так =)
HTTP клиент доступен с последнего мажорного релиза. Чтобы установить плагин, можно воспользоваться рекомендуемым способом установки.