19 December 2024

Новый HTTP клиент от Amplicode на Kotlin Script для Spring приложений и не только

Как я уже неоднократно повторял, Amplicode придерживается CDD – Community Driven 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.