При разработке веб-приложений разработчики сталкиваются с задачами, которые нужно решать на каждом этапе обработки запроса. Например, если у нас есть несколько маршрутов, которые требуют аутентификации, или если нам нужно логировать все входящие запросы, то реализация этой логики в каждом маршруте может привести к дублированию кода и усложнению поддержки приложения. Это также увеличивает вероятность ошибок, так как любой измененный маршрут необходимо будет обновить в нескольких местах. Использование мидлваров позволяет решить эти проблемы.
Мидлвара - это функции, которые обрабатывают запросы и ответы в веб-приложениях. Они выполняются между получением запроса от клиента и отправкой ответа. Они могут выполнять различные задачи, такие как проверка прав доступа, логирование или преобразование данных в ответе
В этом уроке мы рассмотрим, как создавать и использовать мидлвары в Javalin, а также изучим примеры их применения
Оборачивание
Прежде чем перейти к изучению непосредственно мидлвар, разберем их принцип действия на примере обычных статических методов.
Представим, что у нас есть метод, который прибавляет 5 к аргументу:
public static int addFive(int x) {
return x + 5;
}
Наша задача расширить функционал этого метода, при этом не меняя сам метод. Например, мы хотим прибавлять 5 к удвоенному значению аргумента. Чтобы решить эту задачу, мы можем написать новую функцию, которая будет внутри себя использовать уже существующую функцию:
public static int doubleAddFive(int x) {
return addFive(x * 2);
}
Точно так же мы можем расширить и второй метод:
public static int calculate(int x) {
return doubleAddFive(x - 10) - 3;
}
App.calculate(20); // ((((20 - 10) * 2) + 5) - 3) = 22
В общем случае расширение метода выглядит так:
public static /* type */ nextMethod(/* args */) {
// preprocessing
var result = prevMethod(/* updatedArgs */);
// afterprocessing
return /* newResult */;
}
Чтобы расширить поведение метода, нужно создать новый метод, который будет использовать исходный. Главное условие — интерфейсы обоих методов должны совпадать: количество и тип аргументов, а также тип возвращаемого результата должны быть одинаковыми. Тогда вы можете просто заменить в коде вызов одного метода на вызов другого, обернутого, и код продолжит работать без изменений, поскольку интерфейс остался прежним. В этом случае код, использующий ваш новый метод, не заметит, что он обернут и его не потребуется переписывать. Этот подход также называется декорированием и описывается в справочниках по шаблонам проектирования как "паттерн Декоратор".
По похожей идее работают мидлвары. Они расширяют функционал обработчиков запросов, оборачивая их в подобные методы. При этом оригинальный обработчик остается неизменным
Использование мидвар
Чтобы использовать мидлвары в Javalin, не нужно ничего устанавливать, фреймворк имеет встроенную поддержку мидлвар. Для добавления мидлвар в Javalin используются методы before()
или after()
. Метод before()
используется для указания кода, который должен быть выполнен до основного обработчика запроса, а метод after()
— после.
Рассмотрим пример кода с подключением мидлвары:
import io.javalin.Javalin;
public class App {
public static void main(String[] args) {
Javalin app = Javalin.create()
app.before(ctx -> {
var path = ctx.path();
System.out.println("Request path: " + path);
});
app.get("/", ctx -> {
ctx.result("Hello from middleware!");
});
app.after(ctx -> {
System.out.println("Response has been sent");
});
app.start(7070);
}
}
В приведенном примере подключена мидлвара, которая для каждого входящего запроса выводит в консоль сообщение с указанием пути. Также добавлена мидлвара, которая выполняется после обработки запроса и выводит в консоль сообщение, информируя о том, что ответ был успешно отправлен клиенту
Каждый раз, когда мы используем before()
или after()
, очередная мидлвара добавляется в общую цепочку. Каждый запрос, отправляемый на обработку в приложение, проходит через всю цепочку этих мидлвар
Каждая мидлвара представляет собой лямбда-функцию, которая принимает на вход параметр Context ctx
. Через объект контекста она может обратиться к данным запроса или дополнить ответ. После того как мидлвара отработает, управление переходит к следующей по порядку мидлваре
public final class App {
public static Javalin getApp() {
var app = Javalin.create(config -> {
config.bundledPlugins.enableDevLogging();
});
app.before(ctx -> {
var path = ctx.path();
System.out.println("Request path: " + path);
});
app.before(ctx -> {
ctx.header("X-Custom-Header", "value");
});
app.get("/", ctx -> ctx.result("Welcome to Hexlet!"));
return app;
}
public static void main(String[] args) {
Javalin app = getApp();
app.start(7070);
}
}
В примере выше мы подключили мидлвары, одна из которых выводит в консоль сообщение, а другая добавляет кастомный заголовок X-Custom-Header в каждый ответ.
Запустим сервер и сделаем запрос:
http localhost:8080
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: text/plain
Date: Wed, 27 Nov 2024 08:32:10 GMT
# Вот наш заголовок
X-Custom-Header: value
Welcome to Hexlet!
При этом в консоли, где запущено приложение, мы можем увидеть вывод:
Request path: /
Обработчики
Самое интересное в мидлварах, что обработчики конкретных маршрутов — это тоже мидлвары. Но в отличие от мидлвар, которые выполняются для всех запросов, обработчики привязаны к конкретному маршруту
app.get("/", ctx -> ctx.result("Welcome to Hexlet!"));
app.get("/hello", ctx -> ctx.result("Hello, "));
Такие мидлвары позволяют реализовывать базовый роутинг в приложении
Терминальная мидлвара
Далеко не всегда мы хотим двигаться вглубь по всей цепочке добавленных мидлвар. Более того, в какой-то момент одна из мидлвар должна взять обработку запроса на себя. Чтобы завершить обработку, используется метод skipRemainingHandlers()
. После вызова этого метода движение по цепочке прекращается, все оставшиеся мидлвары для текущего запроса пропускаются и происходит отправка ответа
Например, хотим создать мидлвару для проверки наличия определенного параметра в запросе, не меняя при этом сам обработчик. Если параметр отсутствует, мы можем сразу отправить ответ с ошибкой и пропустить выполнение остальных мидлвар и обработчиков:
public final class App {
public static Javalin getApp() {
var app = Javalin.create();
app.before(ctx -> {
String id = ctx.queryParam("id");
if (id == null || id.isEmpty()) {
ctx.status(400).result("Bad Request: Missing 'id' parameter");
ctx.skipRemainingHandlers(); // Завершаем обработку
}
});
// Эта мидлвара и обработчики не будет выполнены, если параметр "id" отсутствует
app.before(ctx -> {
var path = ctx.path();
System.out.println("Request path: " + path);
});
app.get("/resource", ctx -> {
String id = ctx.queryParam("id");
ctx.result("Resource with id: " + id);
});
// Другие обработчики
return app;
}
public static void main(String[] args) {
Javalin app = getApp();
app.start(7070);
}
}
http localhost:7070/resource
HTTP/1.1 400 Bad Request
Content-Length: 35
Content-Type: text/plain
Date: Thu, 28 Nov 2024 08:09:09 GMT
Bad Request: Missing 'id' parameter
У такого поведения, когда есть цепочка функций и любая из них в процессе обработки может принять решение остановки цепочки и возврата ответа, есть имя. Такие цепочки называют chain responsibility, и это тоже паттерн
Самостоятельная работа
- Выполните на своем компьютере все шаги из урока
- Добавьте в приложение мидлвару, которая будет логировать дату и время поступления запроса
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.