CLOVER🍀

That was when it all began.

Payara Micro×HazelcastでHTTPセッションレプリケーション

Payara Microに同梱されたHazelcastを使って、HTTPセッションのレプリケーションができそうな感じだったので試してみました。

確認

用意したpom.xmlはこちら。
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>payara-micro-session-clustering</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <build>
        <finalName>payara-micro-session-clustering</finalName>
    </build>

    <dependencies>
        <dependency>
            <groupId>fish.payara.extras</groupId>
            <artifactId>payara-micro</artifactId>
            <version>4.1.152.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>
</project>

Payara MicroのJARファイルは、以下のページからダウンロードしてください。

http://www.payara.co.uk/downloads

今回利用するのは、「payara-micro-4.1.152.1.jar」です。

確認用のサンプルを用意。JAX-RSで行うことにします。

JAX-RS有効化のためのクラス。
src/main/java/org/littlewings/hazelcast/rest/JaxrsApplication.java

package org.littlewings.hazelcast.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("rest")
public class JaxrsApplication extends Application {
}

せっかくなので、自前でクラスを定義してHttpSessionに放り込むとしましょう。
src/main/java/org/littlewings/hazelcast/rest/User.java

package org.littlewings.hazelcast.rest;

import java.io.Serializable;

public class User implements Serializable {
    public String name;
    public int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

あんまり良くありませんが、簡単のため今回はJAX-RSリソースクラスでHttpSessionを直接扱います。
src/main/java/org/littlewings/hazelcast/rest/SessionResource.java

package org.littlewings.hazelcast.rest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Path("session")
public class SessionResource {
    @GET
    @Path("put/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public User put(@PathParam("name") String name, @QueryParam("age") int age, @Context HttpServletRequest request) {
        User user = new User(name, age);
        request.getSession().setAttribute(name, user);
        return user;
    }

    @GET
    @Path("update/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public User update(@PathParam("name") String name, @QueryParam("age") int age, @Context HttpServletRequest request) {
        User user = (User) request.getSession().getAttribute(name);
        user.age = age;
        return user;
    }

    @GET
    @Path("get/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public User get(@PathParam("name") String name, @Context HttpServletRequest request) {
        HttpSession session = request.getSession();
        return (User) session.getAttribute(name);
    }
}

そして、HttpSessionのレプリケーションを行うためには、web.xmlが必要です。
src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <distributable/>
</web-app>

distributable要素が必要ですよ、と。

では、パッケージングして

$ mvn package

起動。2つNodeを起動します。

## Node 1
$ java -jar payara-micro-4.1.152.1.jar --deploy target/payara-micro-session-clustering.war

## Node 2
$ java -jar payara-micro-4.1.152.1.jar --deploy target/payara-micro-session-clustering.war --port 8180

2つめのNodeは、リッスンポートを8180に設定。

起動時にクラスタが構成されることを確認しておきます。

Members [2] {
	Member [192.168.254.129]:5900
	Member [192.168.254.129]:5901 this
}

では、Cookieを保存しつつアクセスしてみます。
まずはデータ登録。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/payara-micro-session-clustering/rest/session/put/Taro?age=20
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONID=0126279d6eb5e563971fcfb08dba; Path=/payara-micro-session-clustering; HttpOnly
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:0; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:27:49 GMT
Content-Length: 24

{"name":"Taro","age":20}

同じNodeに対して、参照。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/payara-micro-session-clustering/rest/session/get/Taro
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:1; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:28:33 GMT
Content-Length: 24

{"name":"Taro","age":20}

Node 2に対して、参照。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8180/payara-micro-session-clustering/rest/session/get/Taro
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:2; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:29:03 GMT
Content-Length: 24

{"name":"Taro","age":20}

OKですね。

Node 2でデータ登録。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8180/payara-micro-session-clustering/rest/session/put/Hanako?age=22
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:3; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:30:17 GMT
Content-Length: 26

{"name":"Hanako","age":22}

Node 1で参照。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/payara-micro-session-clustering/rest/session/get/Hanako
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:4; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:30:43 GMT
Content-Length: 26

{"name":"Hanako","age":22}

あとは、ちょっと微妙な使い方ですが、セッションに保存済みのインスタンスのメンバーを更新して、HttpSession#setAttributeを再度呼び出さなかった場合は?

    @GET
    @Path("update/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public User update(@PathParam("name") String name, @QueryParam("age") int age, @Context HttpServletRequest request) {
        User user = (User) request.getSession().getAttribute(name);
        user.age = age;
        return user;
    }

Node 1に対して更新。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/payara-micro-session-clustering/rest/session/update/Taro?age=30
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:5; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:33:34 GMT
Content-Length: 24

{"name":"Taro","age":30}

Node 2で見てみます。

$ curl -c cookie.txt -b cookie.txt -i http://localhost:8180/payara-micro-session-clustering/rest/session/get/Taro
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Set-Cookie: JSESSIONIDVERSION=2f7061796172612d6d6963726f2d73657373696f6e2d636c7573746572696e67:6; Path=/payara-micro-session-clustering; HttpOnly
Content-Type: application/json
Date: Fri, 29 May 2015 14:34:00 GMT
Content-Length: 24

{"name":"Taro","age":30}

反映されています…。ちょっと意外でした。

オマケ

Hazelcastには、実はServletFilterで実装されたHttpSessionレプリケーションの機能があります。

Web Session Replication
http://docs.hazelcast.org/docs/3.4/manual/html-single/hazelcast-documentation.html#web-session-replication

で、Payaraではどうしているかですが、自前でHazelcastを使ったレプリケーションを実装しているみたいです。

https://github.com/payara/Payara/tree/payara-server-4.1.152/appserver/ha/ha-hazelcast-store/src/main/java/fish/payara/ha/hazelcast/store

HazelcastのWMモジュール自体、Payaraの依存関係に入っていないようです。

既存のHAの仕組みに乗せた感じでしょうか?それなら、その方が良いと思いますが。

個人的には、これでHazelcast on Payara Microの気になるところはだいたい初歩的な感じで触ったつもりです。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/payara-micro-session-clustering