Http Sessions жизненный цикл в Tomcat - PullRequest
4 голосов
/ 13 апреля 2011

У меня есть задача показать администратору сайта список имен пользователей и сколько сеансов tomcat в настоящее время используются каждым пользователем (вместе с некоторой другой информацией, связанной с поддержкой).

Я сохраняю аутентифицированных пользователейв качестве атрибута контекста приложения следующим образом (исключая ненужные детали).

Hashtable<String, UserInfo> logins //maps login to UserInfo

, где UserInfo определяется как

class UserInfo implements Serializable {
String login;
private transient Map<HttpSession, String> sessions = 
      Collections.synchronizedMap(
             new WeakHashMap<HttpSession, String>() //maps session to sessionId
      );
...
}

Каждый успешный вход в систему сохраняет сеанс в этой карте sessions.Моя HttpSessionsListener реализация в sessionDestroyed() удаляет уничтоженный сеанс из этой карты и, если sessions.size () == 0, удаляет UserInfo из logins.

Время от времени у меня отображается 0 сеансовдля некоторых пользователей.Рецензирование и модульное тестирование показывают, что код верен.Все сеансы сериализуемы.

Возможно ли, что Tomcat выгружает сеансы из памяти на жесткий диск, например, когда существует период бездействия (время ожидания сеанса установлено на 40 минут)?Существуют ли другие сценарии, в которых сеансы «теряются» с точки зрения GC, но HttpSessionsListener.sessionDestroyed () не был вызван?

J2SE 6, Tomcat 6 или 7 автономно, поведение согласовано в любой ОС.

Ответы [ 4 ]

3 голосов
/ 10 января 2014

Поскольку этот вопрос приблизился к 5k просмотров, я думаю, что было бы полезно привести пример рабочего решения.

Подход, изложенный в вопросе, неверен - он не будет обрабатывать перезапуски сервера и будетне масштаб.Вот лучший подход.

Во-первых, ваш HttpServlet должен обрабатывать пользовательские входы и выходы из системы, что-то вроде этого:

public class ExampleServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
              throws ServletException, IOException {
        String action = req.getParameter("do"); 
        HttpSession session =  req.getSession(true);
        //simple plug. Use your own controller here.
        switch (action) {
            case "logout":
                session.removeAttribute("user");
                break;
            case "login":
                User u = new User(session.getId(), req.getParameter("login"));
                //store user on the session
                session.setAttribute("user",u);                    
                break;
        }
    }
}

Пользовательский компонент должен быть Serializable и должен перерегистрировать себя после десериализации:

class User implements Serializable {

    private String sessionId;
    private String login;

    User(String sessionId, String login) {
        this.sessionId = sessionId;
        this.login = login;
    }

    public String getLogin() { return login; }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        //re-register this user in sessions 
        UserAttributeListener.sessions.put(sessionId,this); 
    }

}

Вам также понадобится HttpAttributeListener для правильной обработки жизненного цикла сеанса:

public class UserAttributeListener implements HttpSessionAttributeListener {

    static Map<String, User> sessions = new ConcurrentHashMap<>();

    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        if ("user".equals(event.getName()))
             sessions.put(event.getSession().getId(), (User) event.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        if ("user".equals(event.getName()))
            ExampleServlet.sessions.remove(event.getSession().getId());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        if ("user".equals(event.getName()))
            ExampleServlet.sessions.put(event.getSession().getId(),
                      (User)event.getValue());
    }
}

Конечно, вам необходимо зарегистрировать слушателя в web.xml:

<listener>
    <listener-class>com.example.servlet.UserAttributeListener</listener-class>
</listener>

После этого вы всегда можете получить доступ к статической карте в UserAttributeListener, чтобы получить представление о том, сколько сеансов запущено, сколько сеансов использует каждый пользователь и т. Д. В идеале у вас должна быть немного более сложная структура данных, гарантирующая еесобственный отдельный синглтон-класс с надлежащими методами доступа.Использование контейнеров с параллельной стратегией копирования при записи также может быть хорошей идеей, в зависимости от варианта использования.

2 голосов
/ 21 апреля 2011

Вместо того, чтобы что-то писать с нуля, проверьте psi-probe.

http://code.google.com/p/psi-probe/

Это может быть просто капля, которая решает ваши проблемы.

1 голос
/ 29 августа 2018

Когда клиент посещает веб-приложение в первый раз и / или HttpSession впервые получен через request.getSession(), контейнер сервлета создает новый объект HttpSession, генерирует длинный и уникальный идентификатор (который вы можете получить по session.getId()) и сохранить его в памяти сервера. Контейнер сервлета также устанавливает Cookie в заголовке Set-Cookie ответа HTTP с JSESSIONID в качестве имени и уникальным идентификатором сеанса в качестве значения.

В соответствии со спецификацией HTTP cookie (контракт, который должен соблюдать приличный веб-браузер и веб-сервер), клиент (веб-браузер) должен отправлять этот куки-файл обратно в последующих запросах в заголовке куки-файла так долго поскольку файл cookie действителен (т. е. уникальный идентификатор должен относиться к не истекшему сеансу, а домен и путь указаны правильно). Используя встроенный в ваш браузер монитор HTTP-трафика, вы можете убедиться, что файл cookie действителен (нажмите F12 в Chrome / Firefox 23+ / IE9 + и откройте вкладку Сеть / Сеть). Контейнер сервлета будет проверять заголовок Cookie каждого входящего HTTP запроса на наличие cookie с именем JSESSIONID и использовать его значение (идентификатор сеанса), чтобы получить связанный HttpSession из памяти сервера.

HttpSession остается активным до тех пор, пока оно не будет использовано больше времени ожидания, указанного в <session-timeout>, настройка в web.xml. Значение тайм-аута по умолчанию составляет 30 минут. Таким образом, когда клиент не посещает веб-приложение дольше указанного времени, контейнер сервлета перехватывает сеанс. Каждый последующий запрос, даже с указанным файлом cookie, больше не будет иметь доступа к тому же сеансу; Контейнер сервлета создаст новый сеанс.

На стороне клиента cookie сеанса остается активным до тех пор, пока работает экземпляр браузера. Таким образом, если клиент закрывает экземпляр браузера (все вкладки / окна), то сеанс удаляется на стороне клиента. В новом экземпляре браузера файл cookie, связанный с сеансом, не существует, поэтому он больше не будет отправляться. Это приводит к созданию совершенно нового сеанса HTTPSession с началом использования совершенно нового файла cookie сеанса.

1 голос
/ 14 апреля 2011

Считаете ли вы, что проблема возникает после перезапуска Tomcat? Tomcat будет сериализовать активные сеансы на диск во время успешного завершения, а затем десериализовать при запуске - я не уверен, приведет ли это к вызову вашего HttpSessionListener.sessionCreated (), поскольку сеанс не является строго созданным, просто десериализованным быть неверным (!), но может быть проверено довольно легко).

Вы также сравнивали свои результаты со статистикой сессий менеджеров Tomcat? Он отслеживает количество активных сессий и должен связываться с вашими цифрами, если нет, вы знаете, что ваш код неверен.

Также, вероятно, не связано с вашей проблемой, но есть ли веская причина, по которой вы используете Hashtable и WeakHashMap? Я склонен использовать ConcurrentHashMap, если мне нужна поточно-ориентированная реализация Map, ее производительность намного лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...