Как я уже неоднократно повторял, 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)


  1. Beholder
    19.12.2024 18:53

    Можно плагин скачать и использовать в стандартной IDEA?


    1. befayer Автор
      19.12.2024 18:53

      Добрый день!
      Да, всё так =)
      HTTP клиент доступен с последнего мажорного релиза. Чтобы установить плагин, можно воспользоваться рекомендуемым способом установки.


  1. Rabestro
    19.12.2024 18:53

    Очень подробное описание.

    На сколько понимаю, формат запросов собственный. Интересно узнать, почему отказались от формата HTTP? Этот формат сейчас используется в разных IDE. Microsoft Visual Studio, VSCode + httpYac, VSCode + Rest Client, JetBrains HTTP Client.

    Когда в командах используются разные IDE, очень удобно иметь один формат.

    В любом случае, хорошо, что есть альтернативы.