Статьи

Сборка и тестирование сервера веб-сокетов с загрузкой

Следующая версия JBoss Application Server больше не будет использовать Tomcat в качестве интегрированного веб-сервера, а заменит его на undertow . Архитектура undertow основана на обработчиках, которые можно динамически добавлять через API-интерфейс Builder на сервер. Этот подход аналогичен способу создания веб-сервера в Node.js. Это позволяет разработчикам легко встраивать веб-сервер undertow в свои приложения. Поскольку добавление функций осуществляется через API-интерфейс Builder, можно добавлять только те функции, которые действительно необходимы в приложении. Помимо этого, поддерживается версия WebSockets и Servlet API в версии 3.1. Его можно запускать как блокирующий или неблокирующий сервер, и говорят, что первые тесты доказали, что undertow является самым быстрым веб-сервером, написанным на Java.

Поскольку все это звучит очень многообещающе, давайте попробуем настроить простой сервер веб-сокетов. Как обычно, мы начинаем с создания простого Java-проекта и добавляем зависимость undertow maven:

1
2
3
4
5
<dependency>
  <groupId>io.undertow</groupId>
  <artifactId>undertow-core</artifactId>
  <version>1.0.0.Beta20</version>
</dependency>

С помощью API Undertow Builder наш метод buildAndStartServer () выглядит следующим образом:

1
2
3
4
5
6
7
public void buildAndStartServer(int port, String host) {
    server = Undertow.builder()
            .addListener(port, host)
            .setHandler(getWebSocketHandler())
            .build();
    server.start();
}

Мы просто добавляем прослушиватель, который указывает порт и хост для прослушивания входящих соединений, а затем добавляем обработчик websocket. Поскольку код обработчика веб-сокетов немного более полон, я поместил его в собственный метод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private PathHandler getWebSocketHandler() {
    return path().addPath("/websocket", websocket(new WebSocketConnectionCallback() {
        @Override
        public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) {
            channel.getReceiveSetter().set(new AbstractReceiveListener() {
                @Override
                protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) {
                    String data = message.getData();
                    lastReceivedMessage = data;
                    LOGGER.info("Received data: "+data);
                    WebSockets.sendText(data, channel, null);
                }
            });
            channel.resumeReceives();
        }
    }))
    .addPath("/", resource(new ClassPathResourceManager(WebSocketServer.class.getClassLoader(), WebSocketServer.class.getPackage()))
            .addWelcomeFiles("index.html"));
}

Давайте пройдем по строке через этот фрагмент кода. Прежде всего мы добавим новый путь: / websocket. Второй аргумент методов addPath () позволяет нам указать, какой протокол мы хотим использовать для этого пути. В нашем случае мы создаем новый WebSocket. Анонимная реализация имеет метод onConnect (), в котором мы устанавливаем реализацию AbstractReceiveListener. Здесь у нас есть удобный метод onFullTextMessage (), который вызывается, когда клиент отправил нам текстовое сообщение. Вызов getData () извлекает фактическое сообщение, которое мы получили. В этом простом примере мы просто возвращаем эту строку клиенту, чтобы проверить, работает ли туда-обратно клиент и сервер и обратно.

Для выполнения простых ручных тестов мы также добавляем второй ресурс под путем /, который обслуживает некоторые статические файлы HTML и JavaScript. Каталог, содержащий эти файлы, предоставляется в качестве экземпляра ClassPathResourceManager. Вызов addWelcomeFiles () сообщает, какой файл серверу, когда клиент запрашивает путь /.

Index.html выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
</pre>
<html>
<head><title>Web Socket Test</title></head>
<body>
  <script src="jquery-2.0.3.min.js"></script>
  <script src="jquery.gracefulWebSocket.js"></script>
  <script src="websocket.js"></script>
  <form onsubmit="return false;">
    <input type="text" name="message" value="Hello, World!"/>
    <input type="button" value="Send Web Socket Data" onclick="send(this.form.message.value)"/>
  </form>
  <div id="output"></div>
</body>
</html>
<pre>

Наш код JavaScript выгружается в файл websocket.js. Мы используем jquery и jquery-плагин gracefulWebSocket, чтобы упростить разработку на стороне клиента:

1
2
3
4
5
6
7
8
9
var ws = $.gracefulWebSocket("ws://127.0.0.1:8080/websocket");
ws.onmessage = function(event) {
    var messageFromServer = event.data;
    $('#output').append('Received: '+messageFromServer+'');
}
 
function send(message) {
    ws.send(message);
}

После создания объекта WebSocket путем вызова $ .gracefulWebSocket () мы можем зарегистрировать функцию обратного вызова для входящих сообщений. В этом методе мы только добавляем строку сообщения в DOM страницы. Метод send () — это просто вызов метода send () gracefulWebSocket.

Теперь, когда мы запускаем наше приложение и открываем URL-адрес http://127.0.0.1:8080/ в нашем веб- браузере, мы видим следующую страницу:

прибойный-WebSocket

Ввод какой-либо строки и нажатие кнопки «Отправить данные веб-сокета» отправляет сообщение на сервер, который в ответ возвращает его клиенту.

Теперь, когда мы знаем, что все работает как положено, мы хотим защитить наш код от регрессии с помощью тестового примера junit. В качестве клиента websocket я выбрал библиотеку jetty-websocket:

1
2
3
4
5
6
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-websocket</artifactId>
    <version>8.1.0.RC5</version>
    <scope>test</scope>
</dependency>

В тестовом примере мы создаем и запускаем сервер websocket, чтобы открыть новое соединение с портом websocket. Реализация jetty-websocket в WebSocket позволяет нам реализовать два метода обратного вызова для событий open и close. В рамках открытого обратного вызова мы отправляем тестовое сообщение клиенту. Остальная часть кода ожидает установления соединения, закрывает его и подтверждает, что сервер получил сообщение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void testStartAndBuild() throws Exception {
    subject = new WebSocketServer();
    subject.buildAndStartServer(8080, "127.0.0.1");
    WebSocketClient client = new WebSocketClient();
    Future connectionFuture = client.open(new URI("ws://localhost:8080/websocket"), new WebSocket() {
        @Override
        public void onOpen(Connection connection) {
            LOGGER.info("onOpen");
            try {
                connection.sendMessage("TestMessage");
            } catch (IOException e) {
                LOGGER.error("Failed to send message: "+e.getMessage(), e);
            }
        }
        @Override
        public void onClose(int i, String s) {
            LOGGER.info("onClose");
        }
    });
    WebSocket.Connection connection = connectionFuture.get(2, TimeUnit.SECONDS);
    assertThat(connection, is(notNullValue()));
    connection.close();
    subject.stopServer();
    Thread.sleep(1000);
    assertThat(subject.lastReceivedMessage, is("TestMessage"));
}
  • Как обычно, вы можете найти исходный код на github .

Вывод

API-интерфейс Undertow Builder упрощает создание сервера веб-сокетов и в целом встроенного веб-сервера, который соответствует вашим потребностям. Это также упрощает автоматическое тестирование, поскольку вам не требуется какой-либо специальный плагин maven, который запускает и останавливает ваш сервер до и после ваших интеграционных тестов. Помимо этого, плагин jquery jquery-graceful-websocket позволяет отправлять и получать сообщения через веб-сокеты всего с несколькими строками кода.