Внедрить WebSocket в службу REST - PullRequest
0 голосов
/ 09 октября 2018

Мне нужно отправить сообщение клиенту, после создания товара.Элемент создан ApiRest.Затем я создал свой WebSocket с @ApplicationScope и ввел в serviceREST с @Inject.Проблема в том, что когда webSocket был инициализирован, в моем serviceRest этот сеанс webSocket все еще пуст.Как я могу использовать веб-сокет в моем apirest?

@Path("citas")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class citaResource {

    @Inject
    com.softcase.citasmanager.websocket.ws websocket;

    @GET
    @Path("cita")
    @Produces("application/json")
    public Response cita() {
       websocket.onMessage("Your Item was created");//Session of webSocket is null
        return Response.ok("ItemCreated", MediaType.APPLICATION_JSON).build();
    }

}

@ApplicationScope
@ServerEndpoint("/item")
public class ws{   

    private Session session;

    @OnOpen
    public void open(Session session) {
        this.session = session;
    }

    @OnMessage
    public void onMessage(String message) {
            this.session.getBasicRemote().sendText(message);
    }

1 Ответ

0 голосов
/ 22 октября 2018

Небольшой контекст

Экземпляры : для каждой пары клиент-сервер существует уникальный экземпляр Session, т. Е. Для каждого клиента, который подключается к WebSocket, создается один экземпляр Sessionконечная точка сервера.Короче говоря, количество уникальных экземпляров Session равно числу подключенных клиентов

Источник: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html

Для более подробной информации: https://tyrus -project.github.io / Документация / 1.13.1 / index / lifecycle.html

Рекомендуется использовать static переменные, такие как

// @ApplicationScope
@ServerEndpoint("/item")
public class ws{   
    // something like
    private static final Set<javax.websocket.Session> ALL_SESSIONS = new HashSet<>();

    // ...
}

Пример можно найти здесь .Это вариант, но я не думаю, что он решает проблему с инъекцией.

Другой вариант - использовать метод javax.websocket.Session#getOpenedSessions(), такой как этот пример чата .Но, опять же, это не решает проблему инъекции.

Ваш пример

Вы используете и websocket, и REST.Как я понимаю, поток выглядит так:

  1. Пользователь A, B, C подключен
  2. Пользователь A отправляет запрос на citas/cita и получает ответ REST
  3. В то же время, A, B, C получают уведомление от веб-сокета

Итак, как вы писали, с одной стороны, у вас есть

@Path("citas")
// ...
public class CitaResource{
    // ...
}

и

// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{   
    // ...
}

В этом примере есть один экземпляр CitaResource, когда пользователь A сделал запрос, и три экземпляра ws при подключении A, B, C.Тем не менее, вы были правы в отношении внедрения: вам нужно что-то внедрить в CitaResource, но вам нужен всегда доступный компонент, и, как вы заметили, экземпляры websocket не являются хорошим вариантом, и какой сеанс должен вводить контейнер?

Обработчик сеансов websocket

Решение состоит в том, чтобы использовать бин области действия приложения для обработки всех существующих сеансов.Я получил это из Oracle учебник .Это выглядит так:

// com.softcase.citasmanager.websocket.SessionHandler
@ApplicatedScoped
@Named // optional
public class MySessionHandler{

    private final Set<Session> ALL_SESSIONS;
    // or use a map if you need to identify the
    // the session by a key. This example uses Set
    // private final Map<String, Session> ALL_SESSIONS;

    public MySessionHandler(){
        ALL_SESSIONS = new HashSet<>();
    }

    // manage sessions
    public void addSession(Session session){
        this.ALL_SESSIONS.add(session);
    }

    public void removeSession(Session session){
        this.ALL_SESSIONS.remove(session);
    }

    // send messages to all instances:
    public void sendMessage(String message){
        this.ALL_SESSIONS.stream()
            // optional
            .filter(s -> s.isOpen())
            // or whatever method you want to send a message
            .forEach( s -> s.getBasicRemote().sendText(message);
    }
    // or if you want to target a specific session
    // hence my questions in comments
    public void sendMessage(String message, String target){
        this.ALL_SESSIONS..stream()
            // identity the target session
            .filter(s -> s.equals(target))
            // optional
            .filter(s -> s.isOpen())
            .forEach( s -> s.getBasicRemote().sendText(message);
    }

}

Примечание:

  • Я дополнительно проверяю, что сохраненный сеанс все еще открыт.isOpen() не является обязательным, но это может избежать некоторых ошибок
  • Думайте обработчик сеанса как «капитан»: он знает все о сеансах websocket, тогда как сами сеансы не знают о каждомпрочее.

Однако вам необходимо настроить конечную точку для повышения эффективности обработчика сеанса:

// com.softcase.citasmanager.websocket.WsCita
@ServerEndpoint
public class WsCita{

    // there is no need to declare the session as attribute
    // private Session session;

    // ApplicatedScoped so always defined
    @Inject
    private MySessionHandler handler;

    @OnOpen
    public void open(Session session){
        handler.addSession(session);    // "Aye cap'tain, reporting from duty!"
        // your stuff
    }

    @OnClose
    public void close(Session session, CloseReason closeReason){
        handler.removeSession(session); // "Cya cap'tain, that's all for today!"
        // your stuff
    }

    // your OnMessage and other stuff here
}

Теперь мы установили нашу архитектуру веб-сокетов, что теперь?

  1. У вас есть один экземпляр WsCita на клиента.В любое время может быть ноль, один или несколько экземпляров.
  2. MySessionHandler знает эту информацию и составляет @ApplicatedScoped, поэтому ее безопасно ввести

Конечная точка REST затем изменится на:

@Path("citas")
// ...
public class citaResource {

    @Inject
    com.softcase.citasmanager.websocket.SessionHandler handler;

    @GET
    // ...
    public Response cita() {
        // REST processing
        // ...

        // Websocket processing:
        // - Handler is always here for you
        // - Handler knows which websocket sessions to send the message to.
        //   The RestController is not aware of the recipients
        handler.sendMessage("Your Item was created");
    }

}

Пожалуйстаобратите внимание, что я помещаю обработку веб-сокета после обработки REST, поскольку вы не всегда можете отправлять сообщение (например, создание или любое другое исключение).

Разное

Не имеет отношения к вашим вопросам, но у меня есть некоторые комментарии оВаш код:

  • Имя класса - CamelCase, начинающийся с заглавной буквы в соответствии с рекомендацией Oracle
  • Избегайте использования общего имени для ваших классов, например Ws.Я переименовал его WsCita для примера
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...