Как закрыть пробел между конечной точкой сервера websocket и компонентами поддержки сессий - PullRequest
0 голосов
/ 04 сентября 2018

Короче говоря, я не знаю, как уведомить сессионный компонент из компонента. Я нашел грязный хак, который работает, но я не уверен, что есть лучший способ решить эту проблему (или мой грязный хак не грязный :-D).

У каждого веб-сокета в java ee есть идентификатор сеанса. Но этот sessionid не такой, как в jsf, и нет способа для простого отображения. В моей среде у меня есть веб-страница jsf, базовый компонент сессионной области и веб-сокет, который подключен к внешней службе через jms. Когда страница jsf загружена и веб-сокет также подключен к браузеру, компонент поддержки отправляет запрос во внешнюю службу. Когда я получил асинхронное ответное сообщение через jms, я не знаю, какой веб-сокет связан со страницей jsf / компонентом поддержки, отправляющим запрос.

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

@Named
@ApplicationScoped
public class WebsocketMediator {

    @Inject
    private Event<UUID> notifyBackingBeans;

    private Integer newSequenceId=0;

    // I need this map for the dirty hack
    private Map<UUID, BackingBean> registrationIdBackingBeanMap;

    private Map<Integer, UUID> sequenceIdRegistrationIdMap;
    registrationIdWebSocketMap = new ConcurrentHashMap<>();

    public UUID register(BackingBean backingBean) {
        UUID registrationId = UUID.randomUUID();

        registrationIdSequenceMap.put(registrationId, new HashSet<>());
        registrationIdBackinBeanMap.put(registrationId, backingBean);
    }

    public Integer getSequenceId(UUID registrationId) {
        sequenceId++;
        sequenceIdRegistrationIdMap.put(sequenceId, registrationId);
        registrationIdSequenceMap.get(registrationId).add(sequenceId);
        return sequenceId;
    }

    // this is called from the ws server enpoint
    public void registerWebsocket(UUID registrationId, Session wsSession) {
        registrationIdWebSocketMap.put(registrationId, wsSession);
        websocketRegistrationIdMap.put(wsSession.getId(), registrationId);
        notifyBackingBeans.fire(registrationId); // This does not work

        SwitchDataModel switchDataModel = registrationIdSwitchDataModelMap.get(registrationId);
        if (backingBean != null) {
            backingBean.dirtyHackTrigger();
        }
    }

    public void unregisterWebsocket(String wsSessionId) {
         ...
    }
}

Поддерживающий компонент вызывает метод регистрации и получает уникальный случайный идентификатор регистрации (uuid). Регистрационный идентификатор помещается в таблицу jsf как скрытый атрибут данных (f: passTrough). Когда веб-сокет подключен, в браузере вызывается функция ws.open, которая отправляет идентификатор регистрации через веб-сокет в класс конечных точек сервера веб-сокетов. Класс конечной точки сервера вызывает метод посредника public void registerWebsocket(UUID registrationId, Session wsSession), и идентификатор регистрации сопоставляется. Когда резервный бин истекает, я вызываю незарегистрированный метод из аннотированного метода @PreDestroyed. Каждый раз, когда внешняя система вызывается через jms, я помещаю идентификатор последовательности в полезную нагрузку. Идентификатор последовательности зарегистрирован в классе медиатора. Каждый раз, когда внешняя система отправляет сообщение, я могу найти правильный посредник в посреднике, чтобы пропустить это сообщение через правильный подходящий браузер.

Теперь система может получать асинхронные события через внешнюю систему, но компонент поддержки этого не знает. Я попытался отправить регистрационный идентификатор в событии cdi бэк-бину области сеанса, но событие так и не достигло наблюдателя в бэк-бэк области сеанса. Так что я понял эту болтовню с грязным хаком. Я помещаю экземпляр каждого компонента поддержки с идентификатором регистрации в качестве ключа в карту в посреднике в методе регистрации. Я поместил в public void registerWebsocket(UUID registrationId, Session wsSession) грязный триггерный вызов бэк-компонента. Есть ли лучшее решение?

Я использую Wildfly 13 с CDI 1.2.

Заранее спасибо!

1 Ответ

0 голосов
/ 20 июля 2019

Я нашел решение. Когда вызывается веб-страница, создается компонент @SessionScoped. Я рассчитываю уникальный регистрационный идентификатор и помещаю его как атрибут в HttpSession:

@PostConstruct
public void register() {
    registrationId = switchPortMediator.register(this);
    HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
    session.setAttribute("switchRegistrationId", registrationId);
    log.debug("Registered at mediator - ID=" + registrationId + " http session = "+ session.getId());
}

В аннотированной конечной точке веб-сокета @ServerEndpoint используется аннотированный метод @OnOpen. Этот метод вызывается, когда веб-страница загружена и веб-сокет установлен. Класс конечной точки websocket - @ApplicationScoped. Карта атрибутов из bean-компонента @SessionScoped, где хранится идентификатор регистрации, доступна в EndpointConfig конечной точки веб-сокета. Вот аннотированный метод @OnOpen:

@OnOpen
public void onOpen(Session wsSession, EndpointConfig config) {
    UUID registrationId = (UUID) config.getUserProperties().get("switchRegistrationId");
   websocketRegistrationIdMap.put(registrationId, wsSession);
}

Когда страница JSF загружена, бин @SessionScoped вычисляет идентификатор регистрации, помещает его в карту атрибутов и отправляет асинхронное сообщение через jms во внешнюю систему. Внешняя система отправляет ответное сообщение с регистрационным идентификатором внутри и полезной нагрузкой. Когда внешнее сообщение поступает через JMS в классе конечных точек websocket, результирующий wsSession может быть извлечен из websocketRegistrationIdMap, а полезная нагрузка может быть отправлена ​​через websocket в браузер, который инициирует асинхронное сообщение. Обновления DOM на веб-сайте обрабатываются JavaScript.

...