Зарегистрируйтесь, чтобы продолжить обучение

Middlewares Java: Веб-технологии

При разработке веб-приложений разработчики сталкиваются с задачами, которые нужно решать на каждом этапе обработки запроса. Например, если у нас есть несколько маршрутов, которые требуют аутентификации, или если нам нужно логировать все входящие запросы, то реализация этой логики в каждом маршруте может привести к дублированию кода и усложнению поддержки приложения. Это также увеличивает вероятность ошибок, так как любой измененный маршрут необходимо будет обновить в нескольких местах. Использование мидлваров позволяет решить эти проблемы.

Мидлвара - это функции, которые обрабатывают запросы и ответы в веб-приложениях. Они выполняются между получением запроса от клиента и отправкой ответа. Они могут выполнять различные задачи, такие как проверка прав доступа, логирование или преобразование данных в ответе

В этом уроке мы рассмотрим, как создавать и использовать мидлвары в 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(), очередная мидлвара добавляется в общую цепочку. Каждый запрос, отправляемый на обработку в приложение, проходит через всю цепочку этих мидлвар

pipline

Каждая мидлвара представляет собой лямбда-функцию, которая принимает на вход параметр 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, и это тоже паттерн


Самостоятельная работа

  1. Выполните на своем компьютере все шаги из урока
  2. Добавьте в приложение мидлвару, которая будет логировать дату и время поступления запроса

Дополнительные материалы

  1. Before-handlers

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу