SSEは聞いたことはあっても実装したことがなかったので、使う機会があるかはともかくやってみた。

基本的に書籍とWeb情報そのままという感じ。

メインクラス

非同期処理のために@EnableAsyncアノテーションを付加。

package com.example.seversenteventsample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SeverSentEventSampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(SeverSentEventSampleApplication.class, args);
	}
}

コントローラ

package com.example.seversenteventsample;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;

@Controller
public class TopController {

	private final GreetingMessageSender greetingMessageSender;

	public TopController(GreetingMessageSender greetingMessageSender) {
		this.greetingMessageSender = greetingMessageSender;
	}

	@RequestMapping(path = "/", method = RequestMethod.GET)
	public String index() {
		return "index";
	}

	@RequestMapping(path = "/greeting", method = RequestMethod.GET)
	public SseEmitter greeting() throws IOException, InterruptedException {
		final SseEmitter emitter = new SseEmitter();
		greetingMessageSender.send(emitter);
		return emitter;
	}

}

実装コンポーネント

package com.example.seversenteventsample;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Component
public class GreetingMessageSender {

	@Async
	public void send(SseEmitter emitter) throws IOException, InterruptedException {
		int i = 1;

		emitter.send(SseEmitter.event().id(String.valueOf(i++)).data("Good Morning !"));

		TimeUnit.SECONDS.sleep(1);

		emitter.send(SseEmitter.event().id(String.valueOf(i++)).data("Hello !"));

		TimeUnit.SECONDS.sleep(1);

		emitter.send(SseEmitter.event().id(String.valueOf(i)).data("Good night !"));

		TimeUnit.SECONDS.sleep(1);

		emitter.complete();
	}

}

テスト画面

サーバ側は3回メッセージを送って停止する作りだが、実際にはonerrorのイベントハンドラで止めないと延々繰り返されるので注意。実際に使う際は、サーバからの停止用カスタムイベントを受けて止めるなどの処理が必要かも知れない。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>Server Sent Eventsサンプル</title>
</head>
<body>
<h1>Server Send Eventsサンプル</h1>

<ul id="message"></ul>

<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script>
	$(function () {
		(function getGreetingMessage() {
			var messageUl = $('#message');
			if (window.EventSource) {
				var sse = new EventSource('/greeting');
				sse.onopen = function (e) {
					console.log('接続を開始しました。')
				};
				sse.onmessage = function (e) {
					console.log('event occured.');
					var newLi = '<li>' + 'id:' + e.lastEventId + ' data:' + e.data + '</li>';
					messageUl.append(newLi);
				};
				sse.onerror = function (e) {
					if (sse.readyState !== 1) {
						console.log('エラーが発生したため切断します。readyState=' + sse.readyState);
						sse.close();
					}
				};
			} else {
				alert('未対応のブラウザです。');
			}
		})();
	});
</script>
</body>
</html>

プロジェクトはこちら。

https://github.com/orimajp/springboot-study/tree/master/server-sent-event-sample

参考

サーバサイド

Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発(株式会社NTTデータ)|翔泳社の本

クライアントサイド