diff --git a/Foundation Web Core/src/com/foundation/web/server/AbstractSocketContext.java b/Foundation Web Core/src/com/foundation/web/server/AbstractSocketContext.java index 2508283..7c2421f 100644 --- a/Foundation Web Core/src/com/foundation/web/server/AbstractSocketContext.java +++ b/Foundation Web Core/src/com/foundation/web/server/AbstractSocketContext.java @@ -27,7 +27,7 @@ public abstract class AbstractSocketContext implements IChannelContext { /** The network listener that created this socket context. */ private final NetworkListener networkListener; /** The debug ID for the socket. */ - public final int id; + private final int id; /** The web server that created the socket context. */ protected WebServer webServer = null; /** The key that represents the connection between the channel (socket) and the selector used to multiplex the listener. The code must synchronize on this attribute when accessing the isUsed functionality, or when interacting with the key's interestOps. */ @@ -75,6 +75,8 @@ public WebServer getWebServer() {return networkListener.getWebServer();} public boolean getIsUsed() {return isUsed;} /** Sets whether the socket context is currently in use by a thread. */ public void setIsUsed(boolean isUsed) {this.isUsed = isUsed;} +/** Gets the debug id for the socket. */ +public int getId() {return id;} /** * Gets the lockable (synchronizable) object for this context. For contexts with a related context, only one of the two will be returned, such that a single synchronize block covers both contexts. * @return The object to synchronize on such that two threads don't attempt to interact with the context at the same time (AsynchronousSocketChannel required for that). diff --git a/Foundation Web Core/src/com/foundation/web/server/NetworkListener.java b/Foundation Web Core/src/com/foundation/web/server/NetworkListener.java index 1e05e1b..74d5b7d 100644 --- a/Foundation Web Core/src/com/foundation/web/server/NetworkListener.java +++ b/Foundation Web Core/src/com/foundation/web/server/NetworkListener.java @@ -58,7 +58,7 @@ public void start() { * @param channel The client connection that is now closed. */ private void cleanupClientChannel(SocketContext context, SocketChannel channel) { - if(this.webServer.debug) { + if(getWebServer().debug()) { Debug.log("Connection closed to " + channel.socket().getInetAddress() + ":" + channel.socket().getPort()); }//if// }//cleanupClientChannel()// @@ -154,7 +154,7 @@ public void run() { socketContext.socketReadBuffer = ByteBuffer.allocate(AbstractSocketContext.BUFFER_SIZE); }//if// - if(this.webServer.debug) { + if(getWebServer().debug()) { Debug.log("Connection opened to " + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort()); }//if// }//try// @@ -209,7 +209,7 @@ public void run() { try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.// }//catch// catch(Throwable e) { - if(NetworkListener.this.webServer.debug) Debug.log(e); + if(getWebServer().debug()) Debug.log(e); //Force the socket to be closed (for sure).// try {((SocketChannel) channel).close();} catch(Throwable e2) {} diff --git a/Foundation Web Core/src/com/foundation/web/server/SocketContext.java b/Foundation Web Core/src/com/foundation/web/server/SocketContext.java index a807852..fee6239 100644 --- a/Foundation Web Core/src/com/foundation/web/server/SocketContext.java +++ b/Foundation Web Core/src/com/foundation/web/server/SocketContext.java @@ -497,7 +497,7 @@ private void prepareResponse(Response response) { buffer = ByteBuffer.allocate(headerBytes.length > 2000 ? headerBytes.length : 2000); buffer.put(headerBytes); - if(getWebServer().debug) { + if(getWebServer().debug()) { //Test code... ByteBuffer buffer2 = ByteBuffer.allocate(headerBytes.length); buffer2.put(headerBytes); @@ -744,9 +744,10 @@ private synchronized void internalProcessResponses() { }//if// //If we finished sending the current response then load the next one.// - if(messageSent) { + if(messageSent || (currentOutboundMessage != null && currentOutboundMessage.isClosed())) { //TODO: Queue up the next outbound message. currentOutboundMessage = null; + lastOutboundMessage = null; }//if// if(currentOutboundMessage == null) { @@ -799,7 +800,7 @@ private boolean writeClientBoundMessage() { boolean sendMore = true; if(sslEngine != null) { - sendMore = writeClientBoundSslMessage(); + sendMore = writeClientBoundSslMessage((SocketChannel) key.channel(), currentOutboundMessage); }//if// else { sendMore = writeClientBoundPlainMessage(); @@ -811,7 +812,7 @@ private boolean writeClientBoundMessage() { * Sends a response to the client. * @return Whether the response could be fully sent. This will be false if there is still more data to be written when the call returns. */ -private boolean writeClientBoundSslMessage() { +private boolean writeClientBoundSslMessage(SocketChannel channel, MessageBuffer currentOutboundMessage) { boolean sendMore = true; try { @@ -820,10 +821,18 @@ private boolean writeClientBoundSslMessage() { int remaining = encryptedWriteBuffer.remaining(); //Write the bytes to the stream.// - ((SocketChannel) key.channel()).write(encryptedWriteBuffer); + channel.write(encryptedWriteBuffer); + + if(getWebServer().debug()) { + Debug.log(this.getId() + "|" + System.nanoTime() + "|Wrote " + (remaining - encryptedWriteBuffer.remaining()) + " encrypted bytes to the stream. " + encryptedWriteBuffer.remaining() + " remain."); + }//if// //Check to see if we failed to send the whole frame.// if(encryptedWriteBuffer.hasRemaining()) { + if(getWebServer().debug()) { + Debug.log(this.getId() + "|" + System.nanoTime() + "|Bytes remain in the encrypted write buffer. Flagging that more needs to be sent at a later time."); + }//if// + sendMore = false; }//if// }//if// @@ -831,6 +840,10 @@ private boolean writeClientBoundSslMessage() { while(sendMore && sslNeedsWrap) { SSLEngineResult handshakeResult; + if(getWebServer().debug()) { + Debug.log(this.getId() + "|" + System.nanoTime() + "|SSL handshaking with the client."); + }//if// + //Reset the encrypted write buffer - note that since we will never read while waiting to write data, this should always be empty.// encryptedWriteBuffer.position(0); encryptedWriteBuffer.limit(encryptedWriteBuffer.capacity()); @@ -859,10 +872,8 @@ private boolean writeClientBoundSslMessage() { Debug.log(new RuntimeException("Unexpected ssl engine task.")); }//if// else if(encryptedWriteBuffer.hasRemaining()) { - int remaining = encryptedWriteBuffer.remaining(); - //Write the bytes to the stream.// - ((SocketChannel) key.channel()).write(encryptedWriteBuffer); + channel.write(encryptedWriteBuffer); //If not all the bytes could be written then we will need to wait until we can write more.// if(encryptedWriteBuffer.hasRemaining()) { @@ -885,21 +896,15 @@ private boolean writeClientBoundSslMessage() { }//else// }//while// - if(sendMore && currentOutboundMessage != null) { + if(sendMore && currentOutboundMessage != null && !currentOutboundMessage.isClosed()) { //Check to see if the outbound message is prepared to send more content. For chunked transfers the outbound message may be waiting for additional content from another stream and we should return later.// if(!currentOutboundMessage.getBuffer().hasRemaining()) { if(!currentOutboundMessage.loadBuffer()) { - if(currentOutboundMessage.getBuffer() == null && currentOutboundMessage.getNext() != null) { - currentOutboundMessage = currentOutboundMessage.getNext(); - }//if// - else { - sendMore = false; - }//else// + sendMore = false; }//if// if(currentOutboundMessage.getBuffer() == null) { currentOutboundMessage = null; - lastOutboundMessage = null; }//if// }//if// @@ -928,18 +933,12 @@ private boolean writeClientBoundSslMessage() { // Debug.log(new RuntimeException("Unexpected ssl engine closed.")); //TODO: Handle this closure without an infinate loop... //Close the socket.// - try {key.channel().close();} catch(Throwable e2) {} + try {channel.close();} catch(Throwable e2) {} }//else if// else if(encryptResult.getStatus() == Status.OK) { //Write the bytes to the stream.// try { - int remaining = encryptedWriteBuffer.remaining(); - - ((SocketChannel) key.channel()).write(encryptedWriteBuffer); - -// if(getWebServer().debug) { -// debugBuffer.append("Sent " + (remaining - encryptedWriteBuffer.remaining()) + " encrypted bytes.\n"); -// }//if// + channel.write(encryptedWriteBuffer); }//try// catch(IOException e) { //Caught if the channel is forcably closed by the client. We will ignore it.// @@ -949,10 +948,6 @@ private boolean writeClientBoundSslMessage() { if(encryptedWriteBuffer.hasRemaining()) { //Leave the data in the encrypted write buffer for the writing operation to send it.// sendMore = false; - -// if(getWebServer().debug) { -// debugBuffer.append("Pausing due to a partially sent packet. Bytes actually sent: " + encryptedWriteBuffer.position() + ". Bytes remaining: " + encryptedWriteBuffer.remaining() + ".\n"); -// }//if// }//if// }//else if// else { @@ -962,22 +957,15 @@ private boolean writeClientBoundSslMessage() { //Add more content to the buffer.// //Note: Do this even if the last encrypted write buffer could not be fully sent - so that when it is sent there will be outbound message content.// - if(key.channel().isOpen() && currentOutboundMessage != null) { + if(channel.isOpen() && currentOutboundMessage != null) { if(!currentOutboundMessage.loadBuffer()) { - //Load the next pending outbound message in the chain. This is currently only used for content being passed through to another process via a second socket.// - if(currentOutboundMessage.getBuffer() == null && currentOutboundMessage.getNext() != null) { - currentOutboundMessage = currentOutboundMessage.getNext(); - }//if// - else { - //Wait until additional message bytes are available.// - sendMore = false; - }//else// + //Wait until additional message bytes are available.// + sendMore = false; }//if// //If the message end has been reached then the buffer will be null.// if(currentOutboundMessage.getBuffer() == null) { currentOutboundMessage = null; - lastOutboundMessage = null; }//if// }//if// }//while// @@ -988,14 +976,14 @@ private boolean writeClientBoundSslMessage() { close(); }//catch// catch(SSLException e) { - if(getWebServer().debug) { + if(getWebServer().debug()) { Debug.log(e); }//if// close(); }//catch// catch(IOException e) { - if(getWebServer().debug) { + if(getWebServer().debug()) { Debug.log(e); }//if// @@ -1067,14 +1055,14 @@ private boolean writeClientBoundPlainMessage() { close(); }//catch// catch(SSLException e) { - if(getWebServer().debug) { + if(getWebServer().debug()) { Debug.log(e); }//if// close(); }//catch// catch(IOException e) { - if(getWebServer().debug) { + if(getWebServer().debug()) { Debug.log(e); }//if// @@ -1235,7 +1223,7 @@ protected void readIncomingMessages() throws IOException { }//if// else if(sslResult.getStatus() == Status.BUFFER_OVERFLOW) { //Should never happen.// -// if(getWebServer().debug) Debug.log(new RuntimeException("Unexpected ssl engine buffer overflow.")); +// if(getWebServer().debug())) Debug.log(new RuntimeException("Unexpected ssl engine buffer overflow.")); close(); }//else if// else if(sslResult.getStatus() == Status.CLOSED) { @@ -1712,7 +1700,7 @@ private boolean processClientRequest(ByteBuffer fragment, SelectionKey key) thro if(isCompleteHeader(messageHeaderFragment)) { IWebApplication application = webApplicationContainer != null ? webApplicationContainer.getWebApplication() : null; -// if(getWebServer().debug) { +// if(getWebServer().debug())) { // context.debugBuffer.append("Processing:\r\n" + context.messageHeaderFragment.toString()); // }//if// @@ -1720,8 +1708,8 @@ private boolean processClientRequest(ByteBuffer fragment, SelectionKey key) thro try { request = new Request(++lastRequestNumber, messageHeaderFragment.toString(), ((SocketChannel) key.channel()).socket().getInetAddress().getHostAddress(), this, this, isSsl()); - //Log the request header if running in getWebServer().debug mode.// - if(getWebServer().debug) { + //Log the request header if running in getWebServer().debug() mode.// + if(getWebServer().debug()) { Debug.log(request.toString()); }//if// }//try// @@ -1764,11 +1752,11 @@ private boolean processClientRequest(ByteBuffer fragment, SelectionKey key) thro int contentLength = 0; //TODO: Remove - if(getWebServer().debug) { + if(getWebServer().debug()) { boolean sessionFound = application.getSession(request.getSessionId()) != null; boolean canRecreate = request.getSessionId() != null; - Debug.log("SC: " + id + "; Req#: " + request.getRequestNumber() + "; ReqURI: " + request.getUri() + "\n\t(SessionId: " + request.getSessionId() + "; SecureSessionId: " + request.getSecureSessionId() + ")\n\tSession Found: " + sessionFound + (!sessionFound ? "; Can Recreate: " + canRecreate : "")); + Debug.log("SC: " + getId() + "; Req#: " + request.getRequestNumber() + "; ReqURI: " + request.getUri() + "\n\t(SessionId: " + request.getSessionId() + "; SecureSessionId: " + request.getSecureSessionId() + ")\n\tSession Found: " + sessionFound + (!sessionFound ? "; Can Recreate: " + canRecreate : "")); }//if// request.setSession(application.getSession(request.getSessionId())); @@ -1790,7 +1778,7 @@ private boolean processClientRequest(ByteBuffer fragment, SelectionKey key) thro //This is a security mechanism because the client is SUPPOSED to keep the secure session ID very private and not allow access to it by other sites. It ensures that a recreated session actually comes from a client that had it created in the first place.// if(request.getSecureSessionId() != null && request.getSession() != null && request.getSession().getSecureSessionId() != null) { if(!Comparator.equals(request.getSecureSessionId(), request.getSession().getSecureSessionId())) { - Debug.log("SC: " + id + "Forcing connection closure."); + Debug.log("SC: " + getId() + "Forcing connection closure."); //Force the connection to the client to be closed.// try {key.channel().close();}catch(Throwable e2) {} //Throw an exception that should not be logged. This might happen when an attacker tries to reuse stored session data on a client.// @@ -2203,8 +2191,8 @@ private boolean processClientRequest(final Request request, SelectionKey key) th if(session == null) { //TODO: Remove - if(getWebServer().debug) { - Debug.log("SC: " + id + " Creating Session"); + if(getWebServer().debug()) { + Debug.log("SC: " + getId() + " Creating Session"); }//if// request.setSession(session = application.createSession()); @@ -2225,13 +2213,13 @@ private boolean processClientRequest(final Request request, SelectionKey key) th allowSecureAccess = true; }//if// else { - Debug.log(new RuntimeException("Error: The client did not send the correct secure session id with the request!")); + Debug.log(new RuntimeException("Error: The client did not send the correct secure session getId() with the request!")); }//else// }//if// else if(session != null && session.getSecureSessionId() == null) { //TODO: Remove - if(getWebServer().debug) { - Debug.log("SC: " + id + " Creating Secure Session"); + if(getWebServer().debug()) { + Debug.log("SC: " + getId() + " Creating Secure Session"); }//if// application.createSecureSession(session); 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 d62882e..67a2f6c 100644 --- a/Foundation Web Core/src/com/foundation/web/server/WebServer.java +++ b/Foundation Web Core/src/com/foundation/web/server/WebServer.java @@ -83,7 +83,7 @@ public class WebServer { /** Flag indicating whether the web server is active. The attributes cannot be changed in a thread unsafe manner when this is set. */ private volatile boolean isStarted = false; /** Whether to report all errors. If false, many networking errors (usually results of browsers terminating connections) will not be reported. */ - boolean debug = false; + private boolean debug = false; /** The handler called on to get a result for a bad request. */ private IWebServerErrorHandler errorHandler = null; /** The maximum length of any request's content block. */ @@ -227,6 +227,8 @@ public WebServer() { public WebServer(boolean debug) { this.debug = debug; }//WebServer()// +/** Whether the code should output extra debugging. */ +public boolean debug() {return debug;} /** * Gets the default maximum acceptable size of any request's content block. * @return The default maximum content size for a request.