java-ee
De WebSockets API
Zoeken…
Opmerkingen
WebSocket is een protocol dat communicatie tussen de client en de server / eindpunt mogelijk maakt met behulp van een enkele TCP-verbinding.
WebSocket is ontworpen om te worden geïmplementeerd in webbrowsers en webservers, maar kan door elke client of servertoepassing worden gebruikt.
Dit onderwerp over de Java API's voor websockets die zijn ontwikkeld door JSR 356 en zijn opgenomen in de Java EE 7-specificaties.
Een WebSocket-communicatie maken
WebSocket biedt een duplex / bidirectioneel communicatieprotocol via een enkele TCP-verbinding.
- de client opent een verbinding met een server die luistert naar een WebSocket-aanvraag
- een client maakt verbinding met een server met behulp van een URI.
- Een server kan luisteren naar aanvragen van meerdere clients.
Server eindpunt
U kunt een WebSocket-serveringang maken door gewoon een POJO te annoteren met @ServerEndpoint
. @OnMessage
decoreert een methode die inkomende berichten ontvangt. @OnOpen
kan worden gebruikt om een methode te decoreren die moet worden aangeroepen wanneer een nieuwe verbinding van een peer wordt ontvangen. Op dezelfde manier wordt een methode geannoteerd met @OnClose
aangeroepen wanneer een verbinding wordt @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);
}
}
Client Enpoint
Net als het server-eindpunt kunt u een WebSocket-client-eindpunt maken door een POJO te annoteren met @ClientEndpoint
.
@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);
}
}
Encoders en decoder: Objectgeoriënteerde WebSockets
Dankzij encoders en decoders biedt de JSR 356 objectgerichte communicatiemodellen.
Definitie van berichten
Laten we aannemen dat alle ontvangen berichten door de server moeten worden getransformeerd voordat ze worden teruggestuurd naar alle verbonden sessies:
public abstract class AbstractMsg {
public abstract void transform();
}
Laten we nu aannemen dat de server twee berichttypen beheert: een tekstgebaseerd bericht en een geheel getal-bericht.
Geheel getal berichten vermenigvuldigen de inhoud zelf.
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;
}
}
Stringbericht plaatst tekst voor:
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;
}
}
Encoders en decoder
Er is één encoder per berichttype en één decoder voor alle berichten. Encoders moet werktuigen Encoder.XXX<Type>
-interface wanneer Decoder must werktuigen Decoder.XXX<Type>
.
Codering is vrij eenvoudig: vanuit een bericht moet de encode
een JSON-geformatteerde String uitvoeren. Hier is het voorbeeld voor 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");
}
}
Soortgelijke codering voor StringMsg
klasse. Het is duidelijk dat encoders kunnen worden ontbonden via abstracte klassen.
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");
}
}
Decoder verloopt in twee stappen: controleren of het ontvangen bericht in het uitgezonderde formaat past met willDecode
en het ontvangen onbewerkte bericht vervolgens transformeren in een object met decode
:
public class MsgDecoder implementeert 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
De Server EndPoint lijkt behoorlijk op de WebSocket-communicatie met drie belangrijke verschillen:
ServerEndPoint-annotatie heeft de kenmerken
encoders
endecoders
Berichten worden niet verzonden met
sendText
maar metsendObject
OnError-annotatie wordt gebruikt. Als er tijdens
willDecode
een fout iswillDecode
, wordt deze hier verwerkt en wordt foutinformatie teruggestuurd naar de client@ServerEndpoint (value = "/ webSocketObjectEndPoint", decoders = {MsgDecoder.class}, encoders = {StringMsgEncoder.class, IntegerMsgEncoder.class}) publieke klasse 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); }); }
}
Omdat ik vrij uitgebreid was, is hier een eenvoudige JavaScript-client voor degenen die een visueel voorbeeld willen hebben. Houd er rekening mee dat dit een chatachtig voorbeeld is: alle aangesloten partijen ontvangen het antwoord.
<!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>
Code is compleet en is getest onder Payara 4.1. Voorbeeld is pure standaard (geen externe bibliotheek / framework)