- PVSM.RU - https://www.pvsm.ru -

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

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

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

С 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 [4], поэтому, если она у вас не установлена, установите.
Компилируем класс в 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 [5].
Все. После этого можно создавать сессии самым обычным путем, все работает совершенно прозрачно. Если вы выполнили указанные шаги для 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();
  }
}

Как видите никаких специфичных действий не требуется, совершенно обычный код создания сессии.

Автор: cepreu4habr

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/redis/27065

Ссылки в тексте:

[1] проект: https://github.com/TheDeveloper/redis-session-php

[2] session-name: http://se.php.net/manual/en/function.session-name.php

[3] проект Tomcat Redis Session Manager: https://github.com/jcoleman/tomcat-redis-session-manager

[4] Gson: http://code.google.com/p/google-gson/

[5] Dropbox: http://dl.dropbox.com/u/48796965/jsonserializer.0.1.jar

[6] Источник: http://habrahabr.ru/post/169241/