java-ee
WebSockets API
サーチ…
備考
WebSocketは、単一のTCP接続を使用してクライアントとサーバー/エンドポイント間の通信を可能にするプロトコルです。
WebSocketはWebブラウザやWebサーバーに実装されるように設計されていますが、どのクライアントやサーバーアプリケーションでも使用できます。
このトピックでは、 JSR 356によって開発され、Java EE 7仕様に組み込まれたWebソケット用のJava APIについて説明します。
WebSocketコミュニケーションの作成
WebSocketは、単一のTCP接続でデュプレックス/双方向通信プロトコルを提供します。
- クライアントはWebSocket要求をリスンしているサーバーへの接続を開きます
- クライアントはURIを使用してサーバーに接続します。
- サーバは、複数のクライアントからの要求を聞くことができる。
サーバーエンドポイント
@ServerEndpoint
POJOに注釈を付けるだけで、WebSocketサーバーのエンティティポイントを作成できます。 @OnMessage
は受信メッセージを受け取るメソッドをデコレートします。 @OnOpen
は、ピアからの新しい接続が受信されたときに呼び出されるメソッドを@OnOpen
するために使用できます。同様に、 @OnClose
で注釈を付けられたメソッドは、接続が閉じられるときに呼び出されます。
@ServerEndpoint("/websocket")
public class WebSocketServerEndpoint
{
@OnOpen
public void open(Session session) {
System.out.println("a client connected");
}
@OnClose
public void close(Session session) {
System.out.println("a client disconnected");
}
@OnMessage
public void handleMessage(String message) {
System.out.println("received a message from a websocket client! " + message);
}
}
クライアント・ポイント
サーバーエンドポイントと同様に、POJOに@ClientEndpoint
注釈することでWebSocketクライアントエンドポイントを作成できます。
@ClientEndpoint
public class WebsocketClientEndpoint {
Session userSession = null;
// in our case i.e. "ws://localhost:8080/myApp/websocket"
public WebsocketClientEndpoint(URI endpointURI) {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, endpointURI);
}
@OnOpen
public void onOpen(Session userSession) {
System.out.println("opening websocket");
this.userSession = userSession;
}
@OnClose
public void onClose(Session userSession, CloseReason reason) {
System.out.println("closing websocket");
this.userSession = null;
}
@OnMessage
public void onMessage(String message) {
System.out.println("received message: "+ message);
}
public void sendMessage(String message) {
System.out.println("sending message: "+ message);
this.userSession.getAsyncRemote().sendText(message);
}
}
エンコーダとデコーダ:オブジェクト指向WebSocket
エンコーダとデコーダのおかげで、JSR 356はオブジェクト指向の通信モデルを提供します。
メッセージ定義
接続されたすべてのセッションに返送される前に、受信したすべてのメッセージをサーバーで変換する必要があるとします。
public abstract class AbstractMsg {
public abstract void transform();
}
次に、サーバーが2つのメッセージタイプ、テキストベースのメッセージと整数ベースのメッセージを管理しているとしましょう。
整数メッセージは、コンテンツ自体を増やします。
public class IntegerMsg extends AbstractMsg {
private Integer content;
public IntegerMsg(int content) {
this.content = content;
}
public Integer getContent() {
return content;
}
public void setContent(Integer content) {
this.content = content;
}
@Override
public void transform() {
this.content = this.content * this.content;
}
}
文字列メッセージの前にいくつかのテキストがあります:
public class StringMsg extends AbstractMsg {
private String content;
public StringMsg(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public void transform() {
this.content = "Someone said: " + this.content;
}
}
エンコーダとデコーダ
メッセージタイプごとに1つのエンコーダがあり、すべてのメッセージに1つのデコーダがあります。 DecoderがDecoder.XXX<Type>
実装する必要がある場合、エンコーダはEncoder.XXX<Type>
インタフェースを実装する必要があります。
エンコーディングはかなり簡単です。メッセージからencode
メソッドは、JSON形式のStringを出力する必要があります。ここにIntegerMsg
の例をIntegerMsg
ます。
public class IntegerMsgEncoder implements Encoder.Text<IntegerMsg> {
@Override
public String encode(IntegerMsg object) throws EncodeException {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("content", object.getContent());
JsonObject jsonObject = builder.build();
return jsonObject.toString();
}
@Override
public void init(EndpointConfig config) {
System.out.println("IntegerMsgEncoder initializing");
}
@Override
public void destroy() {
System.out.println("IntegerMsgEncoder closing");
}
}
StringMsg
クラスの同様のエンコーディング。明らかに、エンコーダは抽象クラスを介して因数分解することができます。
public class StringMsgEncoder implements Encoder.Text<StringMsg> {
@Override
public String encode(StringMsg object) throws EncodeException {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("content", object.getContent());
JsonObject jsonObject = builder.build();
return jsonObject.toString();
}
@Override
public void init(EndpointConfig config) {
System.out.println("StringMsgEncoder initializing");
}
@Override
public void destroy() {
System.out.println("StringMsgEncoder closing");
}
}
デコーダは、受信したメッセージがwillDecode
除外されたフォーマットに適合しているかどうかをチェックし、受信した生のメッセージをdecode
されたオブジェクトに変換するという2つのステップで進行decode
。
パブリッククラスMsgDecoderはDecoder.Text {
@Override
public AbstractMsg decode(String s) throws DecodeException {
// Thanks to willDecode(s), one knows that
// s is a valid JSON and has the attribute
// "content"
JsonObject json = Json.createReader(new StringReader(s)).readObject();
JsonValue contentValue = json.get("content");
// to know if it is a IntegerMsg or a StringMsg,
// contentValue type has to be checked:
switch (contentValue.getValueType()) {
case STRING:
String stringContent = json.getString("content");
return new StringMsg(stringContent);
case NUMBER:
Integer intContent = json.getInt("content");
return new IntegerMsg(intContent);
default:
return null;
}
}
@Override
public boolean willDecode(String s) {
// 1) Incoming message is a valid JSON object
JsonObject json;
try {
json = Json.createReader(new StringReader(s)).readObject();
}
catch (JsonParsingException e) {
// ...manage exception...
return false;
}
catch (JsonException e) {
// ...manage exception...
return false;
}
// 2) Incoming message has required attributes
boolean hasContent = json.containsKey("content");
// ... proceed to additional test ...
return hasContent;
}
@Override
public void init(EndpointConfig config) {
System.out.println("Decoding incoming message...");
}
@Override
public void destroy() {
System.out.println("Incoming message decoding finished");
}
}
ServerEndPoint
Server EndPointはWebSocket通信のように見えますが、主な違いは3つあります。
ServerEndPointアノテーションには
encoders
とdecoders
属性がありますメッセージは送信されませんで
sendText
はなくてsendObject
OnError注釈が使用されます。
willDecode
中にスローされたエラーがあれば、ここで処理され、エラー情報がクライアントに返されます@ServerEndpoint(value = "/ webSocketObjectEndPoint"、デコーダ= {MsgDecoder.class}、エンコーダ= {StringMsgEncoder.class、IntegerMsgEncoder.class})パブリッククラスServerEndPoint {
@OnOpen public void onOpen(Session session) { System.out.println("A session has joined"); } @OnClose public void onClose(Session session) { System.out.println("A session has left"); } @OnMessage public void onMessage(Session session, AbstractMsg message) { if (message instanceof IntegerMsg) { System.out.println("IntegerMsg received!"); } else if (message instanceof StringMsg) { System.out.println("StringMsg received!"); } message.transform(); sendMessageToAllParties(session, message); } @OnError public void onError(Session session, Throwable throwable) { session.getAsyncRemote().sendText(throwable.getLocalizedMessage()); } private void sendMessageToAllParties(Session session, AbstractMsg message) { session.getOpenSessions().forEach(s -> { s.getAsyncRemote().sendObject(message); }); }
}
私はかなり冗長であったので、視覚的な例が必要な方のための基本的なJavaScriptクライアントがあります。これはチャットのような例です。接続されているすべての当事者が回答を受け取ります。
<!DOCTYPE html>
<html>
<head>
<title>Websocket-object</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- start of BAD PRACTICE! all style and script must go into a
dedicated CSS / JavaScript file-->
<style>
body{
background: dimgray;
}
.container{
width: 100%;
display: flex;
}
.left-side{
width: 30%;
padding: 2%;
box-sizing: border-box;
margin: auto;
margin-top: 0;
background: antiquewhite;
}
.left-side table{
width: 100%;
border: 1px solid black;
margin: 5px;
}
.left-side table td{
padding: 2px;
width: 50%;
}
.left-side table input{
width: 100%;
box-sizing: border-box;
}
.right-side{
width: 70%;
background: floralwhite;
}
</style>
<script>
var ws = null;
window.onload = function () {
// replace the 'websocket-object' with the
// context root of your web application.
ws = new WebSocket("ws://localhost:8080/websocket-object/webSocketObjectEndPoint");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
};
function onOpen() {
printText("", "connected to server");
}
function onClose() {
printText("", "disconnected from server");
}
function onMessage(event) {
var msg = JSON.parse(event.data);
printText("server", JSON.stringify(msg.content));
}
function sendNumberMessage() {
var content = new Number(document.getElementById("inputNumber").value);
var json = {content: content};
ws.send(JSON.stringify(json));
printText("client", JSON.stringify(json));
}
function sendTextMessage() {
var content = document.getElementById("inputText").value;
var json = {content: content};
ws.send(JSON.stringify(json));
printText("client", JSON.stringify(json));
}
function printText(sender, text) {
var table = document.getElementById("outputTable");
var row = table.insertRow(1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
switch (sender) {
case "client":
row.style.color = "orange";
break;
case "server":
row.style.color = "green";
break;
default:
row.style.color = "powderblue";
}
cell1.innerHTML = new Date().toISOString();
cell2.innerHTML = sender;
cell3.innerHTML = text;
}
</script>
<!-- end of bad practice -->
</head>
<body>
<div class="container">
<div class="left-side">
<table>
<tr>
<td>Enter a text</td>
<td><input id="inputText" type="text" /></td>
</tr>
<tr>
<td>Send as text</td>
<td><input type="submit" value="Send" onclick="sendTextMessage();"/></td>
</tr>
</table>
<table>
<tr>
<td>Enter a number</td>
<td><input id="inputNumber" type="number" /></td>
</tr>
<tr>
<td>Send as number</td>
<td><input type="submit" value="Send" onclick="sendNumberMessage();"/></td>
</tr>
</table>
</div>
<div class="right-side">
<table id="outputTable">
<tr>
<th>Date</th>
<th>Sender</th>
<th>Message</th>
</tr>
</table>
</div>
</div>
</body>
</html>
コードは完成し、Payara 4.1でテストされました。例は純粋な標準です(外部ライブラリ/フレームワークなし)