Статьи

JSR 356, Java API для WebSocket

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

Ряд «хаков» использовался в прошлом для обхода этой проблемы, например, длинный опрос и комета. Однако потребность в стандартном, двунаправленном и дуплексном канале между клиентами и сервером только возросла.

В 2011 году IETF стандартизировал протокол WebSocket как RFC 6455. С тех пор большинство веб-браузеров реализуют клиентские API, поддерживающие протокол WebSocket. Кроме того, был разработан ряд библиотек Java, которые реализуют протокол WebSocket.

Протокол WebSocket использует технологию обновления HTTP для обновления HTTP-соединения до WebSocket. После обновления соединение способно отправлять сообщения (кадры данных) в обоих направлениях, независимо друг от друга (полный дуплекс). Не требуются заголовки или файлы cookie, что значительно снижает требуемую пропускную способность. Как правило, WebSockets используются для периодической отправки небольших сообщений (например, нескольких байтов). Дополнительные заголовки часто делают издержки больше, чем полезная нагрузка.

JSR 356

JSR 356, Java API для WebSocket, определяет API, который разработчики Java могут использовать, когда они хотят интегрировать WebSockets в свои приложения — как на стороне сервера, так и на стороне клиента Java. Каждая реализация протокола WebSocket, которая претендует на совместимость с JSR 356, должна реализовывать этот API. Как следствие, разработчики могут писать свои приложения на основе WebSocket независимо от базовой реализации WebSocket. Это огромное преимущество, поскольку оно предотвращает блокировку от поставщиков и предоставляет больше возможностей для выбора и свободы библиотек и серверов приложений.

JSR 356 является частью будущего стандарта Java EE 7; следовательно, все серверы приложений, совместимые с Java EE 7, будут иметь реализацию протокола WebSocket, которая соответствует стандарту JSR 356. После того, как они установлены, клиент и сервер WebSocket становятся симметричными. Поэтому разница между клиентским API и серверным API минимальна. JSR 356 также определяет клиентский API Java, который является подмножеством полного API, требуемого в Java EE 7.

Клиент-серверное приложение, использующее WebSockets, обычно содержит серверный компонент и один или несколько клиентских компонентов, как показано на рисунке 1:

фигура 1

фигура 1

В этом примере серверное приложение написано на Java, а детали протокола WebSocket обрабатываются реализацией JSR 356, содержащейся в контейнере Java EE 7.

Клиент JavaFX может полагаться на любую реализацию клиента, совместимую с JSR 356, для решения проблем протокола, специфичных для WebSocket. Другие клиенты (например, клиент iOS и клиент HTML5) могут использовать другие (не Java) реализации, совместимые с RFC 6455, для взаимодействия с серверным приложением.

Модель программирования

Группа экспертов, которая определила JSR 356, хотела поддерживать шаблоны и методы, которые являются общими для разработчиков Java EE. Как следствие, JSR 356 использует аннотации и инъекции.

В общем, поддерживаются две разные модели программирования:

  • Аннотация инициативы. Используя аннотированные POJO, разработчики могут взаимодействовать с событиями жизненного цикла WebSocket.
  • Интерфейс с приводом. Разработчики могут реализовать Endpointинтерфейс и методы, которые взаимодействуют с событиями жизненного цикла.

События жизненного цикла

Типичное событие жизненного цикла взаимодействия WebSocket происходит следующим образом:

  • Один узел (клиент) инициирует соединение, отправляя запрос HTTP-рукопожатия.
  • Другой узел (сервер) отвечает ответом рукопожатия.
  • Соединение установлено. Отныне соединение полностью симметрично.
  • Оба узла отправляют и получают сообщения.
  • Один из пиров закрывает соединение.

Большинство событий жизненного цикла WebSocket могут быть сопоставлены с методами Java как в подходах, основанных на аннотациях, так и в интерфейсах.

Подход, основанный на аннотациях

Конечной точкой, принимающей входящие запросы WebSocket, может быть POJO, аннотированный @ServerEndpointаннотацией. Эта аннотация сообщает контейнеру, что данный класс следует рассматривать как конечную точку WebSocket. Обязательный valueэлемент указывает путь к конечной точке WebSocket.

Рассмотрим следующий фрагмент кода:

@ClientEndpoint 
public class MyClientEndpoint {}

Инициирование соединения WebSocket в Java с использованием подхода POJO, управляемого аннотациями, может быть выполнено следующим образом:

javax.websocket.WebSocketContainer container = 
javax.websocket.ContainerProvider.getWebSocketContainer();

container.conntectToServer(MyClientEndpoint.class, 
new URI("ws://localhost:8080/tictactoeserver/endpoint"));

В дальнейшем классы, помеченные @ServerEndpointили @ClientEndpointбудут называться аннотированными конечными точками.

Как только соединение WebSocket установлено, Sessionсоздается и вызывается метод, аннотированный @OnOpenна аннотированной конечной точке. Этот метод может содержать ряд параметров:

  • javax.websocket.SessionПараметр, указав созданныйSession
  • EndpointConfigЭкземпляр , содержащий информацию о конфигурации конечной точки
  • Ноль или более строковых параметров, помеченных @PathParam, ссылаясь на параметры пути на пути конечной точки

Следующая реализация метода напечатает идентификатор сеанса, когда WebSocket будет «открыт»:

@OnOpen
public void myOnOpen (Session session) {
   System.out.println ("WebSocket opened: "+session.getId());
}

SessionЭкземпляр действует до тех пор , как WebSocket не закрыта. SessionКласс содержит ряд интересных методов , которые позволяют разработчикам получить более подробную информацию о подключении. Кроме того, он Sessionсодержит привязку к данным конкретного приложения с помощью getUserProperties()метода, возвращающего a Map<String, Object>. Это позволяет разработчикам заполнять Sessionэкземпляры информацией о сеансах и приложениях, которая должна быть распределена между вызовами методов.

Когда конечная точка WebSocket получает сообщение, вызывается метод, помеченный @OnMessageкак. Метод с комментариями @OnMessageможет содержать следующие параметры:

  • javax.websocket.SessionПараметр.
  • Ноль или более строковых параметров, помеченных с помощью @PathParamссылки на параметры пути на пути конечной точки.
  • Само сообщение. Ниже приведен обзор возможных типов сообщений.

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

@OnMessage
public void myOnMessage (String txt) {
   System.out.println ("WebSocket received message: "+txt);
} 


If the return type of the method annotated with @OnMessage is not void, the WebSocket implementation will send the return value to the other peer. The following code snippet returns the received text message in capitals back to the sender:

@OnMessage
public String myOnMessage (String txt) {
   return txt.toUpperCase();
} 

Другой способ отправки сообщений через соединение WebSocket показан ниже:

RemoteEndpoint.Basic other = session.getBasicRemote();
other.sendText ("Hello, world");

В этом подходе мы начинаем с Sessionобъекта, который можно получить из методов обратного вызова жизненного цикла (например, метода, помеченного как @OnOpen). getBasicRemote()Способ по Sessionпримеру возвращает представление другой части WebSocket, с RemoteEndpoint. Этот RemoteEndpointэкземпляр можно использовать для отправки текстовых или других типов сообщений, как описано ниже.

Когда соединение WebSocket закрывается, вызывается метод, помеченный @OnCloseкак. Этот метод может принимать следующие параметры:

  • javax.websocket.SessionПараметр. Обратите внимание, что этот параметр нельзя использовать, когда WebSocket действительно закрыт, что происходит после @OnCloseвозврата аннотированного метода.
  • javax.websocket.CloseReasonПараметр , описывающий причину для закрытия WebSocket, например, нормального замыкание, ошибки протокола, перегруженное обслуживание, и так далее.
  • Ноль или более строковых параметров, помеченных с помощью @PathParamссылки на параметры пути на пути конечной точки.

Следующий фрагмент кода напечатает причину закрытия WebSocket:

@OnClose
public void myOnClose (CloseReason reason) {
   System.out.prinlnt ("Closing a WebSocket due to "+reason.getReasonPhrase());
}

Чтобы завершить, есть еще одна аннотация жизненного цикла: в случае получения ошибки @OnErrorбудет вызван метод, помеченный как.

Управляемый интерфейсом подход

Подход, основанный на аннотациях, позволяет нам аннотировать класс и методы Java аннотациями жизненного цикла. Используя подход интерфейса с приводом, разработчик расширяет javax.websocket.Endpointи отменяет onOpen, onCloseи onErrorметоды:

public class myOwnEndpoint extends javax.websocket.Endpoint {
   public void onOpen(Session session, EndpointConfig config) {...}
   public void onClose(Session session, CloseReason closeReason) {...}
   public void onError (Session session, Throwable throwable) {...}
}

Для перехвата сообщений в реализации javax.websocket.MessageHandlerнеобходимо зарегистрировать onOpen:

public void onOpen (Session session, EndpointConfig config) {
   session.addMessageHandler (new MessageHandler() {...});
}

MessageHandlerинтерфейс с двумя подинтерфейсами: MessageHandler.Partialи MessageHandler.Whole. MessageHandler.PartialИнтерфейс должен быть использован , когда разработчик хочет получать уведомления о частичных поставках сообщений, а также реализация MessageHandler.Wholeдолжна быть использована для уведомления о прибытии полного сообщения.

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

public void onOpen (Session session, EndpointConfig config) {
   final RemoteEndpoint.Basic remote = session.getBasicRemote();
   session.addMessageHandler (new MessageHandler.Whole<String>() {
      public void onMessage(String text) {
                 try {
                     remote.sendString(text.toUpperCase());
                 } catch (IOException ioe) {
                     // handle send failure here
                 }
             }

   });
}

Типы сообщений, кодеры и декодеры

Java API для WebSocket является очень мощным, поскольку позволяет отправлять или получать любой объект Java в виде сообщения WebSocket.

В основном, есть три разных типа сообщений:

  • Текстовые сообщения
  • Двоичные сообщения
  • Сообщения Pong, касающиеся самого соединения с WebSocket

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

При использовании модели на основе аннотаций для каждого отдельного типа сообщения @onMessageдопускается один аннотированный метод. Допустимые параметры для указания содержимого сообщения в аннотированных методах зависят от типа сообщения.

В Javadoc для @OnMessageаннотации четко указаны допустимые параметры сообщения в зависимости от типа сообщения (из Javadoc указано следующее):

  • «если метод обрабатывает текстовые сообщения:
    • String получить целое сообщение
    • Примитив Java или эквивалент класса для получения всего сообщения, преобразованного в этот тип
    • String и логическая пара для получения сообщения по частям
    • Reader получить сообщение целиком в виде блокирующего потока
    • любой параметр объекта, для которого конечная точка имеет текстовый декодер ( Decoder.Textили Decoder.TextStream).
  • если метод обрабатывает двоичные сообщения:
    • byte[]или ByteBufferполучить сообщение целиком
    • byte[]и логическая пара, или ByteBufferлогическая пара, чтобы получить сообщение по частям
    • InputStream получить сообщение целиком в виде блокирующего потока
    • любой параметр объекта, для которого конечная точка имеет двоичный декодер ( Decoder.Binaryили Decoder.BinaryStream).
  • если метод обрабатывает сообщения pong:
    • PongMessage для обработки сообщений понг

Любой объект Java может быть закодирован в текстовое или двоичное сообщение с использованием кодера. Это текстовое или двоичное сообщение передается другому узлу, где оно может быть снова декодировано в Java-объект или может быть интерпретировано другой библиотекой WebSocket. Часто XML или JSON используются для передачи сообщений WebSocket, а затем кодирование / декодирование сводится к маршалированию объекта Java в XML или JSON и обратно.

Кодер определен как реализация javax.websocket.Encoderинтерфейса, а декодер — реализация javax.websocket.Decoderинтерфейса. Каким-то образом экземплярам конечной точки необходимо знать, каковы возможные кодеры и декодеры. Использование подхода аннотаций привода, список кодеров и декодеров пропускают через кодер и декодер элементов в @ClientEndpointи @ServerEndpointаннотации.

Код в листинге 1 показывает, как зарегистрировать MessageEncoderкласс, который определяет преобразование экземпляра MyJavaObjectв текстовое сообщение. MessageDecoderКласс зарегистрирован для преобразования противоположного.

@ServerEndpoint(value="/endpoint", encoders = MessageEncoder.class, decoders= MessageDecoder.class)
public class MyEndpoint {
...
}

class MessageEncoder implements Encoder.Text<MyJavaObject> {
   @override
   public String encode(MyJavaObject obj) throws EncodingException {
      ...
   }
}

class MessageDecoder implements Decoder.Text<MyJavaObject> {
   @override 
   public MyJavaObject decode (String src) throws DecodeException {
      ...
   }

   @override 
   public boolean willDecode (String src) {
      // return true if we want to decode this String into a MyJavaObject instance
   }
}

Листинг 1

EncoderИнтерфейс имеет ряд субинтерфейсов:

  • Encoder.Text для преобразования объектов Java в текстовые сообщения
  • Encoder.TextStream для добавления объектов Java в поток символов
  • Encoder.Binary для преобразования объектов Java в двоичные сообщения
  • Encoder.BinaryStream для добавления объектов Java в двоичный поток

Аналогично, Decoderинтерфейс имеет четыре подинтерфейса:

  • Decoder.Text для преобразования текстового сообщения в объект Java
  • Decoder.TextStream для чтения объекта Java из потока символов
  • Decoder.Binary для преобразования двоичного сообщения в объект Java
  • Decoder.BinaryStream для чтения объекта Java из двоичного потока

Вывод

Java API для WebSocket предоставляет разработчикам Java стандартный API для интеграции со стандартом IETF WebSocket. Таким образом, веб-клиенты или собственные клиенты, использующие любую реализацию WebSocket, могут легко обмениваться данными с серверной частью Java.

Java API легко настраивается и гибок, и позволяет разработчикам Java использовать их предпочтительные шаблоны.

Смотрите также

об авторе

Энтузиаст как встраиваемых, так и корпоративных разработок, Vos фокусируется на сквозной Java с использованием JavaFX и Java EE. Узнайте больше в его блоге или следуйте за ним по адресу http://twitter.com/johanvos .