Tomcat 8.0でSSEを使用してJava(Server-Sent-Events)を試しました。ここに私が気づいたいくつかのものがあります。
サーブレットに自動的にリクエストを行うボタンをクリックします。サーブレットのGETメソッドが実行され、イベントストリームが返されます。完全なストリームが受信されると、ページは再び同じデータを再度受信する別のリクエストを自動的に行います!!!無限ループはありません!!!
サーバー上で実際に何が起こっていますか?通常のシナリオでは、Tomcatはすべてのリクエストを処理するスレッドを作成します。今何が起こっていますか?
イベントストリームが同じ接続/ブラウザセッションに一度だけ送信されることを保証する正しい方法は何ですか?
イベントストリームが閉じられ、サーバーでリソースのオーバーヘッドが発生しないようにするための正しい方法は何ですか?
GET要求とPOST要求を区別する方法。なぜGETを選択したのですか?
TomcatでSSEを使用するのは早すぎますか?パフォーマンスの問題はありますか?
好奇心の強い人のためのコードは次のとおりです。
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//content type must be set to text/event-stream
response.setContentType("text/event-stream");
//cache must be set to no-cache
response.setHeader("Cache-Control", "no-cache");
//encoding is set to UTF-8
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
for(int i=0; i<10; i++) {
System.out.println(i);
writer.write("data: "+ i +"\n\n");
writer.flush();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
writer.close();
}
}
ページ上のJavascript(ページには他に何もありません)、
<button onclick="start()">Start</button>
<script type="text/javascript">
function start() {
var eventSource = new EventSource("TestServlet");
eventSource.onmessage = function(event) {
console.log("data: "+event.data)
document.getElementById('foo').innerHTML = event.data;
};
}
</script>
CURLを使用してこれを試しました。そして、応答は一度だけでした。私はクロムを使用しているので、これはchormeの問題なのでしょうか?
編集:
私が学んだことと学んだことは私のブログに文書化されました- Server Sent Events
この行を変更
_writer.write("data: "+ i +"\n\n");
_
に
_writer.write("data: "+ i +"\r\n");
_
ところで、すべてのイベントが送信されるまでスレッドを保持するため、コードには重大なパフォーマンスの問題があります。代わりに非同期処理APIを使用してください。例えば.
_protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext actx = req.startAsync();
actx.setTimeout(30*1000);
//save actx and use it when we need sent data to the client.
}
_
その後、後でAsyncContextを使用できます
_//write some data to client when a certain event happens
actx.getResponse().getWriter().write("data: " + mydata + "\r\n");
actx.getResponse().getWriter().flush();
_
すべてのイベントが送信されたら、閉じることができます
_actx.complete();
_
更新1:
サーバーが応答を完了したときにブラウザーがサーバーに再接続しないようにするには、ブラウザーでイベントソースを閉じる必要があります。
_eventSource.close();
_
別の方法が役立つ場合があります。私たちは非常に大きな再試行時間を設定しましたが、私はそれを試していません。
_protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext actx = req.startAsync();
actx.getResponse().getWriter().write("retry: 36000000000\r\n"); // 10000 hours!
actx.getResponse().getWriter().flush();
//save actx and use it when we need sent data to the client.
}
_
更新2:
あなたの場合、Websocketの方が良いと思います。
更新3:(質問に答える)
- サーバー上で実際に何が起こっていますか?通常のシナリオでは、Tomcatはすべてのリクエストを処理するスレッドを作成します。今何が起こっていますか?
Tomcat 8.0.XでデフォルトであるNIOコネクタを使用する場合、処理サイクル全体で、リクエストに関するHTTP I/Oはスレッドを保持しません。 BIOを使用する場合、処理サイクル全体が完了するまでスレッドが保持されます。すべてのスレッドはスレッドプールからのものであり、Tomcatはリクエストごとにスレッドを作成しません。
- イベントストリームが同じ接続/ブラウザセッションに一度だけ送信されることを保証する正しい方法は何ですか?
ブラウザ側でeventSource.close()
を行うのが最善の選択です。
- イベントストリームが閉じられ、サーバーでリソースのオーバーヘッドが発生しないようにするための正しい方法は何ですか?
サーバー側でAsyncContext.complete()を呼び出すことを忘れないでください。
- GETリクエストとPOSTリクエストを区別する方法。なぜGETを選択したのですか?
ブラウザのEventSource APIはGETリクエストのみをサポートしますが、サーバー側ではそのような制限はありません。 SSEは主にサーバーからイベントデータを受信するために使用されます。イベントが発生した場合、ブラウザは時間内にそれを受信でき、ポーリングする新しいリクエストを作成する必要はありません。 SSEのWebSocket instreadを試してください。
- TomcatでSSEを使用するのは早すぎますか?パフォーマンスの問題はありますか?
NIOコネクタと非同期処理APIを使用する場合、パフォーマンスの問題はありません。 Tomcat NIOコネクタが成熟しているかどうかはわかりませんが、試してみないと何かがわからないでしょう。
最初に サーバー送信イベントによるストリーム更新 を読んで、テクノロジーの一般的な理解を深めることを強くお勧めします。次に、 例による非同期サーブレットを使用したサーバー送信イベント に従って、SSEをサーブレットテクノロジで具体的に使用する方法を確認します。