- PVSM.RU - https://www.pvsm.ru -
Собственно задача весьма специфичная, но может еще кому-то эта работа пригодится. Вопрос целесообразности совместного использования 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 выглядит так:
# подключаем модуль, предполагается, что вы его скопировали в ваш проект
# в папку, откуда выполняется этот код
require('redis-session-php/redis-session.php');
# переопределяем префикс, под которым будут храниться сессии в
# Redis. Это нужно сделать, т. к. в tomcat-redis-session-manager
# захардкодили, что сессии хранятся без префикса
define('REDIS_SESSION_PREFIX', '');
# в Tomcat мы имя куки поменять не можем, поэтому меняем в PHP
session_name('JSESSIONID');
# собственно начинаем сессию
RedisSession::start();
С 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/
Нажмите здесь для печати.