升级Tomcat版本 apache-tomcat-7.0.77
diff --git a/tomcat-uid/webapps/examples/websocket/chat.xhtml b/tomcat-uid/webapps/examples/websocket/chat.xhtml
new file mode 100644
index 0000000..5e31dab
--- /dev/null
+++ b/tomcat-uid/webapps/examples/websocket/chat.xhtml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title>Apache Tomcat WebSocket Examples: Chat</title>
+ <style type="text/css"><![CDATA[
+ input#chat {
+ width: 410px
+ }
+
+ #console-container {
+ width: 400px;
+ }
+
+ #console {
+ border: 1px solid #CCCCCC;
+ border-right-color: #999999;
+ border-bottom-color: #999999;
+ height: 170px;
+ overflow-y: scroll;
+ padding: 5px;
+ width: 100%;
+ }
+
+ #console p {
+ padding: 0;
+ margin: 0;
+ }
+ ]]></style>
+ <script type="application/javascript"><![CDATA[
+ var Chat = {};
+
+ Chat.socket = null;
+
+ Chat.connect = (function(host) {
+ if ('WebSocket' in window) {
+ Chat.socket = new WebSocket(host);
+ } else if ('MozWebSocket' in window) {
+ Chat.socket = new MozWebSocket(host);
+ } else {
+ Console.log('Error: WebSocket is not supported by this browser.');
+ return;
+ }
+
+ Chat.socket.onopen = function () {
+ Console.log('Info: WebSocket connection opened.');
+ document.getElementById('chat').onkeydown = function(event) {
+ if (event.keyCode == 13) {
+ Chat.sendMessage();
+ }
+ };
+ };
+
+ Chat.socket.onclose = function () {
+ document.getElementById('chat').onkeydown = null;
+ Console.log('Info: WebSocket closed.');
+ };
+
+ Chat.socket.onmessage = function (message) {
+ Console.log(message.data);
+ };
+ });
+
+ Chat.initialize = function() {
+ if (window.location.protocol == 'http:') {
+ Chat.connect('ws://' + window.location.host + '/examples/websocket/chat');
+ } else {
+ Chat.connect('wss://' + window.location.host + '/examples/websocket/chat');
+ }
+ };
+
+ Chat.sendMessage = (function() {
+ var message = document.getElementById('chat').value;
+ if (message != '') {
+ Chat.socket.send(message);
+ document.getElementById('chat').value = '';
+ }
+ });
+
+ var Console = {};
+
+ Console.log = (function(message) {
+ var console = document.getElementById('console');
+ var p = document.createElement('p');
+ p.style.wordWrap = 'break-word';
+ p.innerHTML = message;
+ console.appendChild(p);
+ while (console.childNodes.length > 25) {
+ console.removeChild(console.firstChild);
+ }
+ console.scrollTop = console.scrollHeight;
+ });
+
+ Chat.initialize();
+
+
+ document.addEventListener("DOMContentLoaded", function() {
+ // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+ var noscripts = document.getElementsByClassName("noscript");
+ for (var i = 0; i < noscripts.length; i++) {
+ noscripts[i].parentNode.removeChild(noscripts[i]);
+ }
+ }, false);
+
+ ]]></script>
+</head>
+<body>
+<div class="noscript"><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+ Javascript and reload this page!</h2></div>
+<div>
+ <p>
+ <input type="text" placeholder="type and press enter to chat" id="chat" />
+ </p>
+ <div id="console-container">
+ <div id="console"/>
+ </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tomcat-uid/webapps/examples/websocket/drawboard.xhtml b/tomcat-uid/webapps/examples/websocket/drawboard.xhtml
new file mode 100644
index 0000000..342bef4
--- /dev/null
+++ b/tomcat-uid/webapps/examples/websocket/drawboard.xhtml
@@ -0,0 +1,897 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title>Apache Tomcat WebSocket Examples: Drawboard</title>
+ <style type="text/css"><![CDATA[
+
+ body {
+ font-family: Arial, sans-serif;
+ font-size: 11pt;
+ background-color: #eeeeea;
+ padding: 10px;
+ }
+
+ #console-container {
+ float: left;
+ background-color: #fff;
+ width: 250px;
+ }
+
+ #console {
+ font-size: 10pt;
+ height: 600px;
+ overflow-y: scroll;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+
+ #console p {
+ padding: 0;
+ margin: 0;
+ }
+
+ #drawContainer {
+ float: left;
+ display: none;
+ margin-right: 25px;
+ }
+
+ #drawContainer canvas {
+ display: block;
+ -ms-touch-action: none;
+ touch-action: none; /* Disable touch behaviors, like pan and zoom */
+ cursor: crosshair;
+ }
+
+ #labelContainer {
+ margin-bottom: 15px;
+ }
+
+ #drawContainer, #console-container {
+ box-shadow: 0px 0px 8px 3px #bbb;
+ border: 1px solid #CCCCCC;
+ }
+
+ ]]></style>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ (function() {
+
+ document.addEventListener("DOMContentLoaded", function() {
+ // Remove elements with "noscript" class - <noscript> is not
+ // allowed in XHTML
+ var noscripts = document.getElementsByClassName("noscript");
+ for (var i = 0; i < noscripts.length; i++) {
+ noscripts[i].parentNode.removeChild(noscripts[i]);
+ }
+
+ // Add script for expand content.
+ var expandElements = document.getElementsByClassName("expand");
+ for (var ixx = 0; ixx < expandElements.length; ixx++) {
+ (function(el) {
+ var expandContent = document.getElementById(el.getAttribute("data-content-id"));
+ expandContent.style.display = "none";
+ var arrow = document.createTextNode("◢ ");
+ var arrowSpan = document.createElement("span");
+ arrowSpan.appendChild(arrow);
+
+ var link = document.createElement("a");
+ link.setAttribute("href", "#!");
+ while (el.firstChild != null) {
+ link.appendChild(el.removeChild(el.firstChild));
+ }
+ el.appendChild(arrowSpan);
+ el.appendChild(link);
+
+ var textSpan = document.createElement("span");
+ textSpan.setAttribute("style", "font-weight: normal;");
+ textSpan.appendChild(document.createTextNode(" (click to expand)"));
+ el.appendChild(textSpan);
+
+
+ var visible = true;
+
+ var switchExpand = function() {
+ visible = !visible;
+ expandContent.style.display = visible ? "block" : "none";
+ arrowSpan.style.color = visible ? "#000" : "#888";
+ return false;
+ };
+
+ link.onclick = switchExpand;
+ switchExpand();
+
+ })(expandElements[ixx]);
+ }
+
+
+ var Console = {};
+
+ Console.log = (function() {
+ var consoleContainer =
+ document.getElementById("console-container");
+ var console = document.createElement("div");
+ console.setAttribute("id", "console");
+ consoleContainer.appendChild(console);
+
+ return function(message) {
+ var p = document.createElement('p');
+ p.style.wordWrap = "break-word";
+ p.appendChild(document.createTextNode(message));
+ console.appendChild(p);
+ while (console.childNodes.length > 25) {
+ console.removeChild(console.firstChild);
+ }
+ console.scrollTop = console.scrollHeight;
+ }
+ })();
+
+
+ function Room(drawContainer) {
+
+ /* A pausable event forwarder that can be used to pause and
+ * resume handling of events (e.g. when we need to wait
+ * for a Image's load event before we can process further
+ * WebSocket messages).
+ * The object's callFunction(func) should be called from an
+ * event handler and give the function to handle the event as
+ * argument.
+ * Call pauseProcessing() to suspend event forwarding and
+ * resumeProcessing() to resume it.
+ */
+ function PausableEventForwarder() {
+
+ var pauseProcessing = false;
+ // Queue for buffering functions to be called.
+ var functionQueue = [];
+
+ this.callFunction = function(func) {
+ // If message processing is paused, we push it
+ // into the queue - otherwise we process it directly.
+ if (pauseProcessing) {
+ functionQueue.push(func);
+ } else {
+ func();
+ }
+ };
+
+ this.pauseProcessing = function() {
+ pauseProcessing = true;
+ };
+
+ this.resumeProcessing = function() {
+ pauseProcessing = false;
+
+ // Process all queued functions until some handler calls
+ // pauseProcessing() again.
+ while (functionQueue.length > 0 && !pauseProcessing) {
+ var func = functionQueue.pop();
+ func();
+ }
+ };
+ }
+
+ // The WebSocket object.
+ var socket;
+ // ID of the timer which sends ping messages.
+ var pingTimerId;
+
+ var isStarted = false;
+ var playerCount = 0;
+
+ // An array of PathIdContainer objects that the server
+ // did not yet handle.
+ // They are ordered by id (ascending).
+ var pathsNotHandled = [];
+
+ var nextMsgId = 1;
+
+ var canvasDisplay = document.createElement("canvas");
+ var canvasBackground = document.createElement("canvas");
+ var canvasServerImage = document.createElement("canvas");
+ var canvasArray = [canvasDisplay, canvasBackground,
+ canvasServerImage];
+ canvasDisplay.addEventListener("mousedown", function(e) {
+ // Prevent default mouse event to prevent browsers from marking text
+ // (and Chrome from displaying the "text" cursor).
+ e.preventDefault();
+ }, false);
+
+ var labelPlayerCount = document.createTextNode("0");
+ var optionContainer = document.createElement("div");
+
+
+ var canvasDisplayCtx = canvasDisplay.getContext("2d");
+ var canvasBackgroundCtx = canvasBackground.getContext("2d");
+ var canvasServerImageCtx = canvasServerImage.getContext("2d");
+ var canvasMouseMoveHandler;
+ var canvasMouseDownHandler;
+
+ var isActive = false;
+ var mouseInWindow = false;
+ var mouseDown = false;
+ var currentMouseX = 0, currentMouseY = 0;
+ var currentPreviewPath = null;
+
+ var availableColors = [];
+ var currentColorIndex;
+ var colorContainers;
+ var previewTransparency = 0.65;
+
+ var availableThicknesses = [2, 3, 6, 10, 16, 28, 50];
+ var currentThicknessIndex;
+ var thicknessContainers;
+
+ var availableDrawTypes = [
+ { name: "Brush", id: 1, continuous: true },
+ { name: "Line", id: 2, continuous: false },
+ { name: "Rectangle", id: 3, continuous: false },
+ { name: "Ellipse", id: 4, continuous: false }
+ ];
+ var currentDrawTypeIndex;
+ var drawTypeContainers;
+
+
+ var labelContainer = document.getElementById("labelContainer");
+ var placeholder = document.createElement("div");
+ placeholder.appendChild(document.createTextNode("Loading... "));
+ var progressElem = document.createElement("progress");
+ placeholder.appendChild(progressElem);
+
+ labelContainer.appendChild(placeholder);
+
+ function rgb(color) {
+ return "rgba(" + color[0] + "," + color[1] + ","
+ + color[2] + "," + color[3] + ")";
+ }
+
+ function PathIdContainer(path, id) {
+ this.path = path;
+ this.id = id;
+ }
+
+ function Path(type, color, thickness, x1, y1, x2, y2, lastInChain) {
+ this.type = type;
+ this.color = color;
+ this.thickness = thickness;
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ this.lastInChain = lastInChain;
+
+ function ellipse(ctx, x, y, w, h) {
+ /* Drawing a ellipse cannot be done directly in a
+ * CanvasRenderingContext2D - we need to use drawArc()
+ * in conjunction with scaling the context so that we
+ * get the needed proportion.
+ */
+ ctx.save();
+
+ // Translate and scale the context so that we can draw
+ // an arc at (0, 0) with a radius of 1.
+ ctx.translate(x + w / 2, y + h / 2);
+ ctx.scale(w / 2, h / 2);
+
+ ctx.beginPath();
+ ctx.arc(0, 0, 1, 0, Math.PI * 2, false);
+
+ ctx.restore();
+ }
+
+ this.draw = function(ctx) {
+ ctx.beginPath();
+ ctx.lineCap = "round";
+ ctx.lineWidth = thickness;
+ var style = rgb(color);
+ ctx.strokeStyle = style;
+
+ if (x1 == x2 && y1 == y2) {
+ // Always draw as arc to meet the behavior
+ // in Java2D.
+ ctx.fillStyle = style;
+ ctx.arc(x1, y1, thickness / 2.0, 0,
+ Math.PI * 2.0, false);
+ ctx.fill();
+ } else {
+ if (type == 1 || type == 2) {
+ // Draw a line.
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+ } else if (type == 3) {
+ // Draw a rectangle.
+ if (x1 == x2 || y1 == y2) {
+ // Draw as line
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+ } else {
+ ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
+ }
+ } else if (type == 4) {
+ // Draw a ellipse.
+ ellipse(ctx, x1, y1, x2 - x1, y2 - y1);
+ ctx.closePath();
+ ctx.stroke();
+ }
+ }
+ };
+ }
+
+
+ function connect() {
+ var host = (window.location.protocol == "https:"
+ ? "wss://" : "ws://") + window.location.host
+ + "/examples/websocket/drawboard";
+ socket = new WebSocket(host);
+
+ /* Use a pausable event forwarder.
+ * This is needed when we load an Image object with data
+ * from a previous message, because we must wait until the
+ * Image's load event it raised before we can use it (and
+ * in the meantime the socket.message event could be
+ * raised).
+ * Therefore we need this pausable event handler to handle
+ * e.g. socket.onmessage and socket.onclose.
+ */
+ var eventForwarder = new PausableEventForwarder();
+
+ socket.onopen = function () {
+ // Socket has opened. Now wait for the server to
+ // send us the initial packet.
+ Console.log("WebSocket connection opened.");
+
+ // Set up a timer for pong messages.
+ pingTimerId = window.setInterval(function() {
+ socket.send("0");
+ }, 30000);
+ };
+
+ socket.onclose = function () {
+ eventForwarder.callFunction(function() {
+ Console.log("WebSocket connection closed.");
+ disableControls();
+
+ // Disable pong timer.
+ window.clearInterval(pingTimerId);
+ });
+ };
+
+ // Handles an incoming Websocket message.
+ var handleOnMessage = function(message) {
+
+ // Split joined message and process them
+ // individually.
+ var messages = message.data.split(";");
+ for (var msgArrIdx = 0; msgArrIdx < messages.length;
+ msgArrIdx++) {
+ var msg = messages[msgArrIdx];
+ var type = msg.substring(0, 1);
+
+ if (type == "0") {
+ // Error message.
+ var error = msg.substring(1);
+ // Log it to the console and show an alert.
+ Console.log("Error: " + error);
+ alert(error);
+
+ } else {
+ if (!isStarted) {
+ if (type == "2") {
+ // Initial message. It contains the
+ // number of players.
+ // After this message we will receive
+ // a binary message containing the current
+ // room image as PNG.
+ playerCount = parseInt(msg.substring(1));
+
+ refreshPlayerCount();
+
+ // The next message will be a binary
+ // message containing the room images
+ // as PNG. Therefore we temporarily swap
+ // the message handler.
+ var originalHandler = handleOnMessage;
+ handleOnMessage = function(message) {
+ // First, we restore the original handler.
+ handleOnMessage = originalHandler;
+
+ // Read the image.
+ var blob = message.data;
+ // Create new blob with correct MIME type.
+ blob = new Blob([blob], {type : "image/png"});
+
+ var url = URL.createObjectURL(blob);
+
+ var img = new Image();
+
+ // We must wait until the onload event is
+ // raised until we can draw the image onto
+ // the canvas.
+ // Therefore we need to pause the event
+ // forwarder until the image is loaded.
+ eventForwarder.pauseProcessing();
+
+ img.onload = function() {
+
+ // Release the object URL.
+ URL.revokeObjectURL(url);
+
+ // Set the canvases to the correct size.
+ for (var i = 0; i < canvasArray.length; i++) {
+ canvasArray[i].width = img.width;
+ canvasArray[i].height = img.height;
+ }
+
+ // Now draw the image on the last canvas.
+ canvasServerImageCtx.clearRect(0, 0,
+ canvasServerImage.width,
+ canvasServerImage.height);
+ canvasServerImageCtx.drawImage(img, 0, 0);
+
+ // Draw it on the background canvas.
+ canvasBackgroundCtx.drawImage(canvasServerImage,
+ 0, 0);
+
+ isStarted = true;
+ startControls();
+
+ // Refresh the display canvas.
+ refreshDisplayCanvas();
+
+
+ // Finally, resume the event forwarder.
+ eventForwarder.resumeProcessing();
+ };
+
+ img.src = url;
+ };
+ }
+ } else {
+ if (type == "3") {
+ // The number of players in this room changed.
+ var playerAdded = msg.substring(1) == "+";
+ playerCount += playerAdded ? 1 : -1;
+ refreshPlayerCount();
+
+ Console.log("Player " + (playerAdded
+ ? "joined." : "left."));
+
+ } else if (type == "1") {
+ // We received a new DrawMessage.
+ var maxLastHandledId = -1;
+ var drawMessages = msg.substring(1).split("|");
+ for (var i = 0; i < drawMessages.length; i++) {
+ var elements = drawMessages[i].split(",");
+ var lastHandledId = parseInt(elements[0]);
+ maxLastHandledId = Math.max(maxLastHandledId,
+ lastHandledId);
+
+ var path = new Path(
+ parseInt(elements[1]),
+ [parseInt(elements[2]),
+ parseInt(elements[3]),
+ parseInt(elements[4]),
+ parseInt(elements[5]) / 255.0],
+ parseFloat(elements[6]),
+ parseFloat(elements[7]),
+ parseFloat(elements[8]),
+ parseFloat(elements[9]),
+ parseFloat(elements[10]),
+ elements[11] != "0");
+
+ // Draw the path onto the last canvas.
+ path.draw(canvasServerImageCtx);
+ }
+
+ // Draw the last canvas onto the background one.
+ canvasBackgroundCtx.drawImage(canvasServerImage,
+ 0, 0);
+
+ // Now go through the pathsNotHandled array and
+ // remove the paths that were already handled by
+ // the server.
+ while (pathsNotHandled.length > 0
+ && pathsNotHandled[0].id <= maxLastHandledId)
+ pathsNotHandled.shift();
+
+ // Now me must draw the remaining paths onto
+ // the background canvas.
+ for (var i = 0; i < pathsNotHandled.length; i++) {
+ pathsNotHandled[i].path.draw(canvasBackgroundCtx);
+ }
+
+ refreshDisplayCanvas();
+ }
+ }
+ }
+ }
+ };
+
+ socket.onmessage = function(message) {
+ eventForwarder.callFunction(function() {
+ handleOnMessage(message);
+ });
+ };
+
+ }
+
+
+ function refreshPlayerCount() {
+ labelPlayerCount.nodeValue = String(playerCount);
+ }
+
+ function refreshDisplayCanvas() {
+ if (!isActive) { // Don't draw a curser when not active.
+ return;
+ }
+
+ canvasDisplayCtx.drawImage(canvasBackground, 0, 0);
+ if (currentPreviewPath != null) {
+ // Draw the preview path.
+ currentPreviewPath.draw(canvasDisplayCtx);
+
+ } else if (mouseInWindow && !mouseDown) {
+ canvasDisplayCtx.beginPath();
+ var color = availableColors[currentColorIndex].slice(0);
+ color[3] = previewTransparency;
+ canvasDisplayCtx.fillStyle = rgb(color);
+
+ canvasDisplayCtx.arc(currentMouseX, currentMouseY,
+ availableThicknesses[currentThicknessIndex] / 2,
+ 0, Math.PI * 2.0, true);
+ canvasDisplayCtx.fill();
+ }
+
+ }
+
+ function startControls() {
+ isActive = true;
+
+ labelContainer.removeChild(placeholder);
+ placeholder = undefined;
+
+ labelContainer.appendChild(
+ document.createTextNode("Number of Players: "));
+ labelContainer.appendChild(labelPlayerCount);
+
+
+ drawContainer.style.display = "block";
+ drawContainer.appendChild(canvasDisplay);
+
+ drawContainer.appendChild(optionContainer);
+
+ canvasMouseDownHandler = function(e) {
+ if (e.button == 0) {
+ currentMouseX = e.pageX - canvasDisplay.offsetLeft;
+ currentMouseY = e.pageY - canvasDisplay.offsetTop;
+
+ mouseDown = true;
+ canvasMouseMoveHandler(e);
+
+ } else if (mouseDown) {
+ // Cancel drawing.
+ mouseDown = false;
+ currentPreviewPath = null;
+
+ currentMouseX = e.pageX - canvasDisplay.offsetLeft;
+ currentMouseY = e.pageY - canvasDisplay.offsetTop;
+
+ refreshDisplayCanvas();
+ }
+ };
+ canvasDisplay.addEventListener("mousedown", canvasMouseDownHandler, false);
+
+ canvasMouseMoveHandler = function(e) {
+ var mouseX = e.pageX - canvasDisplay.offsetLeft;
+ var mouseY = e.pageY - canvasDisplay.offsetTop;
+
+ if (mouseDown) {
+ var drawType = availableDrawTypes[currentDrawTypeIndex];
+
+ if (drawType.continuous) {
+
+ var path = new Path(drawType.id,
+ availableColors[currentColorIndex],
+ availableThicknesses[currentThicknessIndex],
+ currentMouseX, currentMouseY, mouseX,
+ mouseY, false);
+ // Draw it on the background canvas.
+ path.draw(canvasBackgroundCtx);
+
+ // Send it to the sever.
+ pushPath(path);
+
+ // Refresh old coordinates
+ currentMouseX = mouseX;
+ currentMouseY = mouseY;
+
+ } else {
+ // Create a new preview path.
+ var color = availableColors[currentColorIndex].slice(0);
+ color[3] = previewTransparency;
+ currentPreviewPath = new Path(drawType.id,
+ color,
+ availableThicknesses[currentThicknessIndex],
+ currentMouseX, currentMouseY, mouseX,
+ mouseY, false);
+ }
+
+ refreshDisplayCanvas();
+ } else {
+ currentMouseX = mouseX;
+ currentMouseY = mouseY;
+
+ if (mouseInWindow) {
+ refreshDisplayCanvas();
+ }
+ }
+
+ };
+ document.addEventListener("mousemove", canvasMouseMoveHandler, false);
+
+ document.addEventListener("mouseup", function(e) {
+ if (e.button == 0) {
+ if (mouseDown) {
+ mouseDown = false;
+ currentPreviewPath = null;
+
+ var mouseX = e.pageX - canvasDisplay.offsetLeft;
+ var mouseY = e.pageY - canvasDisplay.offsetTop;
+ var drawType = availableDrawTypes[currentDrawTypeIndex];
+
+ var path = new Path(drawType.id, availableColors[currentColorIndex],
+ availableThicknesses[currentThicknessIndex],
+ currentMouseX, currentMouseY, mouseX,
+ mouseY, true);
+ // Draw it on the background canvas.
+ path.draw(canvasBackgroundCtx);
+
+ // Send it to the sever.
+ pushPath(path);
+
+ // Refresh old coordinates
+ currentMouseX = mouseX;
+ currentMouseY = mouseY;
+
+ refreshDisplayCanvas();
+ }
+ }
+ }, false);
+
+ canvasDisplay.addEventListener("mouseout", function(e) {
+ mouseInWindow = false;
+ refreshDisplayCanvas();
+ }, false);
+
+ canvasDisplay.addEventListener("mousemove", function(e) {
+ if (!mouseInWindow) {
+ mouseInWindow = true;
+ refreshDisplayCanvas();
+ }
+ }, false);
+
+
+ // Create color and thickness controls.
+ var colorContainersBox = document.createElement("div");
+ colorContainersBox.setAttribute("style",
+ "margin: 4px; border: 1px solid #bbb; border-radius: 3px;");
+ optionContainer.appendChild(colorContainersBox);
+
+ colorContainers = new Array(3 * 3 * 3);
+ for (var i = 0; i < colorContainers.length; i++) {
+ var colorContainer = colorContainers[i] =
+ document.createElement("div");
+ var color = availableColors[i] =
+ [
+ Math.floor((i % 3) * 255 / 2),
+ Math.floor((Math.floor(i / 3) % 3) * 255 / 2),
+ Math.floor((Math.floor(i / (3 * 3)) % 3) * 255 / 2),
+ 1.0
+ ];
+ colorContainer.setAttribute("style",
+ "margin: 3px; width: 18px; height: 18px; "
+ + "float: left; background-color: " + rgb(color));
+ colorContainer.style.border = '2px solid #000';
+ colorContainer.addEventListener("mousedown", (function(ix) {
+ return function() {
+ setColor(ix);
+ };
+ })(i), false);
+
+ colorContainersBox.appendChild(colorContainer);
+ }
+
+ var divClearLeft = document.createElement("div");
+ divClearLeft.setAttribute("style", "clear: left;");
+ colorContainersBox.appendChild(divClearLeft);
+
+
+ var drawTypeContainersBox = document.createElement("div");
+ drawTypeContainersBox.setAttribute("style",
+ "float: right; margin-right: 3px; margin-top: 1px;");
+ optionContainer.appendChild(drawTypeContainersBox);
+
+ drawTypeContainers = new Array(availableDrawTypes.length);
+ for (var i = 0; i < drawTypeContainers.length; i++) {
+ var drawTypeContainer = drawTypeContainers[i] =
+ document.createElement("div");
+ drawTypeContainer.setAttribute("style",
+ "text-align: center; margin: 3px; padding: 0 3px;"
+ + "height: 18px; float: left;");
+ drawTypeContainer.style.border = "2px solid #000";
+ drawTypeContainer.appendChild(document.createTextNode(
+ String(availableDrawTypes[i].name)));
+ drawTypeContainer.addEventListener("mousedown", (function(ix) {
+ return function() {
+ setDrawType(ix);
+ };
+ })(i), false);
+
+ drawTypeContainersBox.appendChild(drawTypeContainer);
+ }
+
+
+ var thicknessContainersBox = document.createElement("div");
+ thicknessContainersBox.setAttribute("style",
+ "margin: 3px; border: 1px solid #bbb; border-radius: 3px;");
+ optionContainer.appendChild(thicknessContainersBox);
+
+ thicknessContainers = new Array(availableThicknesses.length);
+ for (var i = 0; i < thicknessContainers.length; i++) {
+ var thicknessContainer = thicknessContainers[i] =
+ document.createElement("div");
+ thicknessContainer.setAttribute("style",
+ "text-align: center; margin: 3px; width: 18px; "
+ + "height: 18px; float: left;");
+ thicknessContainer.style.border = "2px solid #000";
+ thicknessContainer.appendChild(document.createTextNode(
+ String(availableThicknesses[i])));
+ thicknessContainer.addEventListener("mousedown", (function(ix) {
+ return function() {
+ setThickness(ix);
+ };
+ })(i), false);
+
+ thicknessContainersBox.appendChild(thicknessContainer);
+ }
+
+
+ divClearLeft = document.createElement("div");
+ divClearLeft.setAttribute("style", "clear: left;");
+ thicknessContainersBox.appendChild(divClearLeft);
+
+
+ setColor(0);
+ setThickness(0);
+ setDrawType(0);
+
+ }
+
+ function disableControls() {
+ document.removeEventListener("mousedown", canvasMouseDownHandler);
+ document.removeEventListener("mousemove", canvasMouseMoveHandler);
+ mouseInWindow = false;
+ refreshDisplayCanvas();
+
+ isActive = false;
+ }
+
+ function pushPath(path) {
+
+ // Push it into the pathsNotHandled array.
+ var container = new PathIdContainer(path, nextMsgId++);
+ pathsNotHandled.push(container);
+
+ // Send the path to the server.
+ var message = container.id + "|" + path.type + ","
+ + path.color[0] + "," + path.color[1] + ","
+ + path.color[2] + ","
+ + Math.round(path.color[3] * 255.0) + ","
+ + path.thickness + "," + path.x1 + ","
+ + path.y1 + "," + path.x2 + "," + path.y2 + ","
+ + (path.lastInChain ? "1" : "0");
+
+ socket.send("1" + message);
+ }
+
+ function setThickness(thicknessIndex) {
+ if (typeof currentThicknessIndex !== "undefined")
+ thicknessContainers[currentThicknessIndex]
+ .style.borderColor = "#000";
+ currentThicknessIndex = thicknessIndex;
+ thicknessContainers[currentThicknessIndex]
+ .style.borderColor = "#d08";
+ }
+
+ function setColor(colorIndex) {
+ if (typeof currentColorIndex !== "undefined")
+ colorContainers[currentColorIndex]
+ .style.borderColor = "#000";
+ currentColorIndex = colorIndex;
+ colorContainers[currentColorIndex]
+ .style.borderColor = "#d08";
+ }
+
+ function setDrawType(drawTypeIndex) {
+ if (typeof currentDrawTypeIndex !== "undefined")
+ drawTypeContainers[currentDrawTypeIndex]
+ .style.borderColor = "#000";
+ currentDrawTypeIndex = drawTypeIndex;
+ drawTypeContainers[currentDrawTypeIndex]
+ .style.borderColor = "#d08";
+ }
+
+
+ connect();
+
+ }
+
+
+ // Initialize the room
+ var room = new Room(document.getElementById("drawContainer"));
+
+
+ }, false);
+
+ })();
+ ]]></script>
+</head>
+<body>
+ <div class="noscript"><div style="color: #ff0000; font-size: 16pt;">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+ Javascript and reload this page!</div></div>
+ <div id="labelContainer"/>
+ <div id="drawContainer"/>
+ <div id="console-container"/>
+ <div style="clear: left;"/>
+
+ <h1 class="expand" data-content-id="expandContent" style="font-size: 1.3em;"
+ >About Drawboard WebSocket Example</h1>
+ <div id="expandContent">
+ <p>
+ This drawboard is a page where you can draw with your mouse or touch input
+ (using different colors) and everybody else which has the page open will
+ <em>immediately</em> see what you are drawing.<br/>
+ If someone opens the page later, they will get the current room image (so they
+ can see what was already drawn by other people).
+ </p>
+ <p>
+ It uses asynchronous sending of messages so that it doesn't need separate threads
+ for each client to send messages (this needs NIO or APR connector to be used).<br/>
+ Each "Room" (where the drawing happens) uses a ReentrantLock to synchronize access
+ (currently, only a single Room is implemented).
+ </p>
+ <p>
+ When you open the page, first you will receive a binary websocket message containing
+ the current room image as PNG image. After that, you will receive string messages
+ that contain the drawing actions (line from x1,y1 to x2,y2).<br/>
+ <small>Note that it currently only uses simple string messages instead of JSON because
+ I did not want to introduce a dependency on a JSON lib.</small>
+ </p>
+ <p>
+ It uses synchronization mechanisms to ensure that the final image will look the same
+ for every user, regardless of what their network latency/speed is – e.g. if two user
+ draw at the same time on the same place, the server will decide which line was the
+ first one, and that will be reflected on every client.
+ </p>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tomcat-uid/webapps/examples/websocket/echo.xhtml b/tomcat-uid/webapps/examples/websocket/echo.xhtml
new file mode 100644
index 0000000..6153bff
--- /dev/null
+++ b/tomcat-uid/webapps/examples/websocket/echo.xhtml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title>Apache Tomcat WebSocket Examples: Echo</title>
+ <style type="text/css"><![CDATA[
+ #connect-container {
+ float: left;
+ width: 400px
+ }
+
+ #connect-container div {
+ padding: 5px;
+ }
+
+ #console-container {
+ float: left;
+ margin-left: 15px;
+ width: 400px;
+ }
+
+ #console {
+ border: 1px solid #CCCCCC;
+ border-right-color: #999999;
+ border-bottom-color: #999999;
+ height: 170px;
+ overflow-y: scroll;
+ padding: 5px;
+ width: 100%;
+ }
+
+ #console p {
+ padding: 0;
+ margin: 0;
+ }
+ ]]></style>
+ <script type="application/javascript"><![CDATA[
+ var ws = null;
+
+ function setConnected(connected) {
+ document.getElementById('connect').disabled = connected;
+ document.getElementById('disconnect').disabled = !connected;
+ document.getElementById('echo').disabled = !connected;
+ }
+
+ function connect() {
+ var target = document.getElementById('target').value;
+ if (target == '') {
+ alert('Please select server side connection implementation.');
+ return;
+ }
+ if ('WebSocket' in window) {
+ ws = new WebSocket(target);
+ } else if ('MozWebSocket' in window) {
+ ws = new MozWebSocket(target);
+ } else {
+ alert('WebSocket is not supported by this browser.');
+ return;
+ }
+ ws.onopen = function () {
+ setConnected(true);
+ log('Info: WebSocket connection opened.');
+ };
+ ws.onmessage = function (event) {
+ log('Received: ' + event.data);
+ };
+ ws.onclose = function (event) {
+ setConnected(false);
+ log('Info: WebSocket connection closed, Code: ' + event.code + (event.reason == "" ? "" : ", Reason: " + event.reason));
+ };
+ }
+
+ function disconnect() {
+ if (ws != null) {
+ ws.close();
+ ws = null;
+ }
+ setConnected(false);
+ }
+
+ function echo() {
+ if (ws != null) {
+ var message = document.getElementById('message').value;
+ log('Sent: ' + message);
+ ws.send(message);
+ } else {
+ alert('WebSocket connection not established, please connect.');
+ }
+ }
+
+ function updateTarget(target) {
+ if (window.location.protocol == 'http:') {
+ document.getElementById('target').value = 'ws://' + window.location.host + target;
+ } else {
+ document.getElementById('target').value = 'wss://' + window.location.host + target;
+ }
+ }
+
+ function log(message) {
+ var console = document.getElementById('console');
+ var p = document.createElement('p');
+ p.style.wordWrap = 'break-word';
+ p.appendChild(document.createTextNode(message));
+ console.appendChild(p);
+ while (console.childNodes.length > 25) {
+ console.removeChild(console.firstChild);
+ }
+ console.scrollTop = console.scrollHeight;
+ }
+
+
+ document.addEventListener("DOMContentLoaded", function() {
+ // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+ var noscripts = document.getElementsByClassName("noscript");
+ for (var i = 0; i < noscripts.length; i++) {
+ noscripts[i].parentNode.removeChild(noscripts[i]);
+ }
+ }, false);
+ ]]></script>
+</head>
+<body>
+<div class="noscript"><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+ Javascript and reload this page!</h2></div>
+<div>
+ <div id="connect-container">
+ <div>
+ <span>Connect to service implemented using:</span>
+ <!-- echo example using new programmatic API on the server side -->
+ <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoProgrammatic"
+ onclick="updateTarget(this.value);"/> <label for="radio1">programmatic API</label>
+ <!-- echo example using new annotation API on the server side -->
+ <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoAnnotation"
+ onclick="updateTarget(this.value);"/> <label for="radio2">annotation API</label>
+ </div>
+ <div>
+ <input id="target" type="text" size="40" style="width: 350px"/>
+ </div>
+ <div>
+ <button id="connect" onclick="connect();">Connect</button>
+ <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
+ </div>
+ <div>
+ <textarea id="message" style="width: 350px">Here is a message!</textarea>
+ </div>
+ <div>
+ <button id="echo" onclick="echo();" disabled="disabled">Echo message</button>
+ </div>
+ </div>
+ <div id="console-container">
+ <div id="console"/>
+ </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tomcat-uid/webapps/examples/websocket/index.xhtml b/tomcat-uid/webapps/examples/websocket/index.xhtml
new file mode 100644
index 0000000..2b8d8cd
--- /dev/null
+++ b/tomcat-uid/webapps/examples/websocket/index.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title>Apache Tomcat WebSocket Examples</title>
+</head>
+<body>
+<h1>Apache Tomcat WebSocket Examples</h1>
+<ul>
+ <li><a href="echo.xhtml">Echo example</a></li>
+ <li><a href="chat.xhtml">Chat example</a></li>
+ <li><a href="snake.xhtml">Multiplayer snake example</a></li>
+ <li><a href="drawboard.xhtml">Multiplayer drawboard example</a></li>
+
+</ul>
+</body>
+</html>
\ No newline at end of file
diff --git a/tomcat-uid/webapps/examples/websocket/snake.xhtml b/tomcat-uid/webapps/examples/websocket/snake.xhtml
new file mode 100644
index 0000000..86d1b3c
--- /dev/null
+++ b/tomcat-uid/webapps/examples/websocket/snake.xhtml
@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title>Apache Tomcat WebSocket Examples: Multiplayer Snake</title>
+ <style type="text/css"><![CDATA[
+ #playground {
+ width: 640px;
+ height: 480px;
+ background-color: #000;
+ }
+
+ #console-container {
+ float: left;
+ margin-left: 15px;
+ width: 300px;
+ }
+
+ #console {
+ border: 1px solid #CCCCCC;
+ border-right-color: #999999;
+ border-bottom-color: #999999;
+ height: 480px;
+ overflow-y: scroll;
+ padding-left: 5px;
+ padding-right: 5px;
+ width: 100%;
+ }
+
+ #console p {
+ padding: 0;
+ margin: 0;
+ }
+ ]]></style>
+</head>
+<body>
+ <div class="noscript"><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+ Javascript and reload this page!</h2></div>
+ <div style="float: left">
+ <canvas id="playground" width="640" height="480"/>
+ </div>
+ <div id="console-container">
+ <div id="console"/>
+ </div>
+ <script type="application/javascript"><![CDATA[
+
+ var Game = {};
+
+ Game.fps = 30;
+ Game.socket = null;
+ Game.nextFrame = null;
+ Game.interval = null;
+ Game.direction = 'none';
+ Game.gridSize = 10;
+
+ function Snake() {
+ this.snakeBody = [];
+ this.color = null;
+ }
+
+ Snake.prototype.draw = function(context) {
+ for (var id in this.snakeBody) {
+ context.fillStyle = this.color;
+ context.fillRect(this.snakeBody[id].x, this.snakeBody[id].y, Game.gridSize, Game.gridSize);
+ }
+ };
+
+ Game.initialize = function() {
+ this.entities = [];
+ canvas = document.getElementById('playground');
+ if (!canvas.getContext) {
+ Console.log('Error: 2d canvas not supported by this browser.');
+ return;
+ }
+ this.context = canvas.getContext('2d');
+ window.addEventListener('keydown', function (e) {
+ var code = e.keyCode;
+ if (code > 36 && code < 41) {
+ switch (code) {
+ case 37:
+ if (Game.direction != 'east') Game.setDirection('west');
+ break;
+ case 38:
+ if (Game.direction != 'south') Game.setDirection('north');
+ break;
+ case 39:
+ if (Game.direction != 'west') Game.setDirection('east');
+ break;
+ case 40:
+ if (Game.direction != 'north') Game.setDirection('south');
+ break;
+ }
+ }
+ }, false);
+ if (window.location.protocol == 'http:') {
+ Game.connect('ws://' + window.location.host + '/examples/websocket/snake');
+ } else {
+ Game.connect('wss://' + window.location.host + '/examples/websocket/snake');
+ }
+ };
+
+ Game.setDirection = function(direction) {
+ Game.direction = direction;
+ Game.socket.send(direction);
+ Console.log('Sent: Direction ' + direction);
+ };
+
+ Game.startGameLoop = function() {
+ if (window.webkitRequestAnimationFrame) {
+ Game.nextFrame = function () {
+ webkitRequestAnimationFrame(Game.run);
+ };
+ } else if (window.mozRequestAnimationFrame) {
+ Game.nextFrame = function () {
+ mozRequestAnimationFrame(Game.run);
+ };
+ } else {
+ Game.interval = setInterval(Game.run, 1000 / Game.fps);
+ }
+ if (Game.nextFrame != null) {
+ Game.nextFrame();
+ }
+ };
+
+ Game.stopGameLoop = function () {
+ Game.nextFrame = null;
+ if (Game.interval != null) {
+ clearInterval(Game.interval);
+ }
+ };
+
+ Game.draw = function() {
+ this.context.clearRect(0, 0, 640, 480);
+ for (var id in this.entities) {
+ this.entities[id].draw(this.context);
+ }
+ };
+
+ Game.addSnake = function(id, color) {
+ Game.entities[id] = new Snake();
+ Game.entities[id].color = color;
+ };
+
+ Game.updateSnake = function(id, snakeBody) {
+ if (typeof Game.entities[id] != "undefined") {
+ Game.entities[id].snakeBody = snakeBody;
+ }
+ };
+
+ Game.removeSnake = function(id) {
+ Game.entities[id] = null;
+ // Force GC.
+ delete Game.entities[id];
+ };
+
+ Game.run = (function() {
+ var skipTicks = 1000 / Game.fps, nextGameTick = (new Date).getTime();
+
+ return function() {
+ while ((new Date).getTime() > nextGameTick) {
+ nextGameTick += skipTicks;
+ }
+ Game.draw();
+ if (Game.nextFrame != null) {
+ Game.nextFrame();
+ }
+ };
+ })();
+
+ Game.connect = (function(host) {
+ if ('WebSocket' in window) {
+ Game.socket = new WebSocket(host);
+ } else if ('MozWebSocket' in window) {
+ Game.socket = new MozWebSocket(host);
+ } else {
+ Console.log('Error: WebSocket is not supported by this browser.');
+ return;
+ }
+
+ Game.socket.onopen = function () {
+ // Socket open.. start the game loop.
+ Console.log('Info: WebSocket connection opened.');
+ Console.log('Info: Press an arrow key to begin.');
+ Game.startGameLoop();
+ setInterval(function() {
+ // Prevent server read timeout.
+ Game.socket.send('ping');
+ }, 5000);
+ };
+
+ Game.socket.onclose = function () {
+ Console.log('Info: WebSocket closed.');
+ Game.stopGameLoop();
+ };
+
+ Game.socket.onmessage = function (message) {
+ // _Potential_ security hole, consider using json lib to parse data in production.
+ var packet = eval('(' + message.data + ')');
+ switch (packet.type) {
+ case 'update':
+ for (var i = 0; i < packet.data.length; i++) {
+ Game.updateSnake(packet.data[i].id, packet.data[i].body);
+ }
+ break;
+ case 'join':
+ for (var j = 0; j < packet.data.length; j++) {
+ Game.addSnake(packet.data[j].id, packet.data[j].color);
+ }
+ break;
+ case 'leave':
+ Game.removeSnake(packet.id);
+ break;
+ case 'dead':
+ Console.log('Info: Your snake is dead, bad luck!');
+ Game.direction = 'none';
+ break;
+ case 'kill':
+ Console.log('Info: Head shot!');
+ break;
+ }
+ };
+ });
+
+ var Console = {};
+
+ Console.log = (function(message) {
+ var console = document.getElementById('console');
+ var p = document.createElement('p');
+ p.style.wordWrap = 'break-word';
+ p.innerHTML = message;
+ console.appendChild(p);
+ while (console.childNodes.length > 25) {
+ console.removeChild(console.firstChild);
+ }
+ console.scrollTop = console.scrollHeight;
+ });
+
+ Game.initialize();
+
+
+ document.addEventListener("DOMContentLoaded", function() {
+ // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+ var noscripts = document.getElementsByClassName("noscript");
+ for (var i = 0; i < noscripts.length; i++) {
+ noscripts[i].parentNode.removeChild(noscripts[i]);
+ }
+ }, false);
+
+ ]]></script>
+</body>
+</html>
\ No newline at end of file