yuusuke-roughの日記

Java,SpringBoot,趣味等

WebSocketの学習日記 in Spring Boot

はじめに

単体テスト前に技術について整理→コード修正・追加→単体テストの三段構えで行きます。

いつもそのつもりで書いておりますが、完全に日記です。右往左往した苦しい時間もみっちり記録しております。

理解に乏しい部分が多々あります。

ブログ訪問者が0人なのでないとは思いますが、もし、この記事を読まれた方いらっしゃいましたら、ぜひ教えてください。

お願い致します。

 

内容

 

事前知識整理(※分からない事があったら戻る専用)

 

とりあえず、WebSocketとSTOMPの違いから

What is the difference between WebSocket and STOMP protocols? - Stack Overflow

 

STOMP Specification, Version 1.2 日本語訳 · GitHub

 

入門 | WebSocket を使用してインタラクティブな Web アプリケーションを構築

上記は、ただのSTOMPではなく、STOMP over WebSocketで、WebSocket上でSTOMPを使用している。

 

26. WebSocket Support

(3日目に気づく...ここ読まなきゃダメでしょ...orz)

 

 

用語:

メッセージブローカー・・・メッセージの管理等を行いメッセージを送信する機能

STOMP.js・・・STOMP over WebSocket通信をサポートする。

STOMP.js Documentation

Sock.js・・・WebSocketで使用するようなオブジェクトを提供する。

GitHub - sockjs/sockjs-client: WebSocket emulation - Javascript client

 

 

実装のお題

1.ハートビートによるセッション管理

 

 

以前、実装でつまずいた点が、「ハートビートが動作しなくなった時のセッションの切断方法は?」という事だった。

以前から目をつけていたのは

WebSocketHandler (Spring Framework API) - Javadoc

22. WebSocket Support

どう適用すればいいんだろう。

 

WebSocketMessageBrokerConfigurer (Spring Framework API) - Javadoc

にあった。configureWebSocketTransport...そのままの名前...

WebSocketTransportRegistrationにてaddDecoratorFactoryのWebSocketHandlerDecoratorFactoryにて追加ができる。

 

WebSocketHandlerについては、WebSocketHandlerを実現したクラスを使用したい。

だが...

How to chunk large message over stomp with spring boot - Stack Overflow

似たような悩み...情報はなし。頑張る。

 

public class CustomWebSocketHandlerDecoratorFactory implements WebSocketHandlerDecoratorFactory{
    
    private WebSocketHandler webSocketHandler;
    public CustomWebSocketHandlerDecoratorFactory(WebSocketHandler handler) {
        this.webSocketHandler = handler;
    }
    
    @Override
    public WebSocketHandler decorate(WebSocketHandler handler) {
        System.out.println("読み込まれています");
        return handler;
        }
    
    
}

こんなのを考えてみた。

CustomWebSocketHandlerDecoratorFactoryは呼び出した際にWebSocketHandlerを呼び出すのだから、コンストラクタを追加してカスタマイズしたWebSocketHandlerを渡せないかな~って。

 

デバッグすると、decorateはSubProtocolWebSocketHandlerになっている。

没ですね

 

the same handler or the handler wrapped with a subclass of WebSocketHandlerDecorator

とのため、WebSocketHandlerをWebSocketHandlerDecoratorを継承したクラスに渡して、afterConnectionClosedとafterConnectionEstablishedをOverrideして使用する。

 

動いたが、

サーバー側

Failed to parse TextMessage payload=[CONNECT
ac..], byteCount=56, last=true] in session h1rc4zi0. Sending STOMP ERROR to client.

 

No decoder for session id

 

クライアント側

  1. {command: 'ERROR', headers: {…}, body: ''}
    1. body: ""
    2. command: "ERROR"
    3. Prototype: Object

 

WebSocket、SockJs、STOMP、SubProtocol...ごっちゃになってますね。

下記キーワードと概念含めた理解を試みて、残り二日でクリアしたいです。

SubProtocolWebSocketHandler

StompSubProtocolHandler

StompSessionHandlerAdapter

java - Spring STOMP WebSocket session disconnect handler / reconnection handling - Stack Overflow

 

読む

では改めて。SpringドキュメントのWebSocket:26.4から読んでいきます。

STOMP can be used over any reliable 2-way streaming network protocol such as TCP and WebSocket. Although STOMP is a text-oriented protocol, the payload of messages can be either text or binary

 

STOMP is a frame based protocol whose frames are modeled on HTTP.

Command

SEND・・・サーバーの宛先にメッセージを送る

SUBSCRIBE・・・MESSAGEフレームとしてサーバーからメッセージを受信する。

Header

destination・・・SENDなら宛先、SUBSCRIBEなら受信元を示す。受信はMESSAGEフレームとして配信。正常にサブスクライブできない場合はERRORフレームを送信。

 

26.4.2

うーん、どうなんだろう。(自信ない)

 

26.4.3

 

  • Message — a message with headers and a payload.
  • MessageHandler — a contract for handling a message.
  • MessageChannel — a contract for sending a message enabling loose coupling between senders and receivers.
  • SubscribableChannel — extends MessageChannel and sends messages to registered MessageHandler subscribers.
  • ExecutorSubscribableChannel — a concrete implementation of SubscribableChannel that can deliver messages asynchronously via a thread pool.

 

MessageHandlerについては、SubProtocolWebSocketHandlerがサブクラスになっている。

 

  • SessionConnectEvent — published when a new STOMP CONNECT is received indicating the start of a new client session. The event contains the message representing the connect including the session id, user information (if any), and any custom headers the client may have sent. This is useful for tracking client sessions. Components subscribed to this event can wrap the contained message using SimpMessageHeaderAccessor or StompMessageHeaderAccessor.
  • SessionConnectedEvent — published shortly after a SessionConnectEvent when the broker has sent a STOMP CONNECTED frame in response to the CONNECT. At this point the STOMP session can be considered fully established.
  • SessionSubscribeEvent — published when a new STOMP SUBSCRIBE is received.
  • SessionUnsubscribeEvent — published when a new STOMP UNSUBSCRIBE is received.
  • SessionDisconnectEvent — published when a STOMP session ends. The DISCONNECT may have been sent from the client, or it may also be automatically generated when the WebSocket session is closed. In some cases this event may be published more than once per session. Components should be idempotent with regard to multiple disconnect events.

 

落ち込むレベルの話でした。ドキュメント読まずに一体...。

 

ともかく、SessionDisconnectEventを実装。System.out.println("OK")と入れて、Tomcat側で30s、ハートビートを3秒にして試しに実行すると、30sピッタリではないが、しっかり切れた際にOKと表示される。

 

ここで現れた問題点としては、

1.Sessionの復帰処理できない。

→remember-meの有効期限を長くすれば問題なくない?(ひとまず置いておく)

 

2.下記のようにPingPong毎にそれぞれInboundChannelとOutboundChannelがスレッドに生成される。


ThreadPoolTaskSchedulerの設定かと思われるので、後日、行う。

 

 

 

社内での競プロ勉強会

二年以上前にPaizaで問題を解いていて、そこから全くのブランク。

最近は再帰処理を学習していた。

頭の中で表現方法が繋がらず、時間がかかるうえに、ガサツな解き方が多い。

まずは、基本から解いていく。

 

033 - Not Too Bright(★2)

「2*2の領域」を想像した行列に適用して考える事。エッジケース。

 

B - AcCepted

分岐多くしすぎて、漏れていたケースの発見に遅れた。

 

A - Task Scheduling Problem

タスクの並び替えはOK。コストのコードに漏れがあった。

 

コードについてレビュー頂いた点やアドバイス

・Comparatorや自前の入れ替え処理を実装する必要はない→配列ならArray().sort、Listならsort()を使用しよう

・処理をした後に、ひとまとめにして出力するのはもったいない→都度、System.out.print()等で出力。Stringのrepeat()やjoin()などで処理を簡潔に。

ラムダ式はシンプルに記述できる事があるので身に着けておくと良い。

・実行時間がかかる場合もあるので、キューやデキュー含めたアルゴリズムを身に着けておくとよい。

・自分で便利だと思うライブラリを作ろう

 

その後に進めて気付いた事

・Collectionが便利だった記憶。(なんだっけ...)

・ScannerのnextとnextLineで挙動が違う事がある(後日調べる)

・String.substring()便利。

 

感想

いつも「このままでいいのだろうか」と考えています。

ダメです。即答です。

 

列挙していくと、

・ドキュメントを読んでいても、OutboundChannelとInboundChannelのスレッドの適切な実装が思い浮かばない。

理由:ハートビート以外にもメッセージ送受信に使われるスレッドなので、送ったら終了する仕組みが良いのか、他にも繋げておくメリットがあるのか見当がつかない。スレッド管理の知識やWebSocketの知識がない。

→ドキュメントの読み進めていない部分は進める。

 

ステータスコード1008といった通信失敗時における処理の設定ができていない

→今回のDisConnectEventで実装できないか、SubProtocolWebSocketHandler関係で何か処理できるクラスがないか探す

 

最後に、「そもそも、ここまで一人で時間かけて実装する意味があるのか」という事です。これについてはYESだと思います。

私のしている事など、建築している大工さんの前で砂遊びをしている子供のようなものでしょう。正直、ただ嘲笑ものでしかないのではと思う事があります。

ただ、それでいいのです。

また、是が非でも作りたい気持ちの反面、その機会があるのか疑う心もあります。

だから、安心することなく(できず)、毎日勉強しています。作っています。

それが楽しいのです。

私は、社内の方々も数年前からお世話になっているエンジニアの方々のお話も信じておりますので、期限を設けて、必要な努力をする。置かれている状況を俯瞰して、都度、違う視点から複数の意見を伺い、疑問を解消する。

思うように進まない事も一つの結果です。そのうえで更に時間をかける価値があるかどうかは何を目的にしているかによって決めればいい。