CLOVER🍀

That was when it all began.

WildFlyのInfinispanサブシステムでProtoStreamが使われているようになっていたという話

これは、なにをしたくて書いたもの?

前に、WildFlyのHTTPセッションの保存先をInfinispan Serverにするエントリーを書きました。

WildFlyのHTTPセッションの保存先をInfinispan Serverに変更する - CLOVER🍀

この時、Marshallerについては割と軽く流してしまったのですが、よくよく見ると思っていたのとけっこう違ったので、ちゃんと
見ていくことにしました。

WildFlyのInfinispanサブシステムのMarshaller定義

とりあえず、現在のWildFlyの最新バージョン(26.1.1.Final)のInfinispanサブシステムの定義を見てみましょう。

$ curl -OL https://github.com/wildfly/wildfly/releases/download/26.1.1.Final/wildfly-26.1.1.Final.zip
$ unzip wildfly-26.1.1.Final.zip
$ cd wildfly-26.1.1.Final

standalone/configuration/standalone.xmlの該当箇所を見てみます。

        <subsystem xmlns="urn:jboss:domain:infinispan:13.0">
            <cache-container name="ejb" default-cache="passivation" marshaller="PROTOSTREAM" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
                <local-cache name="passivation">
                    <expiration interval="0"/>
                    <file-store passivation="true" purge="false"/>
                </local-cache>
            </cache-container>
            <cache-container name="web" default-cache="passivation" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.web.infinispan">
                <local-cache name="passivation">
                    <expiration interval="0"/>
                    <file-store passivation="true" purge="false"/>
                </local-cache>
                <local-cache name="sso">
                    <expiration interval="0"/>
                </local-cache>
            </cache-container>
            <cache-container name="server" default-cache="default" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.server">
                <local-cache name="default">
                    <expiration interval="0"/>
                </local-cache>
            </cache-container>
            <cache-container name="hibernate" marshaller="JBOSS" modules="org.infinispan.hibernate-cache">
                <local-cache name="entity">
                    <heap-memory size="10000"/>
                    <expiration max-idle="100000"/>
                </local-cache>
                <local-cache name="local-query">
                    <heap-memory size="10000"/>
                    <expiration max-idle="100000"/>
                </local-cache>
                <local-cache name="timestamps">
                    <expiration interval="0"/>
                </local-cache>
                <local-cache name="pending-puts">
                    <expiration max-idle="60000"/>
                </local-cache>
            </cache-container>
        </subsystem>

standalone/configuration/standalone-ha.xmlの場合。

        <subsystem xmlns="urn:jboss:domain:infinispan:13.0">
            <cache-container name="ejb" default-cache="dist" marshaller="PROTOSTREAM" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
                <transport lock-timeout="60000"/>
                <distributed-cache name="dist">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <expiration interval="0"/>
                    <file-store/>
                </distributed-cache>
            </cache-container>
            <cache-container name="server" default-cache="default" marshaller="PROTOSTREAM" aliases="singleton cluster" modules="org.wildfly.clustering.server">
                <transport lock-timeout="60000"/>
                <replicated-cache name="default">
                    <transaction mode="BATCH"/>
                    <expiration interval="0"/>
                </replicated-cache>
            </cache-container>
            <cache-container name="web" default-cache="dist" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.web.infinispan">
                <transport lock-timeout="60000"/>
                <replicated-cache name="sso">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <expiration interval="0"/>
                </replicated-cache>
                <replicated-cache name="routing">
                    <expiration interval="0"/>
                </replicated-cache>
                <distributed-cache name="dist">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <expiration interval="0"/>
                    <file-store/>
                </distributed-cache>
            </cache-container>
            <cache-container name="hibernate" marshaller="JBOSS" modules="org.infinispan.hibernate-cache">
                <transport lock-timeout="60000"/>
                <local-cache name="local-query">
                    <heap-memory size="10000"/>
                    <expiration max-idle="100000"/>
                </local-cache>
                <local-cache name="pending-puts">
                    <expiration max-idle="60000"/>
                </local-cache>
                <invalidation-cache name="entity">
                    <heap-memory size="10000"/>
                    <expiration max-idle="100000"/>
                </invalidation-cache>
                <replicated-cache name="timestamps">
                    <expiration interval="0"/>
                </replicated-cache>
            </cache-container>
        </subsystem>

standalone/configuration/standalone-microprofile.xmlなどもありますが、これらから数が減ったりするだけなので、割愛。

Marshallerの定義は、marshaller属性の部分ですね。

            <cache-container name="web" default-cache="dist" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.web.infinispan">

Cache自体はLocal CacheなのかDistributed CacheなのかReplicated Cacheなのか、Cacheの定義に差はありますが、各Cache Containerの
Marshallerの定義にフォーカスして見るとこんな感じですね。

  • ejb … PROTOSTREAM
  • web … PROTOSTREAM
  • server … PROTOSTREAM
  • hibernate … JBOSS

WildFly Model Referenceのcache-containerおよびremote-cache-containerの定義を見ると、marshaller属性にはLEGACY、
JBOSS、PROTOSTREAMのいずれかを指定できます。なにも指定していない場合はLEGACYを選択したことになります。

それぞれの値の説明は、WildFlyのドキュメントのどこにも書かれていないのですが…。

いつからProtoStreamが入ったんでしょうね?少なくとも、WildFly 24.0.0.Finalにはこの設定はあったようです。

ProtoStreamが入ったタイミング自体は、どうやらWildFly 21.0.0.Finalのようです。

[WFLY-13426] Optimize marshalling in clustering subsystems using ProtoStream - Red Hat Issue Tracker

Marshallerの設定が可能になったのが、もう少し後、という感じですね。

InfinispanのMarshaller

ここで挙がっているMarshallerというのは、Infinispanにデータを格納する際にJavaのオブジェクトをどのようにマーシャリングするのか
という話になります。

というわけで、Infinispanのドキュメントを見てみます。

Encoding Infinispan caches and marshalling data / Configuring cache encoding / Marshalled Java objects

InfinispanでJavaのオブジェクトをマーシャリングする際には、以下の3つから選ぶことができます。

Infinispanの標準のMarshallerは、ProtoStreamです。ProtoStreamのマーシャリングの方法としては、Protocol Buffersになります。

また、この中でもJBoss Marshallingは非推奨扱いになっています。

JBoss Marshalling is deprecated. You should use it only as a temporary measure while migrating your applications from an older version of Infinispan.

Encoding Infinispan caches and marshalling data / Using alternative and custom marshaller implementations / Using JBoss Marshalling

となると、LEGACYはJava Serializationのことなのかな?と短絡的に思ったりもしたのですが、そうではないようです。

WildFlyでのInfinispanサブシステムのMarshallerの実体

話をWildFlyのInfinispanサブシステムに戻して。

Embedded、Hot RodそれぞれのMarshallerの定義(LEGACY、JBOSS、PROTOSTREAM)に応じてどのようなMarshallerが
選択されるのかを見てみます。

Embedded。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/spi/src/main/java/org/wildfly/clustering/infinispan/spi/marshalling/InfinispanMarshallerFactory.java

Hot Rod。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/client/src/main/java/org/wildfly/clustering/infinispan/client/marshaller/HotRodMarshallerFactory.java

両者で微妙に定義が異なりますが、PROTOSTREAMはProtoStream、JBOSSはJBoss Marshalling、そしてLEGACYは
条件によってPROTOSTREAMかJBOSSを選択する、ということになっています。

そもそも、Java標準のシリアライズは使われないんですね。

ここで使われているMarshallerFactory。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/marshalling/src/main/java/org/wildfly/clustering/infinispan/marshalling/MarshallerFactory.java

そして、各Marshaller。

WildFlyでProtoStreamを使う?

ProtoStreamといえば、Protocol Buffersのスキーマ定義とMarshallerを書く、もしくはアノテーションからスキーマ定義を自動生成して使う
ことになるわけですが。なので、前のエントリーを書いた時に、PROTOSTREAMを選ぶとスキーマ定義やMarshallerを書かないと
いけなくなると思ってしまいました。

ですが、デフォルトの設定ファイルがPROTOSTREAMとなっているということは、特に意識しない場合はProtoStreamでマーシャリングを
行っていることになります。どうなっているんでしょう?

こちらに、WildFlyの持つProtoStreamのモジュールがありそうです。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/protostream

このモジュール内を見ると、その量にけっこう驚いたりするのですが。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream

Javaの標準ライブラリの一部をカバーするMarshallerが書かれています。以下は、その一部です。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/util

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/time

以下が含まれていそうですね。

  • java.lang
  • java.math
  • java.net
  • java.sql
  • java.util
  • java.util.concurrent
  • java.util.concurrent.atomic
  • java.time

Collectionに対するMarshaller。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/util/CollectionMarshaller.java

public class CollectionMarshaller<T extends Collection<Object>> extends AbstractCollectionMarshaller<T> {

各パッケージ内にMarshallerProviderというenumがあり、こちらでMarshallerをまとめて取り扱っているようです。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/util/UtilMarshallerProvider.java

public enum UtilMarshallerProvider implements ProtoStreamMarshallerProvider {
    ARRAY_DEQUE(new CollectionMarshaller<>(ArrayDeque::new)),
    ARRAY_LIST(new CollectionMarshaller<>(ArrayList::new)),
    BIT_SET(new FunctionalScalarMarshaller<>(Scalar.BYTE_ARRAY.cast(byte[].class), BitSet::new, BitSet::isEmpty, BitSet::toByteArray, BitSet::valueOf)),
    CALENDAR(new CalendarMarshaller()),
    CURRENCY(new FunctionalScalarMarshaller<>(Currency.class, Scalar.STRING.cast(String.class), Functions.constantSupplier(getDefaultCurrency()), Currency::getCurrencyCode, Currency::getInstance)),
    DATE(new FunctionalMarshaller<>(Date.class, Instant.class, Date::toInstant, Date::from)),
    
    〜省略〜

    SYNCHRONIZED_SET(new SynchronizedDecoratorMarshaller<>(Set.class, Collections::synchronizedSet, Collections.emptySet())),
    SYNCHRONIZED_SORTED_MAP(new SynchronizedDecoratorMarshaller<>(SortedMap.class, Collections::synchronizedSortedMap, Collections.emptySortedMap())),
    SYNCHRONIZED_SORTED_SET(new SynchronizedDecoratorMarshaller<>(SortedSet.class, Collections::synchronizedSortedSet, Collections.emptySortedSet())),
    TIME_ZONE(new FunctionalScalarMarshaller<>(TimeZone.class, Scalar.STRING.cast(String.class), Functions.constantSupplier(TimeZone.getDefault()), TimeZone::getID, TimeZone::getTimeZone)),
    TREE_MAP(new SortedMapMarshaller<>(TreeMap::new)),
    TREE_SET(new SortedSetMarshaller<>(TreeSet::new)),
    UNMODIFIABLE_COLLECTION(new DecoratorMarshaller<>(Collection.class, Collections::unmodifiableCollection, Collections.emptyList())),
    UNMODIFIABLE_LIST(new DecoratorMarshaller<>(List.class, Collections::unmodifiableList, new LinkedList<>())),
    UNMODIFIABLE_MAP(new DecoratorMarshaller<>(Map.class, Collections::unmodifiableMap, Collections.emptyMap())),
    UNMODIFIABLE_NAVIGABLE_MAP(new DecoratorMarshaller<>(NavigableMap.class, Collections::unmodifiableNavigableMap, Collections.emptyNavigableMap())),
    UNMODIFIABLE_NAVIGABLE_SET(new DecoratorMarshaller<>(NavigableSet.class, Collections::unmodifiableNavigableSet, Collections.emptyNavigableSet())),
    UNMODIFIABLE_RANDOM_ACCESS_LIST(new DecoratorMarshaller<>(List.class, Collections::unmodifiableList, Collections.emptyList())),
    UNMODIFIABLE_SET(new DecoratorMarshaller<>(Set.class, Collections::unmodifiableSet, Collections.emptySet())),
    UNMODIFIABLE_SORTED_MAP(new DecoratorMarshaller<>(SortedMap.class, Collections::unmodifiableSortedMap, Collections.emptySortedMap())),
    UNMODIFIABLE_SORTED_SET(new DecoratorMarshaller<>(SortedSet.class, Collections::unmodifiableSortedSet, Collections.emptySortedSet())),
    UUID(new ProtoStreamBuilderFieldSetMarshaller<>(UUIDMarshaller.INSTANCE)),
    ;

ProtoStreamByteBufferMarshaller#isMarshallableを見たりすると、クラスに対応するMarshallerを親クラスをたどって探していたりするので、
その中ですでに用意されているMarshallerを利用するような感じなのかなと思ったり。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/ProtoStreamByteBufferMarshaller.java#L61-L82

    @Override
    public boolean isMarshallable(Object object) {
        if ((object == null) || (object instanceof Class)) return true;
        Class<?> targetClass = object.getClass();
        if (AnyField.fromJavaType(targetClass) != null) return true;
        if (targetClass.isArray()) {
            for (int i = 0; i < Array.getLength(object); ++i) {
                if (!this.isMarshallable(Array.get(object, i))) return false;
            }
            return true;
        }
        if (Proxy.isProxyClass(targetClass)) {
            return this.isMarshallable(Proxy.getInvocationHandler(object));
        }
        while (targetClass != null) {
            if (this.context.canMarshall(targetClass)) {
                return true;
            }
            targetClass = targetClass.getSuperclass();
        }
        return false;
    }

で、このモジュール内のソースコードを見ていると、.protoファイルを扱っている箇所がありました。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/AbstractSerializationContextInitializer.java#L49-L52

    protected AbstractSerializationContextInitializer(String resourceName, ClassLoader loader) {
        this.resourceName = (resourceName == null) ? this.getClass().getPackage().getName() + ".proto" : resourceName;
        this.loader = (loader == null) ? WildFlySecurityManager.getClassLoaderPrivileged(this.getClass()) : loader;
    }

ということは、.protoファイルが含まれていそうですね。ちょっと探してみましょう。

$ find modules/system/layers/base/org/wildfly/clustering/ -name '*.jar' | xargs -I {} unzip -l {} | grep proto$
      843  2022-05-18 11:57   org.wildfly.clustering.ee.cache.function.proto
      371  2022-05-18 12:00   org.wildfly.clustering.ee.infinispan.scheduler.proto
      139  2022-05-18 11:56   org.wildfly.clustering.marshalling.spi.proto
     1049  2022-05-18 11:56   org.wildfly.clustering.marshalling.protostream.proto
      459  2022-05-18 11:55   java.net.proto
      658  2022-05-18 11:55   java.sql.proto
      362  2022-05-18 11:55   java.util.concurrent.atomic.proto
     3198  2022-05-18 11:55   java.time.proto
      665  2022-05-18 11:55   java.lang.proto
      495  2022-05-18 11:55   java.math.proto
     1067  2022-05-18 11:55   java.util.concurrent.proto
     4706  2022-05-18 11:55   java.util.proto
      270  2022-05-18 12:02   io.undertow.security.api.proto
      242  2022-05-18 12:02   org.wildfly.clustering.web.undertow.sso.elytron.proto
      158  2022-05-18 12:01   org.wildfly.clustering.web.cache.sso.proto
      152  2022-05-18 12:01   org.wildfly.clustering.web.cache.sso.coarse.proto
      420  2022-05-18 12:01   org.wildfly.clustering.web.cache.session.proto
      716  2022-05-18 12:01   org.wildfly.clustering.web.cache.session.fine.proto
      319  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.session.fine.proto
      149  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.sso.proto
      163  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.session.coarse.proto
      158  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.routing.proto
      211  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.sso.coarse.proto
      533  2022-05-18 12:02   org.wildfly.clustering.web.infinispan.session.proto
      207  2022-05-18 12:01   org.wildfly.clustering.web.hotrod.sso.coarse.proto
      315  2022-05-18 12:01   org.wildfly.clustering.web.hotrod.session.fine.proto
      244  2022-05-18 12:01   org.wildfly.clustering.web.hotrod.session.proto
      159  2022-05-18 12:01   org.wildfly.clustering.web.hotrod.session.coarse.proto
      145  2022-05-18 12:01   org.wildfly.clustering.web.hotrod.sso.proto
      133  2022-05-18 11:57   org.infinispan.commons.io.proto
      745  2022-05-18 11:57   org.infinispan.metadata.proto
      254  2022-05-18 12:00   org.wildfly.clustering.ejb.infinispan.proto
      401  2022-05-18 12:00   org.wildfly.clustering.ejb.infinispan.bean.proto
      266  2022-05-18 12:00   org.jboss.as.network.proto
      365  2022-05-18 12:00   org.wildfly.clustering.ejb.infinispan.group.proto
      125  2022-05-18 12:00   org.jboss.ejb.client.proto
      445  2022-05-18 12:01   org.wildfly.clustering.server.singleton.proto
      125  2022-05-18 12:01   org.wildfly.clustering.server.dispatcher.proto
      714  2022-05-18 12:01   org.jboss.msc.service.proto
      110  2022-05-18 12:01   org.infinispan.remoting.transport.proto
      353  2022-05-18 12:01   org.infinispan.remoting.transport.jgroups.proto
      170  2022-05-18 12:01   org.jgroups.util.proto
      135  2022-05-18 12:01   org.wildfly.clustering.server.registry.proto
      313  2022-05-18 12:01   org.jgroups.stack.proto
      480  2022-05-18 12:01   org.wildfly.clustering.server.group.proto
     1070  2022-05-18 12:01   org.wildfly.clustering.server.provider.proto

…たくさんありました。

java.〜パッケージの分は、こちらに置かれていましたね。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/api/src/main/resources

例として、java.util.protoファイルの中身を見てみましょう。java.utilパッケージ内のクラスに対する定義がずらっと並んでいます。

$ unzip -p modules/system/layers/base/org/wildfly/clustering/marshalling/api/main/wildfly-clustering-marshalling-api-26.1.1.Final.jar java.util.proto
package java.util;

import "java.lang.proto";

// IDs: 10 - 59

// Empty collections

/**
 * @TypeId(10)
 */
message EmptyList {
}

/**
 * @TypeId(11)
 */
message EmptyMap {
}

/**
 * @TypeId(12)
 */
message EmptyNavigableMap {
}

/**
 * @TypeId(13)
 */
message EmptyNavigableSet {
}

/**
 * @TypeId(14)
 */
message EmptySet {
}

// Singleton collections

/**
 * @TypeId(15)
 */
message SingletonList {
        optional        bytes   value   = 1;
}

/**
 * @TypeId(16)
 */
message SingletonMap {
        optional        bytes   key     = 1;
        optional        bytes   value   = 2;
}

/**
 * @TypeId(17)
 */
message SingletonSet {
        optional        bytes   value   = 1;
}

// Synchronized collections

/**
 * @TypeId(20)
 */
message SynchronizedCollection {
        optional        bytes   value    = 1;
}

/**
 * @TypeId(21)
 */
message SynchronizedList {
        optional        bytes   value    = 1;
}


〜省略〜


message UnmodifiableSortedMap {
        optional        bytes   collection       = 1;
}

message UnmodifiableSortedSet {
        optional        bytes   collection       = 1;
}

// Collections

/**
 * @TypeId(30)
 */
message ArrayDeque {
        repeated        bytes   element = 1;
}

/**
 * @TypeId(31)
 */
message ArrayList {
        repeated        bytes   element = 1;
}

/**
 * @TypeId(32)
 */
message BitSet {
        optional        bytes   value   = 1;
}

/**
 * @TypeId(33)
 */
message Calendar {
        optional        string  type    = 1;
        optional        Date    time    = 2;
        optional        bool    lenient = 3;
        optional        string  zone    = 4;
        optional        uint32  firstDayOfWeek  = 5;
        optional        uint32  minDaysInFirstWeek      = 6;
}

/**
 * @TypeId(34)
 */
message Currency {
        optional        string  value   = 1;
}

/**
 * @TypeId(35)
 */
message Date {
        optional        uint64  postEpochSeconds        = 1;
        optional        uint64  preEpochSeconds = 2;
        optional        uint32  millisOfSecond  = 3;
        optional        uint32  nanosOfSecond   = 4;
}

/**
 * @TypeId(36)
 */
message EnumMap {
        optional        java.lang.Class enumClass       = 1;
        optional        java.lang.Class complementClass = 2;
        optional        bytes   bits    = 3;
        repeated        uint32  element = 4;
        repeated        bytes   value   = 5;
}

/**
 * @TypeId(37)
 */
message EnumSet {
        optional        java.lang.Class enumClass       = 1;
        optional        java.lang.Class complementClass = 2;
        optional        bytes   bits    = 3;
        repeated        uint32  element = 4;
}

/**
 * @TypeId(38)
 */
message HashMap {
        repeated        bytes   key     = 1;
        repeated        bytes   value   = 2;
}

/**
 * @TypeId(39)
 */
message HashSet {
        repeated        bytes   element = 1;
}

/**
 * @TypeId(40)
 */
message LinkedHashMap {
        repeated        bytes   key     = 1;
        repeated        bytes   value   = 2;
        optional        bool    accessOrder     = 3;
}


〜省略〜


/**
 * @TypeId(49)
 */
message SimpleImmutableEntry {
        optional        bytes   key     = 1;
        optional        bytes   value   = 2;
}

/**
 * @TypeId(50)
 */
message TimeZone {
        optional        string  value   = 1;
}

/**
 * @TypeId(51)
 */
message TreeMap {
        repeated        bytes   key     = 1;
        repeated        bytes   value   = 2;
        optional        bool    reverse = 3;
        optional        bytes   comparator      = 4;
}

/**
 * @TypeId(52)
 */
message TreeSet {
        repeated        bytes   element = 1;
        optional        bool    reverse = 2;
        optional        bytes   comparator      = 3;
}

/**
 * @TypeId(53)
 */
message UUID {
        optional        sfixed64        high    = 1;
        optional        sfixed64        low     = 2;
}

WilfFlyのモジュール内にも.protoファイルがありました。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/web/infinispan/src/main/resources

中身を見てみましょう。

$ unzip -p modules/system/layers/base/org/wildfly/clustering/web/infinispan/main/wildfly-clustering-web-infinispan-26.1.1.Final.jar org.wildfly.clustering.web.infinispan.session.proto
package org.wildfly.clustering.web.infinispan.session;

import "java.time.proto";

// IDs: 200 - 204

/**
 * @TypeId(200)
 */
message SessionCreationMetaDataKey {
        required        bytes   id      = 1;
}

/**
 * @TypeId(201)
 */
message SessionAccessMetaDataKey {
        required        bytes   id      = 1;
}

/**
 * @TypeId(202)
 */
enum SessionCreationMetaDataKeyFilter {
        INSTANCE        = 0;
}

/**
 * @TypeId(203)
 */
message SimpleSessionExpirationMetaData {
        optional        java.time.Duration      maxInactiveInterval      = 1;
        optional        java.time.Instant       lastAccessEndTime        = 2;
}

Marshallerも含まれていますね。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/web/infinispan/src/main/java/org/wildfly/clustering/web/infinispan/session/SimpleSessionExpirationMetaDataMarshaller.java

public class SimpleSessionExpirationMetaDataMarshaller implements ProtoStreamMarshaller<SimpleSessionExpirationMetaData> {

となると、これらのMarshallerをどうやって検出しているのかという疑問も出てきます。

ちょっと探していると、このあたりでService Loaderの仕組みでSerializationContextInitializerをロードしているようです。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/SerializationContextBuilder.java#L117-L134

    private boolean tryLoad(ClassLoader loader) {
        PrivilegedAction<Iterator<SerializationContextInitializer>> action = new PrivilegedAction<Iterator<SerializationContextInitializer>>() {
            @Override
            public Iterator<SerializationContextInitializer> run() {
                return ServiceLoader.load(SerializationContextInitializer.class, loader).iterator();
            }
        };
        Iterator<SerializationContextInitializer> initializers = WildFlySecurityManager.doUnchecked(action);
        boolean init = initializers.hasNext();
        while (initializers.hasNext()) {
            SerializationContextInitializer initializer = initializers.next();
            // Do not load initializers from protostream-types
            if (!initializer.getClass().getName().startsWith(PROTOSTREAM_BASE_PACKAGE_NAME)) {
                this.init(initializer);
            }
        }
        return init;
    }

探してみましょう。

$ find modules/system/layers/base/org/wildfly/clustering/ -name '*.jar' | xargs -I {} unzip -l {} | grep SerializationContextInitializer
       81  2022-05-18 11:57   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     5107  2022-05-18 11:57   org/wildfly/clustering/ee/cache/function/FunctionSerializationContextInitializer.class
       88  2022-05-18 12:00   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     2672  2022-05-18 12:00   org/wildfly/clustering/ee/infinispan/scheduler/SchedulerSerializationContextInitializer.class
     2592  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/CompositeSerializationContextInitializer.class
     1173  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/SerializationContextInitializerProvider.class
     1524  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/LangSerializationContextInitializer.class
     2236  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/ProviderSerializationContextInitializer.class
     2508  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/AbstractSerializationContextInitializer.class
      995  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/AnySerializationContextInitializer.class
     3619  2022-05-18 11:56   org/wildfly/clustering/marshalling/protostream/DefaultSerializationContextInitializerProvider.class
      960  2022-05-18 12:02   org/wildfly/clustering/web/undertow/sso/elytron/ElytronSSOSerializationContextInitializer.class
      178  2022-05-18 12:02   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     1024  2022-05-18 12:02   org/wildfly/clustering/web/undertow/sso/UndertowSecuritySerializationContextInitializer.class
     1037  2022-05-18 12:01   org/wildfly/clustering/web/cache/session/SessionSerializationContextInitializer.class
      336  2022-05-18 12:01   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     2131  2022-05-18 12:01   org/wildfly/clustering/web/cache/sso/SSOSerializationContextInitializer.class
     4352  2022-05-18 12:01   org/wildfly/clustering/web/cache/session/fine/FineSessionAttributesSerializationContextInitializer.class
     2159  2022-05-18 12:01   org/wildfly/clustering/web/cache/sso/coarse/CoarseSSOSerializationContextInitializer.class
     1825  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/session/coarse/CoarseSessionAttributeSerializationContextInitializer.class
      558  2022-05-18 12:02   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     1964  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/sso/coarse/CoarseSSOSerializationContextInitializer.class
     2304  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/session/SessionSerializationContextInitializer.class
     1718  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/sso/SSOSerializationContextInitializer.class
     2444  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/routing/InfinispanRoutingSerializationContextInitializer.class
     1930  2022-05-18 12:02   org/wildfly/clustering/web/infinispan/session/fine/FineSessionAttributesSerializationContextInitializer.class
     1962  2022-05-18 12:01   org/wildfly/clustering/web/hotrod/session/SessionSerializationContextInitializer.class
      443  2022-05-18 12:01   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     1702  2022-05-18 12:01   org/wildfly/clustering/web/hotrod/sso/SSOSerializationContextInitializer.class
     1944  2022-05-18 12:01   org/wildfly/clustering/web/hotrod/sso/coarse/CoarseSSOSerializationContextInitializer.class
     1809  2022-05-18 12:01   org/wildfly/clustering/web/hotrod/session/coarse/CoarseSessionAttributeSerializationContextInitializer.class
     1910  2022-05-18 12:01   org/wildfly/clustering/web/hotrod/session/fine/FineSessionAttributesSerializationContextInitializer.class
     1117  2022-05-18 11:57   org/wildfly/clustering/infinispan/marshalling/protostream/IOSerializationContextInitializer.class
     1521  2022-05-18 11:57   org/wildfly/clustering/infinispan/spi/metadata/MetadataSerializationContextInitializer.class
     2747  2022-05-18 12:00   org/wildfly/clustering/ejb/infinispan/EJBSerializationContextInitializerProvider.class
      945  2022-05-18 12:00   org/wildfly/clustering/ejb/infinispan/InfinispanEJBSerializationContextInitializer.class
      589  2022-05-18 12:00   org/wildfly/clustering/ejb/infinispan/EJBSerializationContextInitializer.class
       73  2022-05-18 12:00   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     2175  2022-05-18 12:00   org/wildfly/clustering/ejb/infinispan/bean/BeanSerializationContextInitializer.class
     2745  2022-05-18 12:00   org/wildfly/clustering/ejb/infinispan/group/BeanGroupSerializationContextInitializer.class
       75  2022-05-18 12:00   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     2194  2022-05-18 12:00   org/wildfly/clustering/ejb/client/EJBClientSerializationContextInitializer.class
     1404  2022-05-18 12:01   org/wildfly/clustering/server/singleton/SingletonSerializationContextInitializer.class
     1012  2022-05-18 12:01   org/wildfly/clustering/server/registry/RegistrySerializationContextInitializer.class
     2315  2022-05-18 12:01   org/wildfly/clustering/server/group/GroupSerializationContextInitializer.class
       68  2022-05-18 12:01   META-INF/services/org.infinispan.protostream.SerializationContextInitializer
     1039  2022-05-18 12:01   org/wildfly/clustering/server/dispatcher/CommandDispatcherSerializationContextInitializer.class
     3677  2022-05-18 12:01   org/wildfly/clustering/server/ServerSerializationContextInitializerProvider.class
     4092  2022-05-18 12:01   org/wildfly/clustering/server/provider/ServiceProviderRegistrySerializationContextInitializer.class
      577  2022-05-18 12:01   org/wildfly/clustering/server/ServerSerializationContextInitializer.class

…たくさんありました。

META-INF/services/org.infinispan.protostream.SerializationContextInitializerをピックアップして見てみます。

$ unzip -p modules/system/layers/base/org/wildfly/clustering/web/cache/main/wildfly-clustering-web-cache-26.1.1.Final.jar META-INF/services/org.infinispan.protostream.SerializationContextInitializer
org.wildfly.clustering.web.cache.session.SessionSerializationContextInitializer
org.wildfly.clustering.web.cache.session.fine.FineSessionAttributesSerializationContextInitializer
org.wildfly.clustering.web.cache.sso.SSOSerializationContextInitializer
org.wildfly.clustering.web.cache.sso.coarse.CoarseSSOSerializationContextInitializer

どうやら、ここがMarshallerの検出に関係ありそうですね。

いくつか見てみましょう。

こちらに、SerializationContextにMarshallerを登録している箇所があります。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/web/cache/src/main/java/org/wildfly/clustering/web/cache/session/SessionSerializationContextInitializer.java

@MetaInfServices(SerializationContextInitializer.class)
public class SessionSerializationContextInitializer extends AbstractSerializationContextInitializer {

    @Override
    public void registerMarshallers(SerializationContext context) {
        context.registerMarshaller(new SessionCreationMetaDataEntryMarshaller());
        context.registerMarshaller(new SessionAccessMetaDataMarshaller());
    }
}

@MetaInfServicesアノテーションが付与されているので、META-INF/services/org.infinispan.protostream.SerializationContextInitializerファイルを
作成する仕組みはこちらを使っていそうですね。

META-INF/services generator -

Infinispanも内部で使っていたライブラリです。

他のパターンも見てみましょう。

$ unzip -p modules/system/layers/base/org/wildfly/clustering/server/main/wildfly-clustering-server-26.1.1.Final.jar META-INF/services/org.infinispan.protostream.SerializationContextInitializer
org.wildfly.clustering.server.ServerSerializationContextInitializer

Service LoaderによりSerializationContextInitializerをロードして、

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/server/src/main/java/org/wildfly/clustering/server/ServerSerializationContextInitializer.java

@MetaInfServices(SerializationContextInitializer.class)
public class ServerSerializationContextInitializer extends CompositeSerializationContextInitializer {

    public ServerSerializationContextInitializer() {
        super(ServerSerializationContextInitializerProvider.class);
    }
}

実際のMarshallerを把握しているのはSerializationContextInitializerProvider、というパターンもあるようです。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/server/src/main/java/org/wildfly/clustering/server/ServerSerializationContextInitializerProvider.java

public enum ServerSerializationContextInitializerProvider implements SerializationContextInitializerProvider {
    COMMAND_DISPATCHER(new CommandDispatcherSerializationContextInitializer()),
    JGROUPS_UTIL(new ProviderSerializationContextInitializer<>("org.jgroups.util.proto", JGroupsUtilMarshallerProvider.class)),
    JGROUPS_STACK(new ProviderSerializationContextInitializer<>("org.jgroups.stack.proto", JGroupsStackMarshallerProvider.class)),
    INFINISPAN_TRANSPORT(new ProviderSerializationContextInitializer<>("org.infinispan.remoting.transport.proto", InfinispanTransportMarshallerProvider.class)),
    INFINISPAN_JGROUPS_TRANSPORT(new ProviderSerializationContextInitializer<>("org.infinispan.remoting.transport.jgroups.proto", InfinispanJGroupsTransportMarshallerProvider.class)),
    GROUP(new GroupSerializationContextInitializer()),
    PROVIDER(new ServiceProviderRegistrySerializationContextInitializer()),
    REGISTRY(new RegistrySerializationContextInitializer()),
    SERVICE(new ProviderSerializationContextInitializer<>("org.jboss.msc.service.proto", ServiceMarshallerProvider.class)),
    SINGLETON(new SingletonSerializationContextInitializer()),
    ;

ちなみに、META-INF/services/org.infinispan.protostream.SerializationContextInitializerファイルはjava.〜パッケージに対応する
.protoファイルを扱っているwildfly-clustering-marshalling-apiのJARファイル内にはありませんでした。

java.〜の基本的なパッケージに対応するMarshallerは、以下で共通的に登録されているようです。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/marshalling/protostream/src/main/java/org/wildfly/clustering/marshalling/protostream/SerializationContextBuilder.java#L52-L53

    public SerializationContextBuilder(ClassLoaderMarshaller marshaller) {
        // Load default schemas first, so they can be referenced by loader-specific schemas
        this.register(Collections.singleton(new LangSerializationContextInitializer(marshaller)));
        this.register(EnumSet.allOf(DefaultSerializationContextInitializerProvider.class));
    }

概ね、把握できたかなと思います。

WildFly側でProtoStreamのMarshallerを大量に作成しておき、Infinispanに格納する際のシリアライズ処理の効率化と利用者のMarshallerの実装の
手間を軽減している感じですね。

ところで、用意されていないクラスがマーシャリング対象になった場合はどうするんでしょうね…?自分でProtoStreamのMarshallerを
書いたらいいのでしょうか?

JBoss Marshalling

なお、比較としてJBoss Marshallingの方も見ておきましょう。JBoss Marshallingの方のモジュールは、割とあっさりしています。

https://github.com/wildfly/wildfly/tree/26.1.1.Final/clustering/marshalling/jboss/src/main/java/org/wildfly/clustering/marshalling/jboss

Java標準のシリアライズのように、個別にMarshallerを書かなくてもオブジェクトをシリアライズできますからね。

と、見ていくのはこれくらいにして、少し確認してみましょう。

お題

WildFlyのHTTPセッション管理を、Infinispan Serverとする変更をまずは行います。

つまり、このエントリーでやったことですね。

WildFlyのHTTPセッションの保存先をInfinispan Serverに変更する - CLOVER🍀

さらに、作成したソースコードを変更しつつ使って、WildFlyでのマーシャリングとInfinispan Serverでのエンコーディングの関連を
見ていこうと思います。

いきなりInfinispan Server(Hot Rod)でやっていて、デフォルトのEmbeddedを飛ばしていますが。こちらはデフォルトから変えることは
ないだろうし、まあいいかなと…。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.3, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-120-generic", arch: "amd64", family: "unix"

WildFlyは、最初に取得したように26.1.1.Finalを使います。起動は、そのままローカルで。

$ bin/standalone.sh

Infinispan Serverは13.0.10.Finalを使用し、172.18.0.2にひとつのNodeを用意することにします。クラスターは構成しません。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=`hostname -i`

今回、Infinispan Server上にユーザーは2つ作成します。

$ bin/cli.sh user create -g admin -p password ispn-admin
$ bin/cli.sh user create -g application -p password web-session-user

adminグループのユーザーでは、Cacheを作成します。WildFly側では実行時にCacheを定義させず、定義済みのCacheを利用する方針に
なりますね。

WildFlyからはapplicationグループのユーザーを使うことになります。

サンプルアプリケーション

サンプルアプリケーションのソースコードは、このエントリーからそのまま持ってきます。

WildFlyのHTTPセッションの保存先をInfinispan Serverに変更する - CLOVER🍀

Maven依存関係等。

    <groupId>org.littlewings</groupId>
    <artifactId>remote-wildfly-hotrod-session-marshaller</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

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

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>

WARファイル名は、ROOT.warとします。

HTTPセッションに保存するクラス。

src/main/java/org/littlewings/infinispan/session/Counter.java

package org.littlewings.infinispan.session;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter implements Serializable {
    private static final long serialVersionUID = 1L;

    AtomicInteger value = new AtomicInteger();

    public int increment() {
        return value.incrementAndGet();
    }

    public int getValue() {
        return value.get();
    }
}

HTTPセッションを操作するクラス。

src/main/java/org/littlewings/infinispan/session/CounterServlet.java

package org.littlewings.infinispan.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/counter")
public class CounterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();

        Counter counter = (Counter) session.getAttribute("counter");

        if (counter == null) {
            counter = new Counter();
            session.setAttribute("counter", counter);
        }

        int current = counter.increment();

        resp.getWriter().write(String.format("current value = %d%n", current));
    }
}

web.xml。<distributable/>を忘れずに。

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 webapp_4_0.xsd"
         version="4.0">
    <distributable/>
</web-app>

PROTOSTREAM Marshallerを使う

では、MarshallerにPROTOSTREAMを選択しつつ、いろいろ試してみましょう。

まずはInfinispan ServerにCacheを作成します。こんな感じのテンプレートを用意。

/path/to/cache-protostream.xml

<?xml version="1.0" ?>
<infinispan>
    <cache-container>
        <distributed-cache mode="SYNC" name="protoStreamCacheTemplate">
            <encoding>
                <key media-type="application/x-protostream"/>
                <value media-type="application/x-protostream"/>
            </encoding>
        </distributed-cache>
    </cache-container>
</infinispan>

encodingをProtoStreamに限定しています。

Infinispan Serverにログインして

$ bin/cli.sh -c http://ispn-admin:password@localhost:11222
[infinispan-server-10427@cluster//containers/default]>

Cacheを作成。

create cache ROOT.war -f /path/to/cache-protostream.xml

できました。

describe caches/ROOT.war
{
  "distributed-cache" : {
    "mode" : "SYNC",
    "encoding" : {
      "key" : {
        "media-type" : "application/x-protostream"
      },
      "value" : {
        "media-type" : "application/x-protostream"
      }
    }
  }
}

次に、WildFly側も準備をします。

管理CLIを起動して

$ bin/jboss-cli.sh -c
[standalone@localhost:9990 /] 

Socket Binding Group、Remote Cache Container、Hot Rod Session Managementを作成。デフォルトのHTTPセッション保存先も
Infinispan Serverにしておきます。

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server:add(host=172.18.0.2, port=11222)

batch
/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster)
/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server])
run-batch

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])
/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=properties,value={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password})

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=PROTOSTREAM)

/subsystem=distributable-web/hotrod-session-management=remote-session:add(remote-cache-container=remote-infinispan, cache-configuration=org.infinispan.DIST_SYNC, granularity=SESSION)
/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()
/subsystem=distributable-web:write-attribute(name=default-session-management,value=remote-session)

reload

しれっと入っていますが、MarshallerはPROTOSTREAMです。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=PROTOSTREAM)

では、アプリケーションをパッケージングしてデプロイしたいと思うのですが。

ふと思い立ったので、Serializableを外してデプロイしてみましょう。PROTOSTREAMを選んでもやっぱりダメなのでしょうか?

public class Counter /* implements Serializable */ {
    // private static final long serialVersionUID = 1L;

試してみましょう。パッケージング。

$ mvn package

デプロイ。

$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final/standalone/deployments

デプロイ自体は、問題なく成功します。

アクセスしてみましょう。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter

すると、WildFly側でSerializableではないということで失敗します。

13:42:55,469 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /counter: java.lang.IllegalArgumentException: java.io.NotSerializableException: org.littlewings.infinispan.session.Counter
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.coarse.CoarseSessionAttributes.setAttribute(CoarseSessionAttributes.java:76)
        at org.wildfly.clustering.web.undertow@26.1.1.Final//org.wildfly.clustering.web.undertow.session.DistributableSession.setAttribute(DistributableSession.java:228)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.spec.HttpSessionImpl.setAttribute(HttpSessionImpl.java:169)
        at deployment.ROOT.war//org.littlewings.infinispan.session.CounterServlet.doGet(CounterServlet.java:21)
        at javax.servlet.api@2.0.0.Final//javax.servlet.http.HttpServlet.service(HttpServlet.java:503)
        at javax.servlet.api@2.0.0.Final//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)

〜省略〜

やっぱりそうですよね。

というわけで、Serializableを実装するように戻して

public class Counter implements Serializable {
    private static final long serialVersionUID = 1L;

再度パッケージングしてデプロイ。

$ mvn package
$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final/standalone/deployments

当然ですが、今度はうまくいきます。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 1


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 2


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 3

では、Infinispan Serverに保存する際のエンコーディングも本当にProtoStreamなのでしょうか?

Infinispan Serverで、いったんCacheを削除します。

drop cache ROOT.war

encodingをJBoss Marshallingにしたテンプレートを用意。

/path/to//cache-jboss-marshalling.xml

<?xml version="1.0" ?>
<infinispan>
    <cache-container>
        <distributed-cache mode="SYNC" name="jbossMarshallingCacheTemplate">
            <encoding>
                <key media-type="application/x-jboss-marshalling"/>
                <value media-type="application/x-jboss-marshalling"/>
            </encoding>
        </distributed-cache>
    </cache-container>
</infinispan>

Cacheを作成。

create cache ROOT.war -f /path/to/cache-jboss-marshalling.xml

確認。

describe caches/ROOT.war
{
  "distributed-cache" : {
    "mode" : "SYNC",
    "encoding" : {
      "key" : {
        "media-type" : "application/x-jboss-marshalling"
      },
      "value" : {
        "media-type" : "application/x-jboss-marshalling"
      }
    }
  }
}

WildFly側をreloadしてみます。

reload

すると、Cacheのエンコーディングが合わないということで、アプリケーションの起動に失敗します。

13:54:23,932 WARN  [org.infinispan.HOTROD] (HotRod-client-async-pool-0) ISPN004005: Error received from the server: org.infinispan.commons.CacheConfigurationException: Unable to inject dependencies for component class org.infinispan.encoding.DataConversion, path wireDependencies (a org.infinispan.encoding.DataConversion)
org.infinispan.commons.dataconversion.EncodingException: ISPN000492: Cannot find transcoder between 'application/x-protostream' to 'application/x-jboss-marshalling'
13:54:23,932 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-2) MSC000001: Failed to start service jboss.clustering.web."ROOT.war": org.jboss.msc.service.StartException in service jboss.clustering.web."ROOT.war": org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=60 returned server error (status=0x85): org.infinispan.commons.CacheConfigurationException: Unable to inject dependencies for component class org.infinispan.encoding.DataConversion, path wireDependencies (a org.infinispan.encoding.DataConversion)
org.infinispan.commons.dataconversion.EncodingException: ISPN000492: Cannot find transcoder between 'application/x-protostream' to 'application/x-jboss-marshalling'
        at org.wildfly.clustering.service@26.1.1.Final//org.wildfly.clustering.service.FunctionalService.start(FunctionalService.java:66)
        at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1739)
        at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1701)
        at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$ControllerTask.run(ServiceControllerImpl.java:1559)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=60 returned server error (status=0x85): org.infinispan.commons.CacheConfigurationException: Unable to inject dependencies for component class org.infinispan.encoding.DataConversion, path wireDependencies (a org.infinispan.encoding.DataConversion)
org.infinispan.commons.dataconversion.EncodingException: ISPN000492: Cannot find transcoder between 'application/x-protostream' to 'application/x-jboss-marshalling'
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.protocol.Codec20.checkForErrorsInResponseStatus(Codec20.java:323)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.protocol.Codec20.readHeader(Codec20.java:168)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.transport.netty.HeaderDecoder.decode(HeaderDecoder.java:139)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.transport.netty.HintedReplayingDecoder.callDecode(HintedReplayingDecoder.java:94)
        at io.netty.netty-codec@4.1.76.Final//io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:279)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.netty-handler@4.1.76.Final//io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.netty-transport@4.1.76.Final//io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.netty-transport-native-epoll@4.1.76.Final//io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800)
        at io.netty.netty-transport-native-epoll@4.1.76.Final//io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:487)
        at io.netty.netty-transport-native-epoll@4.1.76.Final//io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:385)


〜省略〜

ということは、Infinispan Serverでの保存形態もやっぱりProtoStreamということですね。

なお、今回はreloadしましたが、裏でCacheの再作成をしてそのままアクセスしても、単純にWebアプリケーションを再デプロイしなおしても
同様の理由で失敗します。

JBOSS Marshallerを使う

次は、MarshallerにJBOSSを選択してみます。

Cacheは、encodingをJBoss Marshallerにして再作成。

drop cache ROOT.war


create cache ROOT.war -f /path/to/cache-jboss-marshalling.xml


describe caches/ROOT.war
{
  "distributed-cache" : {
    "mode" : "SYNC",
    "encoding" : {
      "key" : {
        "media-type" : "application/x-jboss-marshalling"
      },
      "value" : {
        "media-type" : "application/x-jboss-marshalling"
      }
    }
  }
}

/path/to//cache-jboss-marshalling.xml

<?xml version="1.0" ?>
<infinispan>
    <cache-container>
        <distributed-cache mode="SYNC" name="jbossMarshallingCacheTemplate">
            <encoding>
                <key media-type="application/x-jboss-marshalling"/>
                <value media-type="application/x-jboss-marshalling"/>
            </encoding>
        </distributed-cache>
    </cache-container>
</infinispan>

WildFlyは、インストールしなおして、Socket Binding Group、Remote Cache Container、Hot Rod Session Managementなどの設定。

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server:add(host=172.18.0.2, port=11222)

batch
/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster)
/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server])
run-batch

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])
/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=properties,value={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password})

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=JBOSS)

/subsystem=distributable-web/hotrod-session-management=remote-session:add(remote-cache-container=remote-infinispan, cache-configuration=org.infinispan.DIST_SYNC, granularity=SESSION)
/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()
/subsystem=distributable-web:write-attribute(name=default-session-management,value=remote-session)

reload

MarshallerはJBOSSです。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=JBOSS)

アプリケーションをデプロイ。

$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final/standalone/deployments

確認。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter

すると、SessionCreationMetaDataKeyというクラスをシリアライズできずに失敗します…。

14:07:39,868 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /counter: org.infinispan.client.hotrod.exceptions.HotRodClientException:: Unable to marshall object of type [org.wildfly.clustering.web.hotrod.session.SessionCreationMetaDataKey]
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.marshall.MarshallerUtil.obj2bytes(MarshallerUtil.java:121)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.DataFormat.keyToBytes(DataFormat.java:132)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.RemoteCacheImpl.keyToBytes(RemoteCacheImpl.java:575)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.RemoteCacheImpl.getAllAsync(RemoteCacheImpl.java:466)
        at org.wildfly.clustering.infinispan.client@26.1.1.Final//org.wildfly.clustering.infinispan.client.RegisteredRemoteCache.getAllAsync(RegisteredRemoteCache.java:277)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.impl.RemoteCacheSupport.getAll(RemoteCacheSupport.java:162)
        at org.wildfly.clustering.web.hotrod@26.1.1.Final//org.wildfly.clustering.web.hotrod.session.HotRodSessionMetaDataFactory.findValue(HotRodSessionMetaDataFactory.java:94)
        at org.wildfly.clustering.web.hotrod@26.1.1.Final//org.wildfly.clustering.web.hotrod.session.HotRodSessionMetaDataFactory.findValue(HotRodSessionMetaDataFactory.java:54)
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.CompositeSessionFactory.findValue(CompositeSessionFactory.java:63)
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.CompositeSessionFactory.findValue(CompositeSessionFactory.java:40)
        at org.wildfly.clustering.web.hotrod@26.1.1.Final//org.wildfly.clustering.web.hotrod.session.HotRodSessionManager.findSession(HotRodSessionManager.java:113)
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.ConcurrentSessionManager$2.apply(ConcurrentSessionManager.java:67)
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.ConcurrentSessionManager$2.apply(ConcurrentSessionManager.java:64)
        at org.wildfly.clustering.ee.cache@26.1.1.Final//org.wildfly.clustering.ee.cache.ConcurrentManager.apply(ConcurrentManager.java:91)
        at org.wildfly.clustering.web.cache@26.1.1.Final//org.wildfly.clustering.web.cache.session.ConcurrentSessionManager.findSession(ConcurrentSessionManager.java:72)
        at org.wildfly.clustering.web.undertow@26.1.1.Final//org.wildfly.clustering.web.undertow.session.DistributableSessionManager.getSession(DistributableSessionManager.java:236)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.spec.ServletContextImpl.getSession(ServletContextImpl.java:903)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.spec.HttpServletRequestImpl.getSession(HttpServletRequestImpl.java:425)
        at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ElytronHttpServletExchange$3.<init>(ElytronHttpServletExchange.java:242)
        at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ElytronHttpServletExchange.sessionScope(ElytronHttpServletExchange.java:241)
        at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ElytronHttpServletExchange.getScope(ElytronHttpServletExchange.java:163)
        at org.wildfly.security.elytron-base@1.19.0.Final//org.wildfly.security.http.HttpAuthenticator.getAttachableSessionScope(HttpAuthenticator.java:279)
        at org.wildfly.security.elytron-base@1.19.0.Final//org.wildfly.security.http.HttpAuthenticator.access$900(HttpAuthenticator.java:56)
        at org.wildfly.security.elytron-base@1.19.0.Final//org.wildfly.security.http.HttpAuthenticator$1.get(HttpAuthenticator.java:252)
        at org.wildfly.security.elytron-base@1.19.0.Final//org.wildfly.security.http.HttpAuthenticator.restoreIdentity(HttpAuthenticator.java:175)
        at org.wildfly.security.elytron-base@1.19.0.Final//org.wildfly.security.http.HttpAuthenticator.authenticate(HttpAuthenticator.java:90)
        at org.wildfly.security.elytron-web.undertow-server@1.10.1.Final//org.wildfly.elytron.web.undertow.server.SecurityContextImpl.authenticate(SecurityContextImpl.java:107)
        at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ServletSecurityContextImpl.authenticate(ServletSecurityContextImpl.java:115)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:55)
        at io.undertow.core@2.2.17.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.core@2.2.17.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
        at io.undertow.core@2.2.17.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
        at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
        at io.undertow.core@2.2.17.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
        at io.undertow.core@2.2.17.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
        at io.undertow.core@2.2.17.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:275)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.extension.undertow@26.1.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
        at io.undertow.servlet@2.2.17.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
        at io.undertow.core@2.2.17.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
        at io.undertow.core@2.2.17.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1348)
        at org.jboss.xnio@3.8.7.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.NotSerializableException: org.wildfly.clustering.web.hotrod.session.SessionCreationMetaDataKey
        at org.jboss.marshalling.river@2.0.12.Final//org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:274)
        at org.jboss.marshalling@2.0.12.Final//org.jboss.marshalling.AbstractObjectOutput.writeObject(AbstractObjectOutput.java:58)
        at org.jboss.marshalling@2.0.12.Final//org.jboss.marshalling.AbstractMarshaller.writeObject(AbstractMarshaller.java:111)
        at org.wildfly.clustering.marshalling.jboss@26.1.1.Final//org.wildfly.clustering.marshalling.jboss.JBossByteBufferMarshaller.writeTo(JBossByteBufferMarshaller.java:92)
        at org.wildfly.clustering.infinispan.marshalling@26.1.1.Final//org.wildfly.clustering.infinispan.marshalling.AbstractUserMarshaller.writeObject(AbstractUserMarshaller.java:62)
        at org.wildfly.clustering.infinispan.marshalling@26.1.1.Final//org.wildfly.clustering.infinispan.marshalling.AbstractMarshaller.objectToBuffer(AbstractMarshaller.java:107)
        at org.wildfly.clustering.infinispan.marshalling@26.1.1.Final//org.wildfly.clustering.infinispan.marshalling.AbstractMarshaller.objectToByteBuffer(AbstractMarshaller.java:101)
        at org.infinispan.client.hotrod@13.0.10.Final//org.infinispan.client.hotrod.marshall.MarshallerUtil.obj2bytes(MarshallerUtil.java:116)
        ... 60 more
Caused by: an exception which occurred:
        in object org.wildfly.clustering.web.hotrod.session.SessionCreationMetaDataKey@caabe3e6

〜省略〜

見てみると、確かにこのクラスは(継承元クラスやインターフェースを含めて)Serializableではありません…。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/web/hotrod/src/main/java/org/wildfly/clustering/web/hotrod/session/SessionCreationMetaDataKey.java

とすると、LEGACYはどうだったのでしょう?

LEGACYを確認する

最後に、LEGACYでも動かしてみます。

Infinispan Server側のCacheは、ProtoStreamで再作成。

drop cache ROOT.war


create cache ROOT.war -f /path/to/cache-protostream.xml


describe caches/ROOT.war
{
  "distributed-cache" : {
    "mode" : "SYNC",
    "encoding" : {
      "key" : {
        "media-type" : "application/x-protostream"
      },
      "value" : {
        "media-type" : "application/x-protostream"
      }
    }
  }
}

WildFlyの設定。

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server:add(host=172.18.0.2, port=11222)

batch
/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster)
/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server])
run-batch

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])
/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=properties,value={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password})

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=LEGACY)

/subsystem=distributable-web/hotrod-session-management=remote-session:add(remote-cache-container=remote-infinispan, cache-configuration=org.infinispan.DIST_SYNC, granularity=SESSION)
/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()
/subsystem=distributable-web:write-attribute(name=default-session-management,value=remote-session)

reload

marshallerはLEGACYにしておきます。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=marshaller,value=LEGACY)

アプリケーションをデプロイ。

$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final/standalone/deployments

確認。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 1


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 2


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 3

問題なく動きますね。ということはProtoStreamが実際のMarshallerになっているようです。

これはどういうことかというと。

LEGACYの場合は条件によってPROTOSTREAMかJBOSSを選択する、となっていましたがこちらをよく見てみます。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/client/src/main/java/org/wildfly/clustering/infinispan/client/marshaller/HotRodMarshallerFactory.java#L41-L53

    LEGACY() {
        private final Set<String> protoStreamModules = Collections.singleton("org.wildfly.clustering.web.hotrod");
        private final Predicate<String> protoStreamPredicate = this.protoStreamModules::contains;

        @Override
        public Marshaller apply(ModuleLoader moduleLoader, List<Module> modules) {
            // Choose marshaller based on the associated modules
            if (modules.stream().map(Module::getName).anyMatch(this.protoStreamPredicate)) {
                return PROTOSTREAM.apply(moduleLoader, modules);
            }
            return JBOSS.apply(moduleLoader, modules);
        }
    },

org.wildfly.clustering.web.hotrodというモジュールは、ここで指定していたもののことですね。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])

つまり、Hot Rod Session ManagementではProtoStreamが前提になっている気がしますね…。

Embeddedの方はどうでしょう。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/spi/src/main/java/org/wildfly/clustering/infinispan/spi/marshalling/InfinispanMarshallerFactory.java#L45-L57

    LEGACY() {
        private final Set<String> protoStreamModules = new HashSet<>(Arrays.asList("org.wildfly.clustering.server", "org.wildfly.clustering.ejb.infinispan", "org.wildfly.clustering.web.infinispan"));
        private final Predicate<String> protoStreamPredicate = this.protoStreamModules::contains;

        @Override
        public Marshaller apply(ModuleLoader moduleLoader, List<Module> modules) {
            // Choose marshaller based on the associated modules
            if (modules.stream().map(Module::getName).anyMatch(this.protoStreamPredicate)) {
                return PROTOSTREAM.apply(moduleLoader, modules);
            }
            return JBOSS.apply(moduleLoader, modules);
        }
    },

server、ejb、webですね。

Arrays.asList("org.wildfly.clustering.server", "org.wildfly.clustering.ejb.infinispan", "org.wildfly.clustering.web.infinispan")

少なくともHTTPセッションのMarshallerに関しては、ProtoStreamを使うことが前提になっていることがわかりました。

だいたい雰囲気がわかったので、これで良しとしましょう。

まとめ

WildFlyのInfinispanサブシステムで、MarshallerにProtoStreamが使われるようになっていたのでちょっと気になっていろいろ調べてみました。

むしろ、今のWildFlyのHTTPセッション等のマーシャリングの前提になっているようですね。ProtoStreamといえば、IDLを書いて
Marshallerを書くイメージだったのでちょっと驚きました。

覚えておきましょう。

あとちょっと気になるのは、WildFlyの用意しているProtoStreamのMarshallerでカバーできなかったりするケースがあるかどうかですが。
どうなんでしょうね?

testssl.shでサーバーが対応しているSSL/TLSプロトコル、暗号化アルゴリズムなどを確認する

これは、なにをしたくて書いたもの?

以前、OpenSSLとnmapを使って、サーバーが対応しているSSL/TLSプロトコルや暗号化アルゴリズムを確認する方法を調べてみました。

サーバーが対応しているSSL/TLSプロトコルを確認する(openssl s_client、nmap) - CLOVER🍀

他に、この用途ではtestssl.shというツールが便利そうなのでちょっと試してみることにしました。

testssl.sh

testssl.shは、サーバーのサポートしているSSL/TLS暗号化、プロトコル、最近の暗号化の血管などについてチェックしてくれるツールです。

testssl.sh is a free command line tool which checks a server's service on any port for the support of TLS/SSL ciphers, protocols as well as recent cryptographic flaws and more.

/bin/bash based SSL/TLS tester: testssl.sh

主な機能は、「Key features」に書かれています。

  • 出力が明確で、良し悪しを容易に見分けられる
  • LinuxやmacOS、FreeBSD等のbashが動作する環境ですぐに使用でき、追加モジュールのインストールは不要
    • Dockerイメージも提供されている
  • Webサーバーだけではなく、STARTTLSサービスに対してもテストができる
  • いくつかのコマンドラインオプションは、利用者のテストの実行や構成の出力に役立つ
  • クライアント側の機能が不足していて特定のチェックができない場合は、警告が出力される

manページもあります。

testssl(1)

GitHubリポジトリはこちらです。

GitHub - drwetter/testssl.sh: Testing TLS/SSL encryption anywhere on any port

簡単に試してみましょう。

環境

今回の環境は、こちら。Ubuntu Linux 20.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
Codename:       focal


$ uname -srvmpio
Linux 5.4.0-117-generic #132-Ubuntu SMP Thu Jun 2 00:39:06 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

お題

IPアドレス192.168.33.11のサーバーに、SSL/TLSを有効にしたApacheを用意します。

ここに、別のサーバーからtestssl.shを使ってアクセスして、構築したApacheが対応しているSSL/TLSのバージョンを確認してみます。

Apacheの用意

まずは、Apacheをインストールします。

$ sudo apt install apache2


$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2022-04-26T18:02:11

mod_sslを有効にして、SSL/TLS用のVirtualHostも有効にします。

$ sudo a2enmod ssl
$ sudo a2ensite default-ssl
$ sudo systemctl restart apache2

確認。

$ curl -I -k https://192.168.33.11
HTTP/1.1 200 OK
Date: Mon, 13 Jun 2022 15:09:16 GMT
Server: Apache/2.4.41 (Ubuntu)
Last-Modified: Mon, 13 Jun 2022 15:07:29 GMT
ETag: "2aa6-5e155a592c396"
Accept-Ranges: bytes
Content-Length: 10918
Vary: Accept-Encoding
Content-Type: text/html

ApacheのデフォルトのSSL/TLS設定はこちらです。

$ grep -v '.*#' /etc/apache2/mods-enabled/ssl.conf
<IfModule mod_ssl.c>

        SSLRandomSeed startup builtin
        SSLRandomSeed startup file:/dev/urandom 512
        SSLRandomSeed connect builtin
        SSLRandomSeed connect file:/dev/urandom 512


        AddType application/x-x509-ca-cert .crt
        AddType application/x-pkcs7-crl .crl

        SSLPassPhraseDialog  exec:/usr/share/apache2/ask-for-passphrase

        SSLSessionCache         shmcb:${APACHE_RUN_DIR}/ssl_scache(512000)
        SSLSessionCacheTimeout  300



        SSLCipherSuite HIGH:!aNULL


        SSLProtocol all -SSLv3



</IfModule>

testssl.shをインストールする

testssl.shのインストール方法は、いくつかあるようです。

  • GitHubリポジトリをクローンする
  • GitHubのReleasesからダウンロードする
  • Dockerイメージを使う

今回は、GitHubのReleasesからダウンロードする方法を取りたいと思います。

$ curl -OL https://github.com/drwetter/testssl.sh/archive/refs/tags/v3.0.7.tar.gz

ダウンロード後、展開してディレクトリ内へ移動。

$ tar xf v3.0.7.tar.gz
$ cd testssl.sh-3.0.7

バージョン。

$ ./testssl.sh --version

###########################################################
    testssl.sh       3.0.7 from https://testssl.sh/

      This program is free software. Distribution and
             modification under GPLv2 permitted.
      USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!

       Please file bugs @ https://testssl.sh/bugs/

###########################################################

 Using "OpenSSL 1.0.2-chacha (1.0.2k-dev)" [~183 ciphers]
 on ubuntu2004:./bin/openssl.Linux.x86_64
 (built: "Jan 18 17:12:17 2019", platform: "linux-x86_64")

ヘルプ。

$ ./testssl.sh --help

     "testssl.sh [options] <URI>"    or    "testssl.sh <options>"


"testssl.sh <options>", where <options> is:

     --help                        what you're looking at
     -b, --banner                  displays banner + version of testssl.sh
     -v, --version                 same as previous
     -V, --local                   pretty print all local ciphers
     -V, --local <pattern>         which local ciphers with <pattern> are available? If pattern is not a number: word match

     <pattern>                     is always an ignore case word pattern of cipher hexcode or any other string in the name, kx or bits

"testssl.sh <URI>", where <URI> is:

     <URI>                         host|host:port|URL|URL:port   port 443 is default, URL can only contain HTTPS protocol)

"testssl.sh [options] <URI>", where [options] is:

     -t, --starttls <protocol>     Does a default run against a STARTTLS enabled <protocol,
                                   protocol is <ftp|smtp|lmtp|pop3|imap|xmpp|telnet|ldap|nntp|postgres|mysql>
     --xmpphost <to_domain>        For STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed
     --mx <domain/host>            Tests MX records from high to low priority (STARTTLS, port 25)

〜省略〜

file output options (can also be preset via environment variables)
     --log, --logging              logs stdout to '${NODE}-p${port}${YYYYMMDD-HHMM}.log' in current working directory (cwd)
     --logfile|-oL <logfile>       logs stdout to 'dir/${NODE}-p${port}${YYYYMMDD-HHMM}.log'. If 'logfile' is a dir or to a specified 'logfile'
     --json                        additional output of findings to flat JSON file '${NODE}-p${port}${YYYYMMDD-HHMM}.json' in cwd
     --jsonfile|-oj <jsonfile>     additional output to the specified flat JSON file or directory, similar to --logfile
     --json-pretty                 additional JSON structured output of findings to a file '${NODE}-p${port}${YYYYMMDD-HHMM}.json' in cwd
     --jsonfile-pretty|-oJ <jsonfile>  additional JSON structured output to the specified file or directory, similar to --logfile
     --csv                         additional output of findings to CSV file '${NODE}-p${port}${YYYYMMDD-HHMM}.csv' in cwd or directory
     --csvfile|-oC <csvfile>       additional output as CSV to the specified file or directory, similar to --logfile
     --html                        additional output as HTML to file '${NODE}-p${port}${YYYYMMDD-HHMM}.html'
     --htmlfile|-oH <htmlfile>     additional output as HTML to the specified file or directory, similar to --logfile
     --out(f,F)ile|-oa/-oA <fname> log to a LOG,JSON,CSV,HTML file (see nmap). -oA/-oa: pretty/flat JSON.
                                   "auto" uses '${NODE}-p${port}${YYYYMMDD-HHMM}'. If fname if a dir uses 'dir/${NODE}-p${port}${YYYYMMDD-HHMM}'
     --hints                       additional hints to findings
     --severity <severity>         severities with lower level will be filtered for CSV+JSON, possible values <LOW|MEDIUM|HIGH|CRITICAL>
     --append                      if (non-empty) <logfile>, <csvfile>, <jsonfile> or <htmlfile> exists, append to file. Omits any header
     --outprefix <fname_prefix>    before  '${NODE}.' above prepend <fname_prefix>


Options requiring a value can also be called with '=' e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl <URI>.
<URI> always needs to be the last parameter.

使ってみる

では、testssl.shを使ってみましょう。

ドキュメントやmanによると、以下のコマンドで実行するようです。

$ testssl.sh [OPTIONS] <URI>, testssl.sh [OPTIONS] --file <FILE>

URIの書式は以下になります。URLを指定できるのは、HTTPSに限るようです。

"testssl.sh <URI>", where <URI> is:

     <URI>                         host|host:port|URL|URL:port   port 443 is default, URL can only contain HTTPS protocol)

つまり、今回の環境はHTTPSを有効にしたApacheなので、以下のどちらかで確認できることになります。

$ ./testssl.sh https://192.168.33.11


$ ./testssl.sh 192.168.33.11:443

実行してみます。

$ ./testssl.sh 192.168.33.11:443

###########################################################
    testssl.sh       3.0.7 from https://testssl.sh/

      This program is free software. Distribution and
             modification under GPLv2 permitted.
      USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!

       Please file bugs @ https://testssl.sh/bugs/

###########################################################

 Using "OpenSSL 1.0.2-chacha (1.0.2k-dev)" [~183 ciphers]
 on ubuntu2004:./bin/openssl.Linux.x86_64
 (built: "Jan 18 17:12:17 2019", platform: "linux-x86_64")


 Start 2022-06-14 01:42:14        -->> 192.168.33.11:443 (192.168.33.11) <<--

 rDNS (192.168.33.11):   --
 Service detected:       HTTP


 Testing protocols via sockets except NPN+ALPN

 SSLv2      not offered (OK)
 SSLv3      not offered (OK)
 TLS 1      not offered
 TLS 1.1    not offered
 TLS 1.2    offered (OK)
 TLS 1.3    offered (OK): final
 NPN/SPDY   not offered
 ALPN/HTTP2 http/1.1 (offered)

 Testing cipher categories

 NULL ciphers (no encryption)                  not offered (OK)
 Anonymous NULL Ciphers (no authentication)    not offered (OK)
 Export ciphers (w/o ADH+NULL)                 not offered (OK)
 LOW: 64 Bit + DES, RC[2,4] (w/o export)       not offered (OK)
 Triple DES Ciphers / IDEA                     not offered
 Obsolete CBC ciphers (AES, ARIA etc.)         offered
 Strong encryption (AEAD ciphers)              offered (OK)


 Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4

 PFS is offered (OK)          TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384
                              ECDHE-RSA-AES256-SHA DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305
                              DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA ECDHE-RSA-CAMELLIA256-SHA384
                              DHE-RSA-CAMELLIA256-SHA256 DHE-RSA-CAMELLIA256-SHA DHE-RSA-ARIA256-GCM-SHA384 ECDHE-ARIA256-GCM-SHA384
                              TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES128-SHA
                              DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA
                              ECDHE-RSA-CAMELLIA128-SHA256 DHE-RSA-CAMELLIA128-SHA256 DHE-RSA-CAMELLIA128-SHA DHE-RSA-ARIA128-GCM-SHA256
                              ECDHE-ARIA128-GCM-SHA256
 Elliptic curves offered:     prime256v1 secp384r1 secp521r1 X25519 X448
 DH group offered:            RFC3526/Oakley Group 14 (2048 bits)

 Testing server preferences

 Has server cipher order?     no (NOT ok)
 Negotiated protocol          TLSv1.3
 Negotiated cipher            TLS_AES_256_GCM_SHA384, 253 bit ECDH (X25519) (limited sense as client will pick)
 Negotiated cipher per proto  (limited sense as client will pick)
     ECDHE-RSA-AES256-GCM-SHA384:   TLSv1.2
     TLS_AES_128_GCM_SHA256:        TLSv1.3
 No further cipher order check has been done as order is determined by the client


 Testing server defaults (Server Hello)

 TLS extensions (standard)    "renegotiation info/#65281" "EC point formats/#11" "session ticket/#35" "supported versions/#43" "key share/#51"
                              "supported_groups/#10" "max fragment length/#1" "application layer protocol negotiation/#16" "encrypt-then-mac/#22"
                              "extended master secret/#23"
 Session Ticket RFC 5077 hint 300 seconds, session tickets keys seems to be rotated < daily
 SSL Session ID support       yes
 Session Resumption           Tickets: yes, ID: yes
 TLS clock skew               Random values, no fingerprinting possible
 Signature Algorithm          SHA256 with RSA
 Server key size              RSA 2048 bits
 Server key usage             --
 Server extended key usage    --
 Serial                       529926083E8825C4B598C836C21C368E8A7A3C58 (OK: length 20)
 Fingerprints                 SHA1 AFE6367C62663F465F166F9EAE2B75F63AA568D9
                              SHA256 43BEE2A6A9C92EFB380A5E3A0999860431A2B886970C15F0F18248FC594EB96B
 Common Name (CN)             web-server
 subjectAltName (SAN)         web-server
 Issuer                       web-server
 Trust (hostname)             certificate does not match supplied URI
 Chain of trust               NOT ok (self signed)
 EV cert (experimental)       no
 ETS/"eTLS", visibility info  not present
 Certificate Validity (UTC)   3649 >= 60 days (2022-06-13 16:40 --> 2032-06-10 16:40)
                              >= 10 years is way too long
 # of certificates provided   1
 Certificate Revocation List  --
 OCSP URI                     --
                              NOT ok -- neither CRL nor OCSP URI provided
 OCSP stapling                not offered
 OCSP must staple extension   --
 DNS CAA RR (experimental)    not offered
 Certificate Transparency     --


 Testing HTTP header response @ "/"

 HTTP Status Code             200 OK
 HTTP clock skew              0 sec from localtime
 Strict Transport Security    not offered
 Public Key Pinning           --
 Server banner                Apache/2.4.41 (Ubuntu)
 Application banner           --
 Cookie(s)                    (none issued at "/")
 Security headers             --
 Reverse Proxy banner         --


 Testing vulnerabilities

 Heartbleed (CVE-2014-0160)                not vulnerable (OK), no heartbeat extension
 CCS (CVE-2014-0224)                       not vulnerable (OK)
 Ticketbleed (CVE-2016-9244), experiment.  not vulnerable (OK)
 ROBOT                                     not vulnerable (OK)
 Secure Renegotiation (RFC 5746)           supported (OK)
 Secure Client-Initiated Renegotiation     not vulnerable (OK)
 CRIME, TLS (CVE-2012-4929)                not vulnerable (OK)
 BREACH (CVE-2013-3587)                    potentially NOT ok, "gzip" HTTP compression detected. - only supplied "/" tested
                                           Can be ignored for static pages or if no secrets in the page
 POODLE, SSL (CVE-2014-3566)               not vulnerable (OK), no SSLv3 support
 TLS_FALLBACK_SCSV (RFC 7507)              No fallback possible (OK), no protocol below TLS 1.2 offered
 SWEET32 (CVE-2016-2183, CVE-2016-6329)    not vulnerable (OK)
 FREAK (CVE-2015-0204)                     not vulnerable (OK)
 DROWN (CVE-2016-0800, CVE-2016-0703)      not vulnerable on this host and port (OK)
                                           make sure you don't use this certificate elsewhere with SSLv2 enabled services
                                           https://censys.io/ipv4?q=43BEE2A6A9C92EFB380A5E3A0999860431A2B886970C15F0F18248FC594EB96B could help you to find out
 LOGJAM (CVE-2015-4000), experimental      common prime with 2048 bits detected: RFC3526/Oakley Group 14 (2048 bits),
                                           but no DH EXPORT ciphers
 BEAST (CVE-2011-3389)                     not vulnerable (OK), no SSL3 or TLS1
 LUCKY13 (CVE-2013-0169), experimental     potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches
 RC4 (CVE-2013-2566, CVE-2015-2808)        no RC4 ciphers detected (OK)


 Testing 370 ciphers via OpenSSL plus sockets against the server, ordered by encryption strength

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits     Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
 x1302   TLS_AES_256_GCM_SHA384            ECDH 253   AESGCM      256      TLS_AES_256_GCM_SHA384
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 253   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
 xc028   ECDHE-RSA-AES256-SHA384           ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
 xc014   ECDHE-RSA-AES256-SHA              ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
 x9f     DHE-RSA-AES256-GCM-SHA384         DH 2048    AESGCM      256      TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
 xccaa   DHE-RSA-CHACHA20-POLY1305         DH 2048    ChaCha20    256      TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
 xc0a3   DHE-RSA-AES256-CCM8               DH 2048    AESCCM8     256      TLS_DHE_RSA_WITH_AES_256_CCM_8
 xc09f   DHE-RSA-AES256-CCM                DH 2048    AESCCM      256      TLS_DHE_RSA_WITH_AES_256_CCM
 x6b     DHE-RSA-AES256-SHA256             DH 2048    AES         256      TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
 x39     DHE-RSA-AES256-SHA                DH 2048    AES         256      TLS_DHE_RSA_WITH_AES_256_CBC_SHA
 xc077   ECDHE-RSA-CAMELLIA256-SHA384      ECDH 521   Camellia    256      TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384
 xc4     DHE-RSA-CAMELLIA256-SHA256        DH 2048    Camellia    256      TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256
 x88     DHE-RSA-CAMELLIA256-SHA           DH 2048    Camellia    256      TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
 x9d     AES256-GCM-SHA384                 RSA        AESGCM      256      TLS_RSA_WITH_AES_256_GCM_SHA384
 xc0a1   AES256-CCM8                       RSA        AESCCM8     256      TLS_RSA_WITH_AES_256_CCM_8
 xc09d   AES256-CCM                        RSA        AESCCM      256      TLS_RSA_WITH_AES_256_CCM
 x3d     AES256-SHA256                     RSA        AES         256      TLS_RSA_WITH_AES_256_CBC_SHA256
 x35     AES256-SHA                        RSA        AES         256      TLS_RSA_WITH_AES_256_CBC_SHA
 xc0     CAMELLIA256-SHA256                RSA        Camellia    256      TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256
 x84     CAMELLIA256-SHA                   RSA        Camellia    256      TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
 xc051   ARIA256-GCM-SHA384                RSA        ARIAGCM     256      TLS_RSA_WITH_ARIA_256_GCM_SHA384
 xc053   DHE-RSA-ARIA256-GCM-SHA384        DH 2048    ARIAGCM     256      TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384
 xc061   ECDHE-ARIA256-GCM-SHA384          ECDH 521   ARIAGCM     256      TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384
 x1301   TLS_AES_128_GCM_SHA256            ECDH 253   AESGCM      128      TLS_AES_128_GCM_SHA256
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
 xc027   ECDHE-RSA-AES128-SHA256           ECDH 521   AES         128      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
 xc013   ECDHE-RSA-AES128-SHA              ECDH 521   AES         128      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
 x9e     DHE-RSA-AES128-GCM-SHA256         DH 2048    AESGCM      128      TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
 xc0a2   DHE-RSA-AES128-CCM8               DH 2048    AESCCM8     128      TLS_DHE_RSA_WITH_AES_128_CCM_8
 xc09e   DHE-RSA-AES128-CCM                DH 2048    AESCCM      128      TLS_DHE_RSA_WITH_AES_128_CCM
 xc0a0   AES128-CCM8                       RSA        AESCCM8     128      TLS_RSA_WITH_AES_128_CCM_8
 xc09c   AES128-CCM                        RSA        AESCCM      128      TLS_RSA_WITH_AES_128_CCM
 x67     DHE-RSA-AES128-SHA256             DH 2048    AES         128      TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
 x33     DHE-RSA-AES128-SHA                DH 2048    AES         128      TLS_DHE_RSA_WITH_AES_128_CBC_SHA
 xc076   ECDHE-RSA-CAMELLIA128-SHA256      ECDH 521   Camellia    128      TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
 xbe     DHE-RSA-CAMELLIA128-SHA256        DH 2048    Camellia    128      TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
 x45     DHE-RSA-CAMELLIA128-SHA           DH 2048    Camellia    128      TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
 x9c     AES128-GCM-SHA256                 RSA        AESGCM      128      TLS_RSA_WITH_AES_128_GCM_SHA256
 x3c     AES128-SHA256                     RSA        AES         128      TLS_RSA_WITH_AES_128_CBC_SHA256
 x2f     AES128-SHA                        RSA        AES         128      TLS_RSA_WITH_AES_128_CBC_SHA
 xba     CAMELLIA128-SHA256                RSA        Camellia    128      TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256
 x41     CAMELLIA128-SHA                   RSA        Camellia    128      TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
 xc050   ARIA128-GCM-SHA256                RSA        ARIAGCM     128      TLS_RSA_WITH_ARIA_128_GCM_SHA256
 xc052   DHE-RSA-ARIA128-GCM-SHA256        DH 2048    ARIAGCM     128      TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256
 xc060   ECDHE-ARIA128-GCM-SHA256          ECDH 521   ARIAGCM     128      TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256


 Running client simulations (HTTP) via sockets

 Android 4.4.2                TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 521 bit ECDH (P-521)
 Android 5.0.0                TLSv1.2 ECDHE-RSA-AES256-SHA, 521 bit ECDH (P-521)
 Android 6.0                  TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
 Android 7.0 (native)         TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
 Android 8.1 (native)         TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
 Android 9.0 (native)         TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Android 10.0 (native)        TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Chrome 74 (Win 10)           TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Chrome 79 (Win 10)           TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Firefox 66 (Win 8.1/10)      TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Firefox 71 (Win 10)          TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 IE 6 XP                      No connection
 IE 8 Win 7                   No connection
 IE 8 XP                      No connection
 IE 11 Win 7                  TLSv1.2 ECDHE-RSA-AES256-SHA384, 256 bit ECDH (P-256)
 IE 11 Win 8.1                TLSv1.2 ECDHE-RSA-AES256-SHA384, 256 bit ECDH (P-256)
 IE 11 Win Phone 8.1          TLSv1.2 AES128-SHA256, No FS
 IE 11 Win 10                 TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 Edge 15 Win 10               TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
 Edge 17 (Win 10)             TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
 Opera 66 (Win 10)            TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
 Safari 9 iOS 9               TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 Safari 9 OS X 10.11          TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 Safari 10 OS X 10.12         TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 Safari 12.1 (iOS 12.2)       TLSv1.3 TLS_CHACHA20_POLY1305_SHA256, 253 bit ECDH (X25519)
 Safari 13.0 (macOS 10.14.6)  TLSv1.3 TLS_CHACHA20_POLY1305_SHA256, 253 bit ECDH (X25519)
 Apple ATS 9 iOS 9            TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 Java 6u45                    No connection
 Java 7u25                    No connection
 Java 8u161                   TLSv1.2 ECDHE-RSA-AES256-SHA384, 256 bit ECDH (P-256)
 Java 11.0.2 (OpenJDK)        TLSv1.3 TLS_AES_128_GCM_SHA256, 256 bit ECDH (P-256)
 Java 12.0.1 (OpenJDK)        TLSv1.3 TLS_AES_128_GCM_SHA256, 256 bit ECDH (P-256)
 OpenSSL 1.0.2e               TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
 OpenSSL 1.1.0l (Debian)      TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
 OpenSSL 1.1.1d (Debian)      TLSv1.3 TLS_AES_256_GCM_SHA384, 253 bit ECDH (X25519)
 Thunderbird (68.3)           TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)

 Done 2022-06-14 01:43:29 [  76s] -->> 192.168.33.11:443 (192.168.33.11) <<--

ものすごく詳細に…というかいろいろ確認してくれますね。ビックリしました。
セキュリティに関する項目も含まれています。

チェックしている内容ですが、ドキュメントによるとtestssl.sh [URI]指定だと-Eおよび-gを除いた内容をチェックするようです。

single check as <options>  ("testssl.sh URI" does everything except -E and -g):
     -e, --each-cipher             checks each local cipher remotely
     -E, --cipher-per-proto        checks those per protocol
     -s, --std, --standard         tests certain lists of cipher suites by strength
     -p, --protocols               checks TLS/SSL protocols (including SPDY/HTTP2)
     -g, --grease                  tests several server implementation bugs like GREASE and size limitations
     -S, --server-defaults         displays the server's default picks and certificate info
     -P, --server-preference       displays the server's picks: protocol+cipher
     -x, --single-cipher <pattern> tests matched <pattern> of ciphers
                                   (if <pattern> not a number: word match)
     -c, --client-simulation       test client simulations, see which client negotiates with cipher and protocol
     -h, --header, --headers       tests HSTS, HPKP, server/app banner, security headers, cookie, reverse proxy, IPv4 address

     -U, --vulnerable              tests all (of the following) vulnerabilities (if applicable)
     -H, --heartbleed              tests for Heartbleed vulnerability
     -I, --ccs, --ccs-injection    tests for CCS injection vulnerability
     -T, --ticketbleed             tests for Ticketbleed vulnerability in BigIP loadbalancers
     -BB, --robot                  tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability
     -R, --renegotiation           tests for renegotiation vulnerabilities
     -C, --compression, --crime    tests for CRIME vulnerability (TLS compression issue)
     -B, --breach                  tests for BREACH vulnerability (HTTP compression issue)
     -O, --poodle                  tests for POODLE (SSL) vulnerability
     -Z, --tls-fallback            checks TLS_FALLBACK_SCSV mitigation
     -W, --sweet32                 tests 64 bit block ciphers (3DES, RC2 and IDEA): SWEET32 vulnerability
     -A, --beast                   tests for BEAST vulnerability
     -L, --lucky13                 tests for LUCKY13
     -F, --freak                   tests for FREAK vulnerability
     -J, --logjam                  tests for LOGJAM vulnerability
     -D, --drown                   tests for DROWN vulnerability
     -f, --pfs, --fs, --nsa        checks (perfect) forward secrecy settings
     -4, --rc4, --appelbaum        which RC4 ciphers are being offered?

個々のオプションを指定することで、チェックする内容を絞ることもできるようです。

たとえば、以下は-eと-pで暗号化スイートとプロトコルをチェックします。

$ ./testssl.sh -e -p 192.168.33.11:443

結果。

###########################################################
    testssl.sh       3.0.7 from https://testssl.sh/

      This program is free software. Distribution and
             modification under GPLv2 permitted.
      USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!

       Please file bugs @ https://testssl.sh/bugs/

###########################################################

 Using "OpenSSL 1.0.2-chacha (1.0.2k-dev)" [~183 ciphers]
 on ubuntu2004:./bin/openssl.Linux.x86_64
 (built: "Jan 18 17:12:17 2019", platform: "linux-x86_64")


 Start 2022-06-14 02:01:46        -->> 192.168.33.11:443 (192.168.33.11) <<--

 rDNS (192.168.33.11):   --
 Service detected:       HTTP


 Testing protocols via sockets except NPN+ALPN

 SSLv2      not offered (OK)
 SSLv3      not offered (OK)
 TLS 1      not offered
 TLS 1.1    not offered
 TLS 1.2    offered (OK)
 TLS 1.3    offered (OK): final
 NPN/SPDY   not offered
 ALPN/HTTP2 http/1.1 (offered)

 Testing 370 ciphers via OpenSSL plus sockets against the server, ordered by encryption strength

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits     Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
 x1302   TLS_AES_256_GCM_SHA384            ECDH 253   AESGCM      256      TLS_AES_256_GCM_SHA384
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 253   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
 xc028   ECDHE-RSA-AES256-SHA384           ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
 xc014   ECDHE-RSA-AES256-SHA              ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
 x9f     DHE-RSA-AES256-GCM-SHA384         DH 2048    AESGCM      256      TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
 xccaa   DHE-RSA-CHACHA20-POLY1305         DH 2048    ChaCha20    256      TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
 xc0a3   DHE-RSA-AES256-CCM8               DH 2048    AESCCM8     256      TLS_DHE_RSA_WITH_AES_256_CCM_8
 xc09f   DHE-RSA-AES256-CCM                DH 2048    AESCCM      256      TLS_DHE_RSA_WITH_AES_256_CCM
 x6b     DHE-RSA-AES256-SHA256             DH 2048    AES         256      TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
 x39     DHE-RSA-AES256-SHA                DH 2048    AES         256      TLS_DHE_RSA_WITH_AES_256_CBC_SHA
 xc077   ECDHE-RSA-CAMELLIA256-SHA384      ECDH 521   Camellia    256      TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384
 xc4     DHE-RSA-CAMELLIA256-SHA256        DH 2048    Camellia    256      TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256
 x88     DHE-RSA-CAMELLIA256-SHA           DH 2048    Camellia    256      TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
 x9d     AES256-GCM-SHA384                 RSA        AESGCM      256      TLS_RSA_WITH_AES_256_GCM_SHA384
 xc0a1   AES256-CCM8                       RSA        AESCCM8     256      TLS_RSA_WITH_AES_256_CCM_8
 xc09d   AES256-CCM                        RSA        AESCCM      256      TLS_RSA_WITH_AES_256_CCM
 x3d     AES256-SHA256                     RSA        AES         256      TLS_RSA_WITH_AES_256_CBC_SHA256
 x35     AES256-SHA                        RSA        AES         256      TLS_RSA_WITH_AES_256_CBC_SHA
 xc0     CAMELLIA256-SHA256                RSA        Camellia    256      TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256
 x84     CAMELLIA256-SHA                   RSA        Camellia    256      TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
 xc051   ARIA256-GCM-SHA384                RSA        ARIAGCM     256      TLS_RSA_WITH_ARIA_256_GCM_SHA384
 xc053   DHE-RSA-ARIA256-GCM-SHA384        DH 2048    ARIAGCM     256      TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384
 xc061   ECDHE-ARIA256-GCM-SHA384          ECDH 521   ARIAGCM     256      TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384
 x1301   TLS_AES_128_GCM_SHA256            ECDH 253   AESGCM      128      TLS_AES_128_GCM_SHA256
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
 xc027   ECDHE-RSA-AES128-SHA256           ECDH 521   AES         128      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
 xc013   ECDHE-RSA-AES128-SHA              ECDH 521   AES         128      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
 x9e     DHE-RSA-AES128-GCM-SHA256         DH 2048    AESGCM      128      TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
 xc0a2   DHE-RSA-AES128-CCM8               DH 2048    AESCCM8     128      TLS_DHE_RSA_WITH_AES_128_CCM_8
 xc09e   DHE-RSA-AES128-CCM                DH 2048    AESCCM      128      TLS_DHE_RSA_WITH_AES_128_CCM
 xc0a0   AES128-CCM8                       RSA        AESCCM8     128      TLS_RSA_WITH_AES_128_CCM_8
 xc09c   AES128-CCM                        RSA        AESCCM      128      TLS_RSA_WITH_AES_128_CCM
 x67     DHE-RSA-AES128-SHA256             DH 2048    AES         128      TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
 x33     DHE-RSA-AES128-SHA                DH 2048    AES         128      TLS_DHE_RSA_WITH_AES_128_CBC_SHA
 xc076   ECDHE-RSA-CAMELLIA128-SHA256      ECDH 521   Camellia    128      TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
 xbe     DHE-RSA-CAMELLIA128-SHA256        DH 2048    Camellia    128      TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
 x45     DHE-RSA-CAMELLIA128-SHA           DH 2048    Camellia    128      TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
 x9c     AES128-GCM-SHA256                 RSA        AESGCM      128      TLS_RSA_WITH_AES_128_GCM_SHA256
 x3c     AES128-SHA256                     RSA        AES         128      TLS_RSA_WITH_AES_128_CBC_SHA256
 x2f     AES128-SHA                        RSA        AES         128      TLS_RSA_WITH_AES_128_CBC_SHA
 xba     CAMELLIA128-SHA256                RSA        Camellia    128      TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256
 x41     CAMELLIA128-SHA                   RSA        Camellia    128      TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
 xc050   ARIA128-GCM-SHA256                RSA        ARIAGCM     128      TLS_RSA_WITH_ARIA_128_GCM_SHA256
 xc052   DHE-RSA-ARIA128-GCM-SHA256        DH 2048    ARIAGCM     128      TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256
 xc060   ECDHE-ARIA128-GCM-SHA256          ECDH 521   ARIAGCM     128      TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256


 Done 2022-06-14 02:02:03 [  19s] -->> 192.168.33.11:443 (192.168.33.11) <<--

コマンドの実行結果を、ファイルに出力することもできるようです。

たとえば、--htmlオプションでカレントディレクトリにHTMLファイルを出力してくれます。

$ ./testssl.sh --html 192.168.33.11:443

指定したホスト名、ポートが含まれるHTMLファイルになるようです。

$ ll *.html
-rw-rw-r-- 1 xxxxx xxxxx 25771  6月 14 01:52 192.168.33.11_p443-20220614-0151.html
-rw-rw-r-- 1 xxxxx xxxxx 62258  2月 19 22:38 openssl-iana.mapping.html

確認。

けっこうお手軽に使えるので、覚えておくと便利そうですね。