Достъп до ServletContext и HttpSession в @OnMessage на JSR-356 @ServerEndpoint

Трябва да получа ServletContext от вътрешността на @ServerEndpoint, за да намеря Spring ApplicationContext и да потърся Bean.

За момента най-добрият ми подход е да обвържа този bean в JNDI контекста на именуване и да го потърся в Endpoint. Всяко по-добро решение е добре дошло.

Също така търся разумен начин за синхронизиране на HttpSession на сървлета с Session на websocket.


person Sombriks    schedule 19.02.2014    source източник
comment
Също така се опитвам да осъществя достъп до ServletContext от крайна точка на уебсокет. Опитвам се да направя това в Tomcat.   -  person egerardus    schedule 29.04.2014
comment
Вижте stackoverflow.com/questions/22880055/   -  person anttix    schedule 02.05.2014
comment
Трябва ли да търсите зърна? Защо не можете да го инжектирате? Трябва да може да се инжектира, ако използвате configurator = org.springframework.web.socket.server.endpoint.SpringConfigurator.class във вашата @ServerEndpoint анотация.   -  person Peter G    schedule 03.05.2014


Отговори (4)


Сървлетът HttpSession е в JSR-356, достъпен от HandshakeRequest#getHttpSession(), което от своя страна е достъпно, когато се направи заявка за ръкостискане точно преди @OnOpen от @ServerEndpoint. ServletContext от своя страна е достъпен само чрез HttpSession#getServletContext(). Това са два заека с един камък.

За да заснемете заявката за ръкостискане, имплементирайте ServerEndpointConfig.Configurator и замени modifyHandshake() метод. HandshakeRequest тук е наличен като аргумент на метода. Можете да поставите HttpSession в EndpointConfig#getUserProperties(). EndpointConfig от своя страна е наличен като аргумент на метод @OnOpen.

Ето начален пример за внедряване на ServerEndpointConfig.Configurator:

public class ServletAwareConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        config.getUserProperties().put("httpSession", httpSession);
    }

}

Ето как можете да го използвате, обърнете внимание на configurator атрибут на @ServerEndpoint:

@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {

    private EndpointConfig config;

    @OnOpen
    public void onOpen(Session websocketSession, EndpointConfig config) {
        this.config = config;
    }

    @OnMessage
    public void onMessage(String message) {
        HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
        ServletContext servletContext = httpSession.getServletContext();
        // ...
    }

}

Като намек за дизайн, най-добре е да запазите своя @ServerEndpoint напълно свободен от зависимости на API на сървлета. Бихте могли в modifyHandshake() изпълнението по-добре веднага да извлечете точно тази информация (обикновено променлив Javabean), от която се нуждаете от сесията на сървлета или контекста, и вместо това да ги поставите в картата на потребителските свойства. Ако не направите това, тогава трябва да имате предвид, че една websocket сесия може да живее по-дълго от HTTP сесията. Така че, когато все още носите около HttpSession в крайната точка, тогава може да се натъкнете на IllegalStateException, когато се опитате да получите достъп до него, докато е изтекъл.

В случай, че случайно имате CDI (и може би JSF) под ръка, може да почерпите вдъхновение от изходния код на OmniFaces <o:socket> (връзките са в най-долната част на витрината).

Вижте също:

person BalusC    schedule 01.05.2014
comment
Благодаря за това обяснение и особено за намека да запазите крайните точки свободни от зависимости на API на сървлета. - person hfmanson; 18.05.2014
comment
Вместо това инжектирайте контекста на сървлета като потребителско свойство, вмъкнах Spring контекста; не е като автоматично свързване на зърна, но работи и реши проблема ми. - person Sombriks; 20.05.2014
comment
за websocket изглежда, че кодът в примера на Omniface използва HTTP заявка за увеличаване на брояча. Какво пропускам тук, не противоречи ли на смисъла? - person Ced; 08.05.2016
comment
@Ced: просто задейства натискането. - person BalusC; 08.05.2016
comment
Ако някой получава null, докато получава HttpSession, ето решението: stackoverflow. com/questions/20240591/ - person Kashyap Kansara; 23.07.2019

Актуализиран код за отговора на BalusC, методът onOpen трябва да бъде украсен с @OnOpen. Тогава вече няма нужда да разширявате класа Endpoint:

@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {

    private EndpointConfig config;

    @OnOpen
    public void onOpen(Session websocketSession, EndpointConfig config) {
        this.config = config;
    }

    @OnMessage
    public void onMessage(String message) {
        HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
        ServletContext servletContext = httpSession.getServletContext();
        // ...
    }

}
person hfmanson    schedule 18.05.2014

Изпробвах отговора на BalusC на Tomcat (версии 7.0.56 и 8.0.14). И в двата контейнера параметърът на заявката на modifyHandshake не съдържа HttpSession (и следователно няма servletContext). Тъй като имах нужда от контекста на сървлета само за достъп до "глобални" променливи (т.е. глобални за уеб приложение), аз просто съхраних тези променливи в обикновено статично поле на клас притежател. Това е неелегантно, но проработи.

Това изглежда като грешка в тази конкретна версия на tomcat - някой виждал ли е това?

person Wolfgang Liebich    schedule 21.11.2014
comment
Не, не е бъг. Това показва, че httpSession просто не е създадена. Можете да го създадете сами предварително. Вижте stackoverflow.com/questions/20240591/ за повече информация . - person Kalle; 18.01.2016
comment
Благодаря - това е решение, за което не се сетих (мислех, че сесията винаги се създава от инфраструктурата). Уви, тъй като исках да използвам това само за глобални данни на приложението, все пак ще се придържам към ръчно изработеното решение, тъй като работи сега. - person Wolfgang Liebich; 01.02.2016
comment
(Това не трябваше да се публикува като отговор) Поведението е същото в Jetty. Както посочва @Kalle; просто използвайте HttpFilter или ServletRequestListener и извикайте request.getSession(true) - жалко, че няма подобно претоварване на HandshakeRequest.getHttpSession() - person earcam; 24.11.2017

Понякога не можем да получим session с над ServletAwareConfig на BalusC, това е така, защото сесията все още не е създадена. тъй като ние не търсим сесия, а servletContext, в tomcat можем да направим следното:

public static class ServletAwareConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        try {
            Field reqfld = request.getClass().getDeclaredField("request");
            reqfld.setAccessible(true);
            HttpServletRequest req = (HttpServletRequest) reqfld.get(request);
            ServletContext ctxt = req.getServletContext();
            Map<String, Object> up = config.getUserProperties();
            up.put("servletContext", ctxt);
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
    }

}

ако искаме init сесия веднага, можем да извикаме request.getSession().

Реф.: https://stackoverflow.com/questions/20240591/websocket-httpsession-returns-null

person Inshua    schedule 29.11.2018