«1. Обзор

Undertow — чрезвычайно легкий и высокопроизводительный веб-сервер от JBoss. Он поддерживает как блокирующие, так и неблокирующие API с NIO.

Поскольку он написан на Java, его можно использовать в любых приложениях на основе JVM во встроенном режиме, даже сервер JBoss WilfFly внутри использует Undertow для повышения производительности сервера.

В этом уроке мы покажем возможности Undertow и как его использовать.

2. Почему Отлив?

    Легкий: Undertow чрезвычайно легкий — менее 1 МБ. Во встроенном режиме он использует только 4 МБ пространства кучи во время выполнения. Сервлет 3.1: полностью поддерживает сервлет 3.1. Веб-сокет: поддерживает функции веб-сокета (включая JSR-356). заголовок живого ответа. Это помогает клиентам, поддерживающим постоянные соединения, оптимизировать производительность за счет повторного использования сведений о соединении

3. Использование Undertow

Давайте начнем использовать Undertow, создав простой веб-сервер.

3.1. Зависимость Maven

Чтобы использовать Undertow, нам нужно добавить следующую зависимость в наш pom.xml:

<dependency>
    <groupId>io.undertow</groupId>
    <artifactId>undertow-servlet</artifactId>
    <version>1.4.18.Final</version>
</dependency>

Чтобы создать исполняемый jar, нам также нужно добавить maven-shade-plugin. Вот почему нам также необходимо добавить следующую конфигурацию:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Последняя версия Undertow доступна в центральном репозитории Maven.

3.2. Простой сервер

С помощью приведенного ниже фрагмента кода мы можем создать простой веб-сервер, используя API-интерфейс Undertow Builder:

public class SimpleServer {
    public static void main(String[] args) {
        Undertow server = Undertow.builder().addHttpListener(8080, 
          "localhost").setHandler(exchange -> {
            exchange.getResponseHeaders()
            .put(Headers.CONTENT_TYPE, "text/plain");
          exchange.getResponseSender().send("Hello Baeldung");
        }).build();
        server.start();
    }
}

Здесь мы использовали API-интерфейс Builder для привязки порта 8080 к этому серверу. Также обратите внимание, что мы использовали лямбда-выражение для использования обработчика.

Мы также можем использовать приведенный ниже фрагмент кода, чтобы сделать то же самое без использования лямбда-выражений:

Undertow server = Undertow.builder().addHttpListener(8080, "localhost")
  .setHandler(new HttpHandler() {
      @Override
      public void handleRequest(HttpServerExchange exchange) 
        throws Exception {
          exchange.getResponseHeaders().put(
            Headers.CONTENT_TYPE, "text/plain");
          exchange.getResponseSender().send("Hello Baeldung");
      }
  }).build();

Здесь важно отметить использование HttpHandler API. Это самый важный плагин для настройки приложения Undertow в соответствии с нашими потребностями.

В этом случае мы добавили настраиваемый обработчик, который будет добавлять заголовок ответа Content-Type: text/plain к каждому запросу.

Аналогичным образом, если мы хотим возвращать некоторый текст по умолчанию с каждым ответом, мы можем использовать приведенный ниже фрагмент кода:

exchange.getResponseSender()
  .send("Hello Baeldung");

3.3. Безопасный доступ

В большинстве случаев мы не разрешаем всем пользователям доступ к нашему серверу. Обычно доступ могут получить пользователи с действительными учетными данными. Мы можем реализовать тот же механизм с Undertow.

Чтобы реализовать это, нам нужно создать менеджер удостоверений, который будет проверять подлинность пользователя для каждого запроса.

Для этого мы можем использовать IdentityManager Undertow:

public class CustomIdentityManager implements IdentityManager {
    private Map<String, char[]> users;

    // standard constructors
    
    @Override
    public Account verify(Account account) {
        return account;
    }
 
    @Override
    public Account verify(Credential credential) {
        return null;
    }
 
    @Override
    public Account verify(String id, Credential credential) {
        Account account = getAccount(id);
        if (account != null && verifyCredential(account, credential)) {
            return account;
        }
        return null;
    }
}

После создания диспетчера идентификации нам нужно создать область, в которой будут храниться учетные данные пользователя:

private static HttpHandler addSecurity(
  HttpHandler toWrap, 
  IdentityManager identityManager) {
 
    HttpHandler handler = toWrap;
    handler = new AuthenticationCallHandler(handler);
    handler = new AuthenticationConstraintHandler(handler);
    List<AuthenticationMechanism> mechanisms = Collections.singletonList(
      new BasicAuthenticationMechanism("Baeldung_Realm"));
    handler = new AuthenticationMechanismsHandler(handler, mechanisms);
    handler = new SecurityInitialHandler(
      AuthenticationMode.PRO_ACTIVE, identityManager, handler);
    return handler;
}

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

Если мы определим AuthenticationMode как CONSTRAINT_DRIVEN, то только те запросы будут проходить через определенные механизмы аутентификации, где срабатывает ограничение(я), которое требует аутентификацию.

Теперь нам просто нужно сопоставить эту область и диспетчер удостоверений с сервером перед его запуском:

public static void main(String[] args) {
    Map<String, char[]> users = new HashMap<>(2);
    users.put("root", "password".toCharArray());
    users.put("admin", "password".toCharArray());

    IdentityManager idm = new CustomIdentityManager(users);

    Undertow server = Undertow.builder().addHttpListener(8080, "localhost")
      .setHandler(addSecurity(e -> setExchange(e), idm)).build();

    server.start();
}

private static void setExchange(HttpServerExchange exchange) {
    SecurityContext context = exchange.getSecurityContext();
    exchange.getResponseSender().send("Hello " + 
      context.getAuthenticatedAccount().getPrincipal().getName(),
      IoCallback.END_EXCHANGE);
}

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

3.4. Веб-сокет

Создать канал обмена веб-сокетами с помощью API WebSocketHttpExchange от UnderTow очень просто.

Например, мы можем открыть коммуникационный канал сокета по пути baeldungApp с помощью приведенного ниже фрагмента кода:

public static void main(String[] args) {
    Undertow server = Undertow.builder().addHttpListener(8080, "localhost")
      .setHandler(path().addPrefixPath("/baeldungApp", websocket(
        (exchange, channel) -> {
          channel.getReceiveSetter().set(getListener());
          channel.resumeReceives();
      })).addPrefixPath("/", resource(new ClassPathResourceManager(
        SocketServer.class.getClassLoader(),
        SocketServer.class.getPackage())).addWelcomeFiles("index.html")))
        .build();

    server.start();
}

private static AbstractReceiveListener getListener() {
    return new AbstractReceiveListener() {
        @Override
        protected void onFullTextMessage(WebSocketChannel channel, 
          BufferedTextMessage message) {
            String messageData = message.getData();
            for (WebSocketChannel session : channel.getPeerConnections()) {
                WebSockets.sendText(messageData, session, null);
            }
        }
    };
}

Мы можем создать HTML-страницу с именем index.html и использовать JavaScript WebSocket API для подключения к этому каналу.

3.5. Файловый сервер

С помощью Undertow мы также можем создать файловый сервер, который может отображать содержимое каталога и напрямую обслуживать файлы из каталога:

public static void main( String[] args ) {
    Undertow server = Undertow.builder().addHttpListener(8080, "localhost")
        .setHandler(resource(new PathResourceManager(
          Paths.get(System.getProperty("user.home")), 100 ))
        .setDirectoryListingEnabled( true ))
        .build();
    server.start();
}

Нам не нужно создавать какой-либо пользовательский интерфейс для отображения содержимого каталога. Стандартный Undertow предоставляет страницу для этой функции отображения.

4. Плагин загрузки Spring

«Помимо Tomcat и Jetty, Spring Boot поддерживает UnderTow в качестве встроенного контейнера сервлетов. Чтобы использовать Undertow, нам нужно добавить следующую зависимость в pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
    <version>1.5.6.RELEASE</version>
</dependency>

Последняя версия плагина Spring Boot Undertow доступна в центральном репозитории Maven.

5. Заключение

В этой статье мы узнали о Undertow и о том, как с его помощью можно создавать различные типы серверов.

Как всегда, полный исходный код доступен на GitHub.