Pull to refresh

Хранение сессий в Redis и их совместное использование в PHP и Tomcat

Reading time5 min
Views18K
Собственно задача весьма специфичная, но может еще кому-то эта работа пригодится. Вопрос целесообразности совместного использования Apache и Tomcat предлагаю оставить за рамками обсуждения. Задача была поставлена на работе уже как данность. Требуется обеспечить хранение сессий в Redis для PHP и Tomcat причем так, чтобы эти сессии были общими между ними. Продолжительное гугление результата не дало. И для PHP и для Tomcat существуют средства хранения сессий в Redis, но формат хранения у них разный. Все 3 найденные проекта для Tomcat сериализуют сессии в Redis с использованием бинарного формата для сериализации объектов Java. Также PHP и в Tomcat разные идентификаторы для Cookie сессии (PHPSESSID и JSESSID, соответственно).

Собственно эти проблемы мне и предстояло разрешить. Хотелось по возможности все сделать по уму: не придумывая собственного API для работы с сессиями; не изобретая собственной функции генерации уникального и случайного ID для сессии (в подобного рода вещах легко накосячить, сделав, в итоге, весь проект уязвимым); написав минимум кода, дублирующего уже придуманную другими функциональность.
C PHP все оказалось предельно просто. Есть замечательный проект, который сохраняя сессию, сериализует ее атрибуты в JSON. Кроме того, PHP дает возможность изменить имя куки, используемой на клиенте при помощи функции session-name. Так что с PHP оказалось практически ничего и не надо делать.
С Tomcat все сложнее. Среди найденных 3-х проектов, предоставляющих возможность хранения сессий в Redis, не оказалось ни одного, способного сохранить сессию во что-либо отличное от бинарного формата сериализации объектов в Java, который прочитать средствами PHP несколько затруднительно (а даже если и нет, то это точно является плохой практикой).
Впрочем проект Tomcat Redis Session Manager позволяет использовать собственные реализации сериализации объектов, что позволяет решить проблему с хранением сессии в Redis в формате JSON.
К коду

PHP

Итак код создания сессии в PHP выглядит так:
# подключаем модуль, предполагается, что вы его скопировали в ваш проект
# в папку, откуда выполняется этот код
require('redis-session-php/redis-session.php');
# переопределяем префикс, под которым будут храниться сессии в
# Redis. Это нужно сделать, т. к. в tomcat-redis-session-manager
# захардкодили, что сессии хранятся без префикса
define('REDIS_SESSION_PREFIX', '');
# в Tomcat мы имя куки поменять не можем, поэтому меняем в PHP
session_name('JSESSIONID');
# собственно начинаем сессию
RedisSession::start();

Скрипт в работе выглядит так:

В итоге в Redis попадет запись с ключом — id сессии и данными сессии, сериализованными в JSON.

С Java все несколько сложнее.
Java

Для начала следует установить Redis Session Manager. Процесс установки подробно описан на официальном сайте проекта. Прописываем в context.xml:
<Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" /> 
<Manager className="com.radiadesign.catalina.session.RedisSessionManager"  
serializationStrategyClass="com.radiadesign.catalina.session.JSONSerializer"/>

Здесь я не прописываю параметры, которые у меня совпадают с параметрами по умолчанию (например, сервер — localhost), кроме того добавлен параметр serializationStrategyClass, который задает путь к моей собственной реализации класса сериализации сессии (код будет ниже).
Скачанный tomcat-redis-session-manager-1.2-tomcat-6.jar кладем в папку lib вашего Tomcat.
Класс сериализации выглядит так:
package com.radiadesign.catalina.session;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Enumeration;
import java.util.Map;
import java.io.ByteArrayOutputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.util.Iterator;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;

public class JSONSerializer implements Serializer {
  private ClassLoader loader;

  @Override
  public void setClassLoader(ClassLoader loader) {
    this.loader = loader;
  }

  @Override
  public byte[] serializeFrom(HttpSession session) throws IOException {

    // create map to put data here
    HashMap<String,Object> sessionData = new HashMap<String,Object>();
    // put every attribute of session in newly created map
    for (Enumeration keys = session.getAttributeNames(); keys.hasMoreElements();){
        String k = (String) keys.nextElement();
        sessionData.put(k, session.getAttribute(k));
    }
    // write it to byte array
    Gson gson = new Gson();
    return gson.toJson(sessionData).getBytes();
  }

  @Override
  public HttpSession deserializeInto(byte[] data, HttpSession session) throws IOException, ClassNotFoundException {

    RedisSession redisSession = (RedisSession) session;
    redisSession.setValid(true);
    // place data to map
    Gson gson = new Gson();
    Type mapType = new TypeToken<HashMap<String,Object>>(){}.getType();
    HashMap<String,Object> sessionData = gson.fromJson(new String(data), mapType);
    // place attributes to session object
    for (Map.Entry<String, Object> entry : sessionData.entrySet())
        redisSession.setAttribute(entry.getKey(), entry.getValue());
    // return filled session*/
    return redisSession;
  }
}

Здесь для сериализации в JSON используется библиотека Gson, поэтому, если она у вас не установлена, установите.
Компилируем класс в Jar и кладем в папку lib вашей копии Tomcat. Пример моей команды компиляции:
javac -cp /usr/share/tomcat6/lib/servlet-api.jar:/usr/share/tomcat6/lib/catalina.jar:/usr/share/tomcat6/lib/tomcat-redis-session-manager-1.2-tomcat-6.jar:/usr/share/tomcat6/lib/gson-2.2.2.jar com/radiadesign/catalina/session/JSONSerializer.java
jar cf jsonserializer.0.1.jar com

Конечно на вашей машине пути к библиотекам могут отличаться. Кому лень компилировать самостоятельно, возьмите готовый Jar из моего Dropbox.
Все. После этого можно создавать сессии самым обычным путем, все работает совершенно прозрачно. Если вы выполнили указанные шаги для Tomcat и создаете сессии в PHP с использованием приведенного ранее кода, то у вас общие сессии между Tomcat и PHP.
На всякий случай привожу пример создания сессии в Tomcat:
import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;

public class HelloServlet extends HttpServlet {
  public void doGet (HttpServletRequest req,
                                         HttpServletResponse res)
        throws ServletException, IOException
  {
        PrintWriter out = res.getWriter();
        HttpSession session = req.getSession();
        out.println("ID: " + session.getId());
        session.setAttribute("Name", "kapitoka");
        out.println("Set done");
        out.println("Get result: " + session.getAttribute("Name"));
        out.close();
  }
}

Как видите никаких специфичных действий не требуется, совершенно обычный код создания сессии.
Результат примера на Java:
Tags:
Hubs:
+9
Comments9

Articles