From 2adcfad86307a543fa720378b1c8e91b1a6a0ec6 Mon Sep 17 00:00:00 2001 From: wcrisman Date: Sun, 7 Dec 2014 16:12:29 -0800 Subject: [PATCH] Merged many of the changes from the first attempt at an HTML5 Websocket implementation. Focused on the ones that didn't change the behavior of the web server. --- Brainstorm View Client/.project | 17 ++ Brainstorm View SWT/.project | 17 ++ Brainstorm View Server/.project | 17 ++ Brainstorm View Test Server/.project | 33 ++ .../com/foundation/web/server/WebServer.java | 282 ++++++++++++++++++ .../web/interfaces/IConnectionContext.java | 50 +++- .../foundation/web/interfaces/IResponse.java | 2 + .../interfaces/IStreamedWebsocketMessage.java | 17 ++ .../web/interfaces/IWebApplication.java | 4 + .../web/interfaces/WebsocketHandler.java | 67 +++++ ... Server) Loads Webapps From Project.launch | 2 +- Foundation Web Server/server.xml | 4 +- Foundation Web View/.project | 17 ++ 13 files changed, 525 insertions(+), 4 deletions(-) create mode 100644 Brainstorm View Client/.project create mode 100644 Brainstorm View SWT/.project create mode 100644 Brainstorm View Server/.project create mode 100644 Brainstorm View Test Server/.project create mode 100644 Foundation Web Interfaces/src/com/foundation/web/interfaces/IStreamedWebsocketMessage.java create mode 100644 Foundation Web Interfaces/src/com/foundation/web/interfaces/WebsocketHandler.java create mode 100644 Foundation Web View/.project diff --git a/Brainstorm View Client/.project b/Brainstorm View Client/.project new file mode 100644 index 0000000..dafcc68 --- /dev/null +++ b/Brainstorm View Client/.project @@ -0,0 +1,17 @@ + + + Brainstorm View Client + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/Brainstorm View SWT/.project b/Brainstorm View SWT/.project new file mode 100644 index 0000000..a4599ce --- /dev/null +++ b/Brainstorm View SWT/.project @@ -0,0 +1,17 @@ + + + Brainstorm View SWT + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/Brainstorm View Server/.project b/Brainstorm View Server/.project new file mode 100644 index 0000000..07aebe8 --- /dev/null +++ b/Brainstorm View Server/.project @@ -0,0 +1,17 @@ + + + Brainstorm View Server + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/Brainstorm View Test Server/.project b/Brainstorm View Test Server/.project new file mode 100644 index 0000000..fded74e --- /dev/null +++ b/Brainstorm View Test Server/.project @@ -0,0 +1,33 @@ + + + Foundation Web Test Webapp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.declarativeengineering.jetson.vmlBuilderId + + + + + com.declarativeengineering.jetson.resourceBuilderId + + + + + com.declarativeengineering.jetson.htmlBuilderId + + + + + + org.eclipse.jdt.core.javanature + com.declarativeengineering.jetson.jefNatureId + + diff --git a/Foundation Web Core/src/com/foundation/web/server/WebServer.java b/Foundation Web Core/src/com/foundation/web/server/WebServer.java index 277c42c..f78aca1 100644 --- a/Foundation Web Core/src/com/foundation/web/server/WebServer.java +++ b/Foundation Web Core/src/com/foundation/web/server/WebServer.java @@ -61,6 +61,7 @@ import com.common.util.IIterator; import com.common.util.LiteHashMap; import com.common.util.LiteHashSet; import com.common.util.LiteList; +import com.common.util.Queue; import com.common.util.StreamBuffer; import com.common.util.StringSupport; import com.common.util.optimized.IntObjectHashMap; @@ -285,6 +286,8 @@ public class WebServer { private abstract class AbstractSocketContext extends ChannelContext { /** The key that represents the connection between the channel (socket) and the selector used to multiplex the listener. */ public SelectionKey key = null; + /** Whether the socket is currently being used by a thread designated by the network listener thread to read or write to the socket. Currently the socket type we use in Java only allows one thread to read and write at a time. Note: Always synchronize on key before using this attribute. */ + private boolean isUsed = false; /** The SelectionKey flags that will be used when the NetworkListener thread finishes processing the input/output of a message. The thread can safely change these flags without synchronizing. */ private int flags = 0; /** A socket context related to this one (when two are tied together such that data from one immediately is sent to the other). */ @@ -443,6 +446,23 @@ public class WebServer { flags = SelectionKey.OP_READ; }//synchronized// }//flagReadOnly()// + /** + * Called to notify the network listener that a pending write operation exists for this socket. + */ + protected void notifyListenerOfPendingWrite() { + synchronized(key) { + //Ignore if a thread is using this socket currently since all operation flags will be set at the end of the use of the socket.// + if(!isUsed) { + int ops = key.interestOps(); + boolean hasWrite = (ops & SelectionKey.OP_WRITE) != 0; + + if(!hasWrite) { + key.interestOps(ops | SelectionKey.OP_WRITE); + key.selector().wakeup(); + }//if// + }//if// + }//synchronized// + }//notifyListenerOfPendingWrite()// }//AbstractSocketContext// private static class RegisterKeyRunnable implements Runnable { @@ -711,6 +731,34 @@ public class WebServer { private LiteHashMap applicationDataMap; /** Used to identify the first unencrypted message (ignored if ssl is being used) so that forwarding to a remote server can be accomplished. */ private boolean isFirstUnencryptedMessage = true; + /** Whether this is a websocket connection. */ + private boolean isWebsocket = false; + /** The protocol passed when this connection was upgraded to a websocket. */ + private String websocketProtocol = null; + /** The maximum number of bytes in an allowed websocket message (may be composed of multiple frames, does not include the frame header sizes). While this is a long, we really can't read longer than Integer.MAX_VALUE sized frames. So the message might be within size limits, but it might still be rejected if the frame exceeds the server's capacity to read a frame. */ + private long websocketMaxMessageLength = 0; + /** The reusable frame header buffer. */ + private byte[] websocketFrameHeader = null; + /** The index into the frame header of the last read byte. */ + private int websocketFrameHeaderIndex = 0; + /** The next partial message (single messages may be broken into frames by the client). */ + private StreamBuffer websocketPartialReceivedMessage = null; + /** The remaining bytes to be read on the current message frame (the frame header and part of the frame was read previously). */ + private int websocketFrameRemainder = 0; + /** Whether the frame currently being read in the current message being received is the last frame in the message (when the frame is fully read it will trigger the message to be processed). */ + private boolean websocketIsLastFrameInMessage = false; + /** The op code for the currently reading message, or zero if the last message was completed. */ + private int websocketMessageOpCode = 0; + /** The currently reading frame's mask key used to decode the frame data. */ + private byte[] websocketMessageMaskKey = null; + /** The queue used to hold outgoing messages before they are sent. Will contain only String, byte[], or IConnectionContext.IStreamedWebsocketMessage instances. */ + private Queue websocketPendingMessages = null; + /** The streambuffer for the currently sending message. This will be for the current part of the streaming message if websocketStreamingMessage is non-null. */ + private ByteBuffer websocketSendingMessage = null; + /** The streaming message handler which will be set only if the currently sending message is streaming. */ + private IStreamedWebsocketMessage websocketStreamingMessage = null; + /** The application specified handler called when websocket events occur (messages received, socket closed, etc). */ + private WebsocketHandler websocketHandler = null; public SocketContext(ServerSocketContext serverSocketContext) { super(); @@ -732,6 +780,16 @@ public class WebServer { return this; }//getLock()// protected synchronized void close() { + try { + if(websocketHandler != null) { + websocketHandler.connectionClosed(); + websocketHandler = null; + }//if// + }//try// + catch(Throwable e) { + Debug.log(e); + }//catch// + try { if(applicationDataMap != null) { for(IIterator iterator = applicationDataMap.valueIterator(); iterator.hasNext(); ) { @@ -1134,6 +1192,14 @@ public class WebServer { // flagWrite(pendingOutboundMessage != null); }//synchronized// }//if// + else if(isWebsocket) { + //Right after upgrading the socket we have one last HTTP response to process.// + if(currentResponse != null) { + internalProcessResponses(); + }//if// + + internalProcessWebsocketMessages(); + }//else if// else { //Go directly to writing the client response if we are just passing everything through to another process.// boolean receive = internalProcessResponses(); @@ -1147,6 +1213,130 @@ public class WebServer { }//else// }//else// }//processCurrentResponse()// + /** + * Loads the next outbound websocket message and attempts to write it to the socket until all outbound messages have been sent, or the socket's buffers are full and a wait is required. + * If a message could only be partially sent then the next call will attempt to finish sending it. + */ + private void internalProcessWebsocketMessages() { + if(websocketSendingMessage == null) { + loadNextWebsocketMessage(); + }//if// + + while(websocketSendingMessage != null) { + //If the socket is open then send the next buffer of data.// + if(key.channel().isOpen()) { + if(pendingOutboundMessage != null) { + //Put the sending message in a MessageBuffer (pendingOutboundMessage).// + pendingOutboundMessage = new MessageBuffer(websocketSendingMessage); + }//if// + + //Write the pendingOutboundMessage to the socket.// + if(writeClientResponse()) { + websocketSendingMessage = null; + pendingOutboundMessage = null; + }//if// + }//if// + + //If we finished sending the message then load the next one.// + if(websocketSendingMessage == null) { + loadNextWebsocketMessage(); + }//if// + }//while// + }//internalProcessWebsocketMessages()// + /** + * Loads and prepares the next websocket message from the queue of pending messages. + * Clears the pending message attributes if there isn't a pending message to be processed. + * The caller can check websocketSendingMessage == null to see if there is a ready message. + */ + private void loadNextWebsocketMessage() { + Object next = null; + boolean isLastPart = true; + + if(websocketStreamingMessage != null && websocketStreamingMessage.hasNextPart()) { + next = websocketStreamingMessage.getNextPart(); + isLastPart = !websocketStreamingMessage.hasNextPart(); + + //Ensure that we got a string or byte array.// + if(!(next instanceof String || next instanceof byte[])) { + throw new RuntimeException("Invalid streaming message part type."); + }//if// + }//if// + else { + synchronized(websocketPendingMessages) { + if(websocketPendingMessages.getSize() > 0) { + next = websocketPendingMessages.dequeue(); + }//if// + }//synchronized// + }//else// + + if(next != null) { + byte[] bytes = null; + int opCode = 0; + int length = 0; + + if(next instanceof String) { + try {bytes = ((String) next).getBytes("UTF-8");} catch(Throwable e) {Debug.log(e);} + opCode = websocketStreamingMessage == null ? 0x01 : 0; + length = bytes.length; + }//if// + else if(next instanceof byte[]) { + bytes = (byte[]) next; + opCode = websocketStreamingMessage == null ? 0x02 : 0; + length = bytes.length; + }//else if// + else if(next instanceof Byte) { //Control Message// + opCode = ((Byte) next).byteValue(); + }//else if// + else if(next instanceof IStreamedWebsocketMessage) { + websocketStreamingMessage = (IStreamedWebsocketMessage) next; + next = websocketStreamingMessage.getNextPart(); + isLastPart = !websocketStreamingMessage.hasNextPart(); + + if(next instanceof String) { + try {bytes = ((String) next).getBytes("UTF-8");} catch(Throwable e) {Debug.log(e);} + opCode = 0x01; //Text// + length = bytes.length; + }//if// + else if(next instanceof byte[]) { + bytes = (byte[]) next; + opCode = 0x02; //Binary// + length = bytes.length; + }//else if// + else { + throw new RuntimeException("Invalid streaming message part type."); + }//if// + }//else if// + + websocketSendingMessage = ByteBuffer.allocate(14 + length); + websocketSendingMessage.put((byte) (isLastPart ? 0x8 : 0)); + websocketSendingMessage.put((byte) opCode); + + //Write the length differently based on how long the content is.// + if(length < 126) { + websocketSendingMessage.put((byte) length); +// websocketSendingMessage.putLong(0, StreamBuffer.NUMBER_MSF); + }//if// + else if(length < 65535) { + websocketSendingMessage.put((byte) 126); + websocketSendingMessage.putShort((short) (length & 0xFFFF)); +// websocketSendingMessage.putShort((short) 0); + websocketSendingMessage.putInt(0); + }//else if// + else { + websocketSendingMessage.put((byte) 127); + websocketSendingMessage.putLong((long) length); + }//else// + + //The server doesn't use a mask key.// +// websocketSendingMessage.putInt(0); + //Put the content at the end of the message.// + websocketSendingMessage.put(bytes); + }//if// + else { + websocketSendingMessage = null; + websocketStreamingMessage = null; + }//else// + }//loadNextWebsocketMessage()// /** * @return */ @@ -1746,6 +1936,98 @@ public class WebServer { // flagWriteOnly(); }//else// }//processRequest()// + /* (non-Javadoc) + * @see com.foundation.web.server.WebServer.AbstractSocketContext#hasPendingWrite() + */ + protected boolean hasPendingWrite() { + return pendingOutboundMessage != null; + }//hasPendingWrite()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#upgradeToWebsocket(java.lang.String, long, com.foundation.web.interfaces.WebsocketHandler) + */ + public void upgradeToWebsocket(String protocol, long maxMessageLength, WebsocketHandler websocketHandler) { + if(!isWebsocket) { + this.isWebsocket = true; + this.websocketProtocol = protocol; + this.websocketMaxMessageLength = maxMessageLength; + this.websocketFrameHeader = new byte[14]; + this.websocketMessageMaskKey = new byte[4]; + this.websocketPendingMessages = new Queue(20); + this.websocketHandler = websocketHandler; + }//if// + }//upgradeToWebsocket()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#getWebsocketProtocol() + */ + public String getWebsocketProtocol() { + return websocketProtocol; + }//getWebsocketProtocol()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#isWebsocket() + */ + public boolean isWebsocket() { + return isWebsocket; + }//isWebsocket()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#sendWebsocketMessage(byte[]) + */ + public void sendWebsocketMessage(byte[] message) { + if(isWebsocket) { + synchronized(websocketPendingMessages) { + websocketPendingMessages.enqueue(message); + }//synchronized// + + notifyListenerOfPendingWrite(); + }//if// + }//sendWebsocketMessage()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#sendWebsocketMessage(java.lang.String) + */ + public void sendWebsocketMessage(String message) { + if(isWebsocket) { + synchronized(websocketPendingMessages) { + websocketPendingMessages.enqueue(message); + }//synchronized// + + notifyListenerOfPendingWrite(); + }//if// + }//sendWebsocketMessage()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#sendWebsocketPing() + */ + public void sendWebsocketPing() { + if(isWebsocket) { + synchronized(websocketPendingMessages) { + websocketPendingMessages.enqueue(new Byte((byte) 0x9)); + }//synchronized// + + notifyListenerOfPendingWrite(); + }//if// + }//sendWebsocketPing()// + /** + * Sends a PONG response to the client's ping. + */ + public void sendWebsocketPong() { + if(isWebsocket) { + synchronized(websocketPendingMessages) { + websocketPendingMessages.enqueue(new Byte((byte) 0xA)); + }//synchronized// + + notifyListenerOfPendingWrite(); + }//if// + }//sendWebsocketPong()// + /* (non-Javadoc) + * @see com.foundation.web.interfaces.IConnectionContext#sendWebsocketMessage(com.foundation.web.interfaces.IConnectionContext.IStreamedWebsocketMessage) + */ + public void sendWebsocketMessage(IStreamedWebsocketMessage message) { + if(isWebsocket) { + synchronized(websocketPendingMessages) { + websocketPendingMessages.enqueue(message); + }//synchronized// + + notifyListenerOfPendingWrite(); + }//if// + }//sendWebsocketMessage()// }//SocketContext// /** diff --git a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IConnectionContext.java b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IConnectionContext.java index 3c380b0..08a446d 100644 --- a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IConnectionContext.java +++ b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IConnectionContext.java @@ -8,14 +8,62 @@ package com.foundation.web.interfaces; public interface IConnectionContext { /** * Gets the application data in the connection context's application data map by the given key. + * This is connection specific data, not client specific data which should be stored in the session. + *
Warning: Websocket connections are multi threaded and as such, threads must synchronize before calling this method. * @return The application specific data element. */ public Object getApplicationData(String key); /** * Stores the application data in the connection context's application data map by the given key. - * The application data may implement ISessionLifecycleAware if it should be called when the session is being released. This will only be called for a normal closing of the session. The session may still be restored at some time in the future. + * The applicationData may implement ISessionLifecycleAware (ignore that this is not a session) if it should be called when the connection is being closed. + * This is connection specific data, not client specific data which should be stored in the session. + *
Warning: Websocket connections are multi threaded and as such, threads must synchronize before calling this method. * @param key The key for the data. If the key is logically equal (equivalent) to an existing key, then the existing data will be replaced and returned. * @param applicationData The application specific data element. */ public void setApplicationData(String key, Object applicationData); +/** + * Upgrades the connection context to be a HTML5 websocket using the given optional protocol. + * @param protocol The optional protocol which will be passed to the application when frames are received. + * @param maxMessageLength The maximum number of bytes in an allowed websocket message (may be composed of multiple frames, does not include the frame header sizes). + * @param websocketHandler The application provided handler of websocket messages. + */ +public void upgradeToWebsocket(String protocol, long maxMessageLength, WebsocketHandler websocketHandler); +/** + * Sends the message to the client if this connection has been upgraded to a websocket. + *
The call is ignored if this is not a websocket. + *
This is a thread safe call. + * @param message The message. The message will be queued for sending and the call will return immediately. + */ +public void sendWebsocketMessage(byte[] message); +/** + * Sends the message to the client if this connection has been upgraded to a websocket. + *
The call is ignored if this is not a websocket. + *
This is a thread safe call. + * @param message The message. The message will be queued for sending and the call will return immediately. + */ +public void sendWebsocketMessage(String message); +/** + * Sends the message to the client if this connection has been upgraded to a websocket. + *
The call is ignored if this is not a websocket. + *
This is a thread safe call. + * @param message The message. The message will be queued for sending and the call will return immediately. + */ +public void sendWebsocketMessage(IStreamedWebsocketMessage message); +/** + * Sends the PING message to the client if this connection has been upgraded to a websocket. + *
The call is ignored if this is not a websocket. + *
This is a thread safe call. + */ +public void sendWebsocketPing(); +/** + * Gets the protocol associated with this websocket. + * @return The optional protocol associated with the connection context when it was upgraded to a websocket, or null if this context was never upgraded. + */ +public String getWebsocketProtocol(); +/** + * Determines whether this connection context has been upgraded to a websocket. + * @return Whether this is a websocket connection. + */ +public boolean isWebsocket(); }//IConnectionContext// \ No newline at end of file diff --git a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IResponse.java b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IResponse.java index f196876..db74fd9 100644 --- a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IResponse.java +++ b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IResponse.java @@ -19,6 +19,8 @@ public interface IResponse { public static final int ERROR_TYPE_RESOURCE_NOT_MODIFIED = 3; /** Used to warn the user that their client did not use TLS + the domain extension, and the server cannot identify which certificate to use to allow them to connect. The user should upgrade their browser. */ public static final int ERROR_TYPE_TLS_FAILURE = 4; + /** Used to notify the client that the socket upgrade or protocol change failed or was rejected by the server. */ + public static final int ERROR_UPGRADE_REJECTED = 5; /** * Gets the request this response is responding to. * @return The request that created this response. diff --git a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IStreamedWebsocketMessage.java b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IStreamedWebsocketMessage.java new file mode 100644 index 0000000..0dbd2cb --- /dev/null +++ b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IStreamedWebsocketMessage.java @@ -0,0 +1,17 @@ +package com.foundation.web.interfaces; + +/** + * Used to stream a message to the client when the size of the message is unknown, or when the content is large and loading it all into memory at once is undesireable. + */ +public interface IStreamedWebsocketMessage { + /** + * Gets the next part of the message. + * @return The non-null byte[] or String for the message. The returned type must be consistent for every call. + */ + public Object getNextPart(); + /** + * Whether there are more parts to the message than those already retrieved. + * @return Whether additional parts exist. + */ + public boolean hasNextPart(); +}//IStreamedWebsocketMessage// \ No newline at end of file diff --git a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IWebApplication.java b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IWebApplication.java index d4431d2..35173d7 100644 --- a/Foundation Web Interfaces/src/com/foundation/web/interfaces/IWebApplication.java +++ b/Foundation Web Interfaces/src/com/foundation/web/interfaces/IWebApplication.java @@ -161,6 +161,10 @@ public ISession createSession(); * @return The newly created and indexed secure session. */ public void createSecureSession(ISession session); +/** + * Gives the web application a chance to handle the web socket upgrade. + */ +public void handleWebSocketUpgrade(IRequest request, IResponse response, ISession session, boolean isSecure, boolean clientHadBadSession, IConnectionContext connectionContext, String connection, String secWebSocketKey, String secWebSocketProtocol, String secWebSocketVersion, String origin); /** * Processes a request from the client associated with the session. The result is placed in the response object. * @param request The request metadata. diff --git a/Foundation Web Interfaces/src/com/foundation/web/interfaces/WebsocketHandler.java b/Foundation Web Interfaces/src/com/foundation/web/interfaces/WebsocketHandler.java new file mode 100644 index 0000000..bc5f969 --- /dev/null +++ b/Foundation Web Interfaces/src/com/foundation/web/interfaces/WebsocketHandler.java @@ -0,0 +1,67 @@ +package com.foundation.web.interfaces; + + +/** + * The base class for a websocket handler passed to the IConnectionContext object when converting an HTTP connection into a websocket. + */ +public abstract class WebsocketHandler { + /** The websocket connection reference. */ + private IConnectionContext connection; +/** + * WebsocketHandler constructor. + * @param connection The websocket connection reference. + */ +public WebsocketHandler(IConnectionContext connection) { + this.connection = connection; +}//WebsocketHandler()// +/** + * Receives a text message from the client. + * @param message The message received. + */ +public abstract void receiveTextMessage(String message); +/** + * Receives a binary message from the client. + * @param message The message received. + */ +public abstract void receiveBinaryMessage(byte[] message); +/** + * Receives a pong control message from the client (in reponse to a ping from the server). + */ +public void receivePong() { + //Does nothing.// +}//receivePong()// +/** + * Sends a text message to the client without blocking. + *
This is a thread safe call. + * @param message The message to be sent. + */ +public void sendTextMessage(String message) { + connection.sendWebsocketMessage(message); +}//sendTextMessage()// +/** + * Sends a binary message to the client without blocking. + *
This is a thread safe call. + * @param message The message to be sent. + */ +public void sendBinaryMessage(byte[] message) { + connection.sendWebsocketMessage(message); +}//sendBinaryMessage()// +/** + * Sends a streamed message to the client without blocking. + *
This is a thread safe call. + * @param message The message to be sent. + */ +public void sendStreamedMessage(IStreamedWebsocketMessage message) { + connection.sendWebsocketMessage(message); +}//sendStreamedMessage()// +/** + * Sends a ping control message to the client which will respond with a pong control message. + */ +public void sendPing() { + connection.sendWebsocketPing(); +}//sendPing()// +/** + * Called by the connection when it is closed for any reason. + */ +public abstract void connectionClosed(); +}//WebsocketHandler// \ No newline at end of file diff --git a/Foundation Web Server/Web Server (Brainstorm Web Server) Loads Webapps From Project.launch b/Foundation Web Server/Web Server (Brainstorm Web Server) Loads Webapps From Project.launch index a91321f..0b7fc5a 100644 --- a/Foundation Web Server/Web Server (Brainstorm Web Server) Loads Webapps From Project.launch +++ b/Foundation Web Server/Web Server (Brainstorm Web Server) Loads Webapps From Project.launch @@ -11,5 +11,5 @@ - + diff --git a/Foundation Web Server/server.xml b/Foundation Web Server/server.xml index 02f8b39..12b5701 100644 --- a/Foundation Web Server/server.xml +++ b/Foundation Web Server/server.xml @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/Foundation Web View/.project b/Foundation Web View/.project new file mode 100644 index 0000000..2b0a7d2 --- /dev/null +++ b/Foundation Web View/.project @@ -0,0 +1,17 @@ + + + Foundation Web View + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + +