init
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package websocket.drawboard;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
import websocket.drawboard.DrawMessage.ParseException;
|
||||
import websocket.drawboard.wsmessages.StringWebsocketMessage;
|
||||
|
||||
|
||||
public final class DrawboardEndpoint extends Endpoint {
|
||||
|
||||
private static final Log log =
|
||||
LogFactory.getLog(DrawboardEndpoint.class);
|
||||
|
||||
|
||||
/**
|
||||
* Our room where players can join.
|
||||
*/
|
||||
private static volatile Room room = null;
|
||||
private static final Object roomLock = new Object();
|
||||
|
||||
public static Room getRoom(boolean create) {
|
||||
if (create) {
|
||||
if (room == null) {
|
||||
synchronized (roomLock) {
|
||||
if (room == null) {
|
||||
room = new Room();
|
||||
}
|
||||
}
|
||||
}
|
||||
return room;
|
||||
} else {
|
||||
return room;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The player that is associated with this Endpoint and the current room.
|
||||
* Note that this variable is only accessed from the Room Thread.<br><br>
|
||||
*
|
||||
* TODO: Currently, Tomcat uses an Endpoint instance once - however
|
||||
* the java doc of endpoint says:
|
||||
* "Each instance of a websocket endpoint is guaranteed not to be called by
|
||||
* more than one thread at a time per active connection."
|
||||
* This could mean that after calling onClose(), the instance
|
||||
* could be reused for another connection so onOpen() will get called
|
||||
* (possibly from another thread).<br>
|
||||
* If this is the case, we would need a variable holder for the variables
|
||||
* that are accessed by the Room thread, and read the reference to the holder
|
||||
* at the beginning of onOpen, onMessage, onClose methods to ensure the room
|
||||
* thread always gets the correct instance of the variable holder.
|
||||
*/
|
||||
private Room.Player player;
|
||||
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
// Set maximum messages size to 10.000 bytes.
|
||||
session.setMaxTextMessageBufferSize(10000);
|
||||
session.addMessageHandler(stringHandler);
|
||||
final Client client = new Client(session);
|
||||
|
||||
final Room room = getRoom(true);
|
||||
room.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
// Create a new Player and add it to the room.
|
||||
try {
|
||||
player = room.createAndAddPlayer(client);
|
||||
} catch (IllegalStateException ex) {
|
||||
// Probably the max. number of players has been
|
||||
// reached.
|
||||
client.sendMessage(new StringWebsocketMessage(
|
||||
"0" + ex.getLocalizedMessage()));
|
||||
// Close the connection.
|
||||
client.close();
|
||||
}
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Unexpected exception: " + ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
Room room = getRoom(false);
|
||||
if (room != null) {
|
||||
room.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Player can be null if it couldn't enter the room
|
||||
if (player != null) {
|
||||
// Remove this player from the room.
|
||||
player.removeFromRoom();
|
||||
|
||||
// Set player to null to prevent NPEs when onMessage events
|
||||
// are processed (from other threads) after onClose has been
|
||||
// called from different thread which closed the Websocket session.
|
||||
player = null;
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Unexpected exception: " + ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onError(Session session, Throwable t) {
|
||||
// Most likely cause is a user closing their browser. Check to see if
|
||||
// the root cause is EOF and if it is ignore it.
|
||||
// Protect against infinite loops.
|
||||
int count = 0;
|
||||
Throwable root = t;
|
||||
while (root.getCause() != null && count < 20) {
|
||||
root = root.getCause();
|
||||
count ++;
|
||||
}
|
||||
if (root instanceof EOFException) {
|
||||
// Assume this is triggered by the user closing their browser and
|
||||
// ignore it.
|
||||
} else if (!session.isOpen() && root instanceof IOException) {
|
||||
// IOException after close. Assume this is a variation of the user
|
||||
// closing their browser (or refreshing very quickly) and ignore it.
|
||||
} else {
|
||||
log.error("onError: " + t.toString(), t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final MessageHandler.Whole<String> stringHandler =
|
||||
new MessageHandler.Whole<String>() {
|
||||
|
||||
@Override
|
||||
public void onMessage(final String message) {
|
||||
// Invoke handling of the message in the room.
|
||||
room.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
// Currently, the only types of messages the client will send
|
||||
// are draw messages prefixed by a Message ID
|
||||
// (starting with char '1'), and pong messages (starting
|
||||
// with char '0').
|
||||
// Draw messages should look like this:
|
||||
// ID|type,colR,colB,colG,colA,thickness,x1,y1,x2,y2,lastInChain
|
||||
|
||||
boolean dontSwallowException = false;
|
||||
try {
|
||||
char messageType = message.charAt(0);
|
||||
String messageContent = message.substring(1);
|
||||
switch (messageType) {
|
||||
case '0':
|
||||
// Pong message.
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
case '1':
|
||||
// Draw message
|
||||
int indexOfChar = messageContent.indexOf('|');
|
||||
long msgId = Long.parseLong(
|
||||
messageContent.substring(0, indexOfChar));
|
||||
|
||||
DrawMessage msg = DrawMessage.parseFromString(
|
||||
messageContent.substring(indexOfChar + 1));
|
||||
|
||||
// Don't ignore RuntimeExceptions thrown by
|
||||
// this method
|
||||
// TODO: Find a better solution than this variable
|
||||
dontSwallowException = true;
|
||||
if (player != null) {
|
||||
player.handleDrawMessage(msg, msgId);
|
||||
}
|
||||
dontSwallowException = false;
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
// Client sent invalid data
|
||||
// Ignore, TODO: maybe close connection
|
||||
} catch (RuntimeException e) {
|
||||
// Client sent invalid data.
|
||||
// Ignore, TODO: maybe close connection
|
||||
if (dontSwallowException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Unexpected exception: " + ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user