diff --git a/Foundation Web Core/src/com/foundation/web/server/HttpMessageBuffer.java b/Foundation Web Core/src/com/foundation/web/server/HttpMessageBuffer.java deleted file mode 100644 index d568e54..0000000 --- a/Foundation Web Core/src/com/foundation/web/server/HttpMessageBuffer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.foundation.web.server; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; - -import com.common.debug.Debug; -import com.common.util.LiteHashMap; -import com.common.util.LiteList; -import com.foundation.web.interfaces.IContent; -import com.foundation.web.interfaces.IMimeType; - -/** - * A message buffer for HTTP messages. - */ -public class HttpMessageBuffer extends MessageBuffer { - protected final WebServer webServer; - private Response response; - private Request request; -public HttpMessageBuffer(WebServer webServer, Response response, Request request) { - this.webServer = webServer; - this.response = response; - this.request = request; -}//HttpMessageBuffer()// -}//HttpMessageBuffer// \ No newline at end of file diff --git a/Foundation Web Core/src/com/foundation/web/server/MessageBuffer.java b/Foundation Web Core/src/com/foundation/web/server/MessageBuffer.java index 143c678..941fc22 100644 --- a/Foundation Web Core/src/com/foundation/web/server/MessageBuffer.java +++ b/Foundation Web Core/src/com/foundation/web/server/MessageBuffer.java @@ -11,118 +11,118 @@ class MessageBuffer { /** The actual underlying buffer containing the bytes to be sent. Will be null if the message buffer needs initializing or has finished. */ private ByteBuffer buffer = null; /** The ability to chain message buffers into a linked list. */ -// private MessageBuffer next = null; + private MessageBuffer next = null; /** The optional response the message is based upon. */ private Response response = null; /** The content if there is any. */ private IContent content = null; -/** - * MessageBuffer constructor. - */ -public MessageBuffer() { -}//MessageBuffer()// -/** - * MessageBuffer constructor. - * @param buffer The buffer to use for assembling the message bytes. - */ -public MessageBuffer(ByteBuffer buffer) { - setBuffer(buffer); -}//MessageBuffer()// -/** - * Sets the actual underlying buffer for the message buffer. - * @param buffer - */ -protected void setBuffer(ByteBuffer buffer) { - this.buffer = buffer; - - if(buffer != null && buffer.position() != 0) buffer.flip(); -}//setBuffer()// -/** - * MessageBuffer constructor. - * @param buffer The buffer to use for assembling the message bytes. - * @param response The optional response that generates this message. - * @param content The content if the response is not just a header. - */ -public MessageBuffer(ByteBuffer buffer, Response response, IContent content) { - this.buffer = buffer; - this.content = content; - - //Fill the remaining buffer space with the content.// - if(content != null) { - content.get(buffer); - }//if// - - //Flip the buffer (if not already flipped) so we can write out the bytes.// - if(buffer.position() != 0) buffer.flip(); - this.response = response; -}//MessageBuffer()// -/** - * Initializes the message buffer for use. - * @return Whether initialization succeded. Intialization should be considered a success even if none is required or has already been performed. If it fails the caller should close the socket. - */ -public boolean initialize() { - //Does nothing by default. Subclasses may implement.// - return true; -}//initialize()// -/** - * Whether the message buffer is closed and all bytes have been sent. - * @return If the bytes have all been sent. - */ -public boolean isClosed() { - return buffer == null; -}//isClosed()// -/** - * Closes the message buffer. - */ -public void close() { - this.buffer = null; -}//close()// -/** - * Gets the byte buffer containing the current portion of the message to be sent. - * @return The buffer containing the next part of the message to be sent, or null if the message end has been reached. - */ -public ByteBuffer getBuffer() {return buffer;} -/** - * Loads the next part of the message into the buffer (any remaining bytes in the buffer will be compacted). - * @return Whether the buffer could be loaded with the next part of the message. If false, then the caller should try again in the future when additional message content may be available. Will always be false if there is no content to load from. - */ -public boolean loadBuffer() { - boolean result = true; - - if(buffer != null) { + /** + * MessageBuffer constructor. + */ + public MessageBuffer() { + }//MessageBuffer()// + /** + * MessageBuffer constructor. + * @param buffer The buffer to use for assembling the message bytes. + */ + public MessageBuffer(ByteBuffer buffer) { + setBuffer(buffer); + }//MessageBuffer()// + /** + * Sets the actual underlying buffer for the message buffer. + * @param buffer + */ + protected void setBuffer(ByteBuffer buffer) { + this.buffer = buffer; + + if(buffer != null && buffer.position() != 0) buffer.flip(); + }//setBuffer()// + /** + * MessageBuffer constructor. + * @param buffer The buffer to use for assembling the message bytes. + * @param response The optional response that generates this message. + * @param content The content if the response is not just a header. + */ + public MessageBuffer(ByteBuffer buffer, Response response, IContent content) { + this.buffer = buffer; + this.content = content; + + //Fill the remaining buffer space with the content.// if(content != null) { - int getResult; - - buffer.compact(); - getResult = content.get(buffer); - - if(getResult == IContent.CONTENT_PENDING) { - result = false; //Should never occur currently: See StreamedContent's javadocs.// - }//if// - else if(getResult == IContent.CONTENT_END) { - buffer = null; - }//else if// - - if(buffer != null && buffer.position() != 0) buffer.flip(); + content.get(buffer); }//if// - else if(!buffer.hasRemaining()) { - //Clear the buffer pointer indicating the message buffer is done.// - buffer = null; + + //Flip the buffer (if not already flipped) so we can write out the bytes.// + if(buffer.position() != 0) buffer.flip(); + this.response = response; + }//MessageBuffer()// + /** + * Initializes the message buffer for use. + * @return Whether initialization succeded. Intialization should be considered a success even if none is required or has already been performed. If it fails the caller should close the socket. + */ + public boolean initialize() { + //Does nothing by default. Subclasses may implement.// + return true; + }//initialize()// + /** + * Whether the message buffer is closed and all bytes have been sent. + * @return If the bytes have all been sent. + */ + public boolean isClosed() { + return buffer == null; + }//isClosed()// + /** + * Closes the message buffer. + */ + public void close() { + this.buffer = null; + }//close()// + /** + * Gets the byte buffer containing the current portion of the message to be sent. + * @return The buffer containing the next part of the message to be sent, or null if the message end has been reached. + */ + public ByteBuffer getBuffer() {return buffer;} + /** + * Loads the next part of the message into the buffer (any remaining bytes in the buffer will be compacted). + * @return Whether the buffer could be loaded with the next part of the message. If false, then the caller should try again in the future when additional message content may be available. Will always be false if there is no content to load from. + */ + public boolean loadBuffer() { + boolean result = true; + + if(buffer != null) { + if(content != null) { + int getResult; + + buffer.compact(); + getResult = content.get(buffer); + + if(getResult == IContent.CONTENT_PENDING) { + result = false; //Should never occur currently: See StreamedContent's javadocs.// + }//if// + else if(getResult == IContent.CONTENT_END) { + buffer = null; + }//else if// + + if(buffer != null && buffer.position() != 0) buffer.flip(); + }//if// + else if(!buffer.hasRemaining()) { + //Clear the buffer pointer indicating the message buffer is done.// + buffer = null; + result = false; + }//else if// + }//if// + else { result = false; - }//else if// - }//if// - else { - result = false; - }//else// - - return result; -}//loadBuffer()// -///** Gets the next message buffer (only used for pass through sockets). */ -//public MessageBuffer getNext() {return next;} -///** Sets the next message buffer (only used for pass through sockets). */ -//public void setNext(MessageBuffer next) {this.next = next;} -/** Gets the response object that created the message. This will be null for pass through sockets. */ -public Response getResponse() {return response;} + }//else// + + return result; + }//loadBuffer()// + /** Gets the next message buffer (only used for pass through sockets). */ + public MessageBuffer getNext() {return next;} + /** Sets the next message buffer (only used for pass through sockets). */ + public void setNext(MessageBuffer next) {this.next = next;} + /** Gets the response object that created the message. This will be null for pass through sockets. */ + public Response getResponse() {return response;} }//MessageBuffer// \ No newline at end of file diff --git a/Foundation Web Core/src/com/foundation/web/server/PassThroughSocketContext.java b/Foundation Web Core/src/com/foundation/web/server/PassThroughSocketContext.java index 56b8b99..ed41e8f 100644 --- a/Foundation Web Core/src/com/foundation/web/server/PassThroughSocketContext.java +++ b/Foundation Web Core/src/com/foundation/web/server/PassThroughSocketContext.java @@ -5,8 +5,6 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; - -import com.common.util.Queue; import com.foundation.web.server.WebServer.RegisterKeyRunnable; /** @@ -14,8 +12,8 @@ import com.foundation.web.server.WebServer.RegisterKeyRunnable; * Allows the web server to act as an SSL front to another web server or service. */ public class PassThroughSocketContext extends AbstractSocketContext { - private MessageBuffer outboundMessage = null; - private Queue outboundMessages = new Queue(8); + private MessageBuffer pendingMessageBuffer = null; + private MessageBuffer lastAddedMessageBuffer = null; /** The byte buffer used to read data from the socket. */ public ByteBuffer socketReadBuffer = ByteBuffer.allocate(BUFFER_SIZE); /** @@ -61,35 +59,47 @@ protected Object getLock() { */ protected synchronized void writeOutgoingMessages() throws IOException { //Actually this is called when a request is being sent via the pass through socket (sending the request to the remote server).// -// //Synchronized to avoid accessing the pendingMessageBuffer and lastAddedMessageBuffer at the same time as a thread that is calling passThrough(ByteBuffer) which also accesses these variables.// -// boolean result = true; -// -// if(result && pendingMessageBuffer != null) { -// //Check to see if the outbound message is prepared to send more content.// -// if(!pendingMessageBuffer.getBuffer().hasRemaining()) { -// //Wait until additional message bytes are available.// -// result = false; -// pendingMessageBuffer = null; -// lastAddedMessageBuffer = null; -// }//if// -// -// //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// -// while(result && (pendingMessageBuffer != null) && pendingMessageBuffer.getBuffer().hasRemaining()) { -// //Write the bytes to the stream.// -// ((SocketChannel) key.channel()).write(pendingMessageBuffer.getBuffer()); -// -// //If not all the bytes could be written then we will need to wait until we can write more.// -// if(pendingMessageBuffer.getBuffer().hasRemaining()) { -// result = false; -// }//if// -// else { -// //Wait until additional message bytes are available.// -// result = false; -// pendingMessageBuffer = null; -// lastAddedMessageBuffer = null; -// }//else// -// }//while// -// }//if// + //Synchronized to avoid accessing the pendingMessageBuffer and lastAddedMessageBuffer at the same time as a thread that is calling passThrough(ByteBuffer) which also accesses these variables.// + boolean result = true; + + if(result && pendingMessageBuffer != null) { + //Check to see if the outbound message is prepared to send more content.// + if(!pendingMessageBuffer.getBuffer().hasRemaining()) { + //Load the next pending outbound message in the chain.// + if(pendingMessageBuffer.getNext() != null) { + pendingMessageBuffer = pendingMessageBuffer.getNext(); + }//if// + else { + //Wait until additional message bytes are available.// + result = false; + pendingMessageBuffer = null; + lastAddedMessageBuffer = null; + }//else// + }//if// + + //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// + while(result && (pendingMessageBuffer != null) && pendingMessageBuffer.getBuffer().hasRemaining()) { + //Write the bytes to the stream.// + ((SocketChannel) key.channel()).write(pendingMessageBuffer.getBuffer()); + + //If not all the bytes could be written then we will need to wait until we can write more.// + if(pendingMessageBuffer.getBuffer().hasRemaining()) { + result = false; + }//if// + else { + //Load the next pending outbound message in the chain.// + if(pendingMessageBuffer.getNext() != null) { + pendingMessageBuffer = pendingMessageBuffer.getNext(); + }//if// + else { + //Wait until additional message bytes are available.// + result = false; + pendingMessageBuffer = null; + lastAddedMessageBuffer = null; + }//else// + }//else// + }//while// + }//if// }//processResponses()// /* (non-Javadoc) * @see com.foundation.web.server.WebServer.AbstractSocketContext#processRequest() @@ -126,25 +136,23 @@ protected void readIncomingMessages() throws IOException { * @see com.foundation.web.server.WebServer.AbstractSocketContext#passThrough(java.nio.ByteBuffer) */ protected synchronized boolean passThrough(ByteBuffer buffer) { -// ByteBuffer messageBytes = ByteBuffer.allocate(buffer.remaining()); -// MessageBuffer message; -// -// //Create a new buffer to hold the data so we don't modify the passed buffer (other than to update its position).// -// messageBytes = ByteBuffer.allocate(buffer.remaining()); -// messageBytes.put(buffer); -// message = new MessageBuffer(messageBytes); -// -// -// //Chain the message into the linked list. -// if(lastAddedMessageBuffer == null) { -// pendingMessageBuffer = lastAddedMessageBuffer = message; -// }//if// -// else { -// lastAddedMessageBuffer.setNext(message); -// lastAddedMessageBuffer = message; -// }//else// -// -// return true; + ByteBuffer messageBytes = ByteBuffer.allocate(buffer.remaining()); + MessageBuffer message; + + //Create a new buffer to hold the data so we don't modify the passed buffer (other than to update its position).// + messageBytes = ByteBuffer.allocate(buffer.remaining()); + messageBytes.put(buffer); + message = new MessageBuffer(messageBytes); + + //Chain the message into the linked list. + if(lastAddedMessageBuffer == null) { + pendingMessageBuffer = lastAddedMessageBuffer = message; + }//if// + else { + lastAddedMessageBuffer.setNext(message); + lastAddedMessageBuffer = message; + }//else// + return true; }//passThrough()// protected synchronized void close() { @@ -155,7 +163,6 @@ protected synchronized void close() { * @see com.foundation.web.server.WebServer.AbstractSocketContext#hasPendingWrite() */ protected boolean hasPendingWrite() { -// return pendingMessageBuffer != null; - return false; + return pendingMessageBuffer != null; }//hasPendingWrite()// }//PassThroughSocketContext// \ No newline at end of file diff --git a/Foundation Web Core/src/com/foundation/web/server/Response.java b/Foundation Web Core/src/com/foundation/web/server/Response.java index 117926c..a814dc2 100644 --- a/Foundation Web Core/src/com/foundation/web/server/Response.java +++ b/Foundation Web Core/src/com/foundation/web/server/Response.java @@ -26,6 +26,8 @@ import com.foundation.web.interfaces.*; public class Response implements IResponse { /** The sequence number for the request. */ private int requestNumber = 0; + /** Used to chain responses together when sending. */ + private Response nextResponse = null; /** The session associated with the response. */ private ISession session = null; /** The application that is serving the response. */ @@ -240,6 +242,20 @@ public String getCustomHeader() { public void setCustomHeader(String customHeader) { this.customHeader = customHeader; }//getCustomHeader()// +/** + * Gets the next response in the chain of responses. + * @return The next response to be sent. + */ +public Response getNextResponse() { + return nextResponse; +}//getNextResponse()// +/** + * Sets the next response in the chain of responses. + * @param nextResponse The response to send after this one. + */ +public void setNextResponse(Response nextResponse) { + this.nextResponse = nextResponse; +}//setNextResponse()// /** * Gets the ordered list of header field names. * @return The optional field names for the header. If provided then the server shouldn't generate a header for the response. 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 abdb407..e043a0d 100644 --- a/Foundation Web Core/src/com/foundation/web/server/SocketContext.java +++ b/Foundation Web Core/src/com/foundation/web/server/SocketContext.java @@ -70,14 +70,10 @@ public class SocketContext extends AbstractSocketContext implements IWebApplicat public ContentPart currentPart = null; /** Whether the final part boundary has already been found. If this is true then the message bytes should keep getting read until the partReadCount == contentLength. */ public boolean endPartBoundaryFound = false; -// /** The bytes containing the unencrypted outbound message that is waiting for the socket to allow a write. */ -// public MessageBuffer/*ByteBuffer*/ currentOutboundMessage = null; -// /** The last message buffer added to the pending outbound message chain (linked list). Used only for pass through contexts currently since locally handled messages link the reponses together into a list. */ -// private MessageBuffer lastOutboundMessage = null; - /** The queue of ready to be sent outbound messages. */ - private Queue outboundMessages = new Queue(6); - /** The currently active outbound message (pulled from the outboundMessages queue). */ - private MessageBuffer outboundMessage = null; + /** The bytes containing the unencrypted outbound message that is waiting for the socket to allow a write. */ + public MessageBuffer/*ByteBuffer*/ currentOutboundMessage = null; + /** The last message buffer added to the pending outbound message chain (linked list). Used only for pass through contexts currently since locally handled messages link the reponses together into a list. */ + private MessageBuffer lastOutboundMessage = null; /** The byte buffer used to read data from the socket. This must be null if a SSL engine is provided. */ public ByteBuffer socketReadBuffer = null; /** The buffer used to store the initial data in a SSL/TLS connection. The buffering is necessary to allow us to pre-read the client's hello message to gather the domain the client is connecting to - allowing for the correct SSL engine to be used. */ @@ -98,10 +94,10 @@ public class SocketContext extends AbstractSocketContext implements IWebApplicat public ByteBuffer encryptedWriteBuffer = null; /** The last used request number which identifies the sequence for the requests. */ private int lastRequestNumber = 0; -// /** The response we are currently processing. */ -// private Response currentResponse = null; -// /** The response we are will process last. */ -// private Response lastResponse = null; + /** The response we are currently processing. */ + private Response currentResponse = null; + /** The response we are will process last. */ + private Response lastResponse = null; /** Tracks the number of bytes sent from the current response. This is only used when debugging. */ private int sentBytes = 0; /** Tracks the getWebServer().debug output for the current request/response cycle. This is only used when debugging. */ @@ -193,7 +189,7 @@ protected synchronized void close() { try {if(key != null) key.cancel();} catch(Throwable e) {} //Clean up after the response and request.// //try {while(currentOutboundMessage != null) {currentOutboundMessage.close(); currentOutboundMessage = currentOutboundMessage.getNext();}} catch(Throwable e2) {} -// try {if(currentResponse != null) currentResponse.close();} catch(Throwable e2) {} + try {if(currentResponse != null) currentResponse.close();} catch(Throwable e2) {} if(getPassThroughSocketContext() != null) { getPassThroughSocketContext().close(); @@ -236,14 +232,22 @@ private void queueOutboundClientMessage(MessageBuffer messageBuffer) { boolean notify = false; synchronized(this) { - outboundMessages.enqueue(messageBuffer); + if(currentOutboundMessage == null) { + lastOutboundMessage = currentOutboundMessage = messageBuffer; + notify = true; + }//if// + else { + lastOutboundMessage.setNext(messageBuffer); + lastOutboundMessage = messageBuffer; + }//else// }//synchronized()// if(notify) { notifyListenerOfPendingWrite(); }//if// }//queueOutboundClientMessage()// -private void writeSessionCookies(Response response, PrintStream pout) { +private void writeSessionCookies(PrintStream pout) { + Response response = currentResponse; ISession session = response.getSession(); if(session != null) { @@ -267,7 +271,8 @@ private void writeSessionCookies(Response response, PrintStream pout) { *

Note: The caller must synchronize on this context to prevent multiple threads from accessing the context at the same time.

* @result Whether request is in a receive state. Will be false if the request generated a response that could not be completely transmitted. */ -private void prepareResponse(Response response) { +private void prepareResponse() { + Response response = currentResponse; Request request = (Request) response.getRequest(); byte[] headerBytes = null; IContent content = null; @@ -303,7 +308,7 @@ private void prepareResponse(Response response) { }//for// //Write out any cookies necessary to retain our session.// - writeSessionCookies(response, pout); + writeSessionCookies(pout); //End the header.// pout.print("\r\n"); @@ -326,7 +331,7 @@ private void prepareResponse(Response response) { pout.print("HTTP/1.1 302 Moved Temporarily\r\n"); //}//else// - writeSessionCookies(response, pout); + writeSessionCookies(pout); pout.print("Location: " + response.getForwardUri() + "\r\n"); @@ -418,7 +423,7 @@ private void prepareResponse(Response response) { }//if// }//if// - writeSessionCookies(response, pout); + writeSessionCookies(pout); pout.print("Server: DE/1.0\r\n"); //TODO: IE has a problem with caching and forwarding/redirecting. A page that redirects to another page that was previously cached does not result in IE sending a request for the forwarded content.// @@ -482,7 +487,7 @@ private void prepareResponse(Response response) { pout.print("HTTP/1.1 200 OK\r\n"); }//else// - writeSessionCookies(response, pout); + writeSessionCookies(pout); pout.print("Content-Length: 0\r\n"); pout.print("Server: DE/1.0\r\n"); pout.print("\r\n"); @@ -511,7 +516,7 @@ private void prepareResponse(Response response) { // }//if// //Save the buffer as the current pending outbound message for this socket context.// - queueOutboundClientMessage(new MessageBuffer(buffer, response, content != null && request.getRequestType() != Request.TYPE_HEAD ? content : null)); + currentOutboundMessage = new MessageBuffer(buffer, response, content != null && request.getRequestType() != Request.TYPE_HEAD ? content : null); }//try// catch(Throwable e) { Debug.log("Fatal Error: Failed to build and send the response message due to an exception.", e); @@ -528,7 +533,18 @@ private void prepareResponse(Response response) { * @result Whether request is in a receive state. Will be false if the request generated a response that could not be completely transmitted. */ public synchronized boolean sendHttpResponse(Response response) { - prepareResponse(response); + if(currentResponse != null) { + lastResponse.setNextResponse(response); + lastResponse = response; + }//if// + else { + lastResponse = currentResponse = response; + sentBytes = 0; + prepareResponse(); + + //Note: Not going to process the response on this thread. Allow the flag to be set for writing to the socket, and have the next thread in the network listener handle the write. This allows for cleaner code and pipelining without all the synchronizing. +// result = internalProcessResponses(); + }//else// request = null; @@ -546,7 +562,14 @@ protected synchronized boolean passThrough(ByteBuffer buffer) { messageBytes.put(buffer); message = new MessageBuffer(messageBytes); - queueOutboundClientMessage(message); + //Chain the message into the linked list. + if(lastOutboundMessage == null || currentOutboundMessage == null) { + currentOutboundMessage = lastOutboundMessage = message; + }//if// + else { + lastOutboundMessage.setNext(message); + lastOutboundMessage = message; + }//else// return true; }//passThrough()// @@ -561,12 +584,12 @@ protected void writeOutgoingMessages() throws IOException { }//synchronized// }//if// else if(isWebsocket) { -// //Right after upgrading the socket we have one last HTTP response to process.// -// if(currentResponse != null) { -// internalProcessResponses(); -// }//if// -// -// internalProcessWebsocketMessages(); + //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.// @@ -578,30 +601,30 @@ protected void writeOutgoingMessages() throws IOException { * 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(currentOutboundMessage != null) { -// //Put the sending message in a MessageBuffer (pendingOutboundMessage).// -// currentOutboundMessage = new MessageBuffer(websocketSendingMessage); -// }//if// -// -// //Write the pendingOutboundMessage to the socket.// -// if(writeClientBoundMessage()) { -// websocketSendingMessage = null; -// currentOutboundMessage = null; -// }//if// -// }//if// -// -// //If we finished sending the message then load the next one.// -// if(websocketSendingMessage == null) { -// loadNextWebsocketMessage(); -// }//if// -// }//while// + 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(currentOutboundMessage != null) { + //Put the sending message in a MessageBuffer (pendingOutboundMessage).// + currentOutboundMessage = new MessageBuffer(websocketSendingMessage); + }//if// + + //Write the pendingOutboundMessage to the socket.// + if(writeClientBoundMessage()) { + websocketSendingMessage = null; + currentOutboundMessage = 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. @@ -703,46 +726,42 @@ private void loadNextWebsocketMessage() { private synchronized void internalProcessResponses() { boolean finishedSending = true; - while(finishedSending) { - finishedSending = writeClientBoundMessage(); + //Keep sending responses while the buffers are not full and there is another response to send.// + while(finishedSending && currentResponse != null) { + //If the socket is open then send the next buffer of data.// + if(key.channel().isOpen()) { + //Send the pending response object's prepared buffer of data.// + finishedSending = writeClientBoundMessage(); + }//if// + + //Close the response if successfully sent, or if the socket is closed.// + if(finishedSending || !key.channel().isOpen()) { + try {currentResponse.close();} catch(Throwable e) {} + }//if// + + //If we finished sending the current response then load the next one.// + if(finishedSending) { + currentResponse = currentResponse.getNextResponse(); + + if(currentResponse == null) { + lastResponse = null; + }//if// + else if(key.channel().isOpen()) { + //Prep the next response object for sending.// + prepareResponse(); + }//else// + else { + //Clean up after all the left over responses.// + while(currentResponse != null) { + currentResponse.close(); + currentResponse = currentResponse.getNextResponse(); + }//while// + + currentResponse = null; + lastResponse = null; + }//else// + }//if// }//while// - -// //Keep sending responses while the buffers are not full and there is another response to send.// -// while(finishedSending && currentResponse != null) { -// //If the socket is open then send the next buffer of data.// -// if(key.channel().isOpen()) { -// //Send the pending response object's prepared buffer of data.// -// finishedSending = writeClientBoundMessage(); -// }//if// -// -// //Close the response if successfully sent, or if the socket is closed.// -// if(finishedSending || !key.channel().isOpen()) { -// try {currentResponse.close();} catch(Throwable e) {} -// }//if// -// -// //If we finished sending the current response then load the next one.// -// if(finishedSending) { -// currentResponse = currentResponse.getNextResponse(); -// -// if(currentResponse == null) { -// lastResponse = null; -// }//if// -// else if(key.channel().isOpen()) { -// //Prep the next response object for sending.// -// prepareResponse(currentResponse); -// }//else// -// else { -// //Clean up after all the left over responses.// -// while(currentResponse != null) { -// currentResponse.close(); -// currentResponse = currentResponse.getNextResponse(); -// }//while// -// -// currentResponse = null; -// lastResponse = null; -// }//else// -// }//if// -// }//while// }//processCurrentResponse()// /** * Sends a response to the client. @@ -751,243 +770,244 @@ private synchronized void internalProcessResponses() { private boolean writeClientBoundMessage() { boolean sendMore = true; - //Process SSL output first.// - if(sslEngine != null) { - sendMore = writeClientBoundSslMessage(); - }//if// - else { - sendMore = writeClientBoundPlainMessage(); - }//else// - - return sendMore; -}//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() { - boolean sendMore = true; - - try { - //If we have part of an SSL frame then try to send it first.// - if(encryptedWriteBuffer.hasRemaining()) { - int remaining = encryptedWriteBuffer.remaining(); +// if(getWebServer().debug) { +// debugBuffer.append("Starting a write cycle.\n"); +// }//if// - //Write the bytes to the stream.// - ((SocketChannel) key.channel()).write(encryptedWriteBuffer); - - //Check to see if we failed to send the whole frame.// + try { + //Process SSL output first.// + if(sslEngine != null) { + //If we have part of an SSL frame then try to send it first.// if(encryptedWriteBuffer.hasRemaining()) { - sendMore = false; - }//if// - }//if// - - while(sendMore && sslNeedsWrap) { - SSLEngineResult handshakeResult; - - //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()); - //Generate the handshake message.// - handshakeResult = sslEngine.wrap(ByteBuffer.allocate(0), encryptedWriteBuffer); - encryptedWriteBuffer.flip(); - - if(handshakeResult.getStatus() == Status.BUFFER_OVERFLOW) { - //Should never happen.// - Debug.log(new RuntimeException("Unexpected ssl engine buffer overflow.")); - }//if// - else if(handshakeResult.getStatus() == Status.BUFFER_UNDERFLOW) { - //Should never happen.// - Debug.log(new RuntimeException("Unexpected ssl engine buffer underflow.")); - }//else if// - else if(handshakeResult.getStatus() == Status.CLOSED) { - //Should never happen.// - 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) {} - }//else if// - else if(handshakeResult.getStatus() == Status.OK) { - if(handshakeResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { - //Should never happen.// - 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); - -// if(getWebServer().debug) { -// debugBuffer.append("Sent " + (remaining - encryptedWriteBuffer.remaining()) + " encrypted bytes.\n"); -// }//if// - - //If not all the bytes could be written then we will need to wait until we can write more.// - if(encryptedWriteBuffer.hasRemaining()) { -// if(getWebServer().debug) { -// debugBuffer.append("Pausing due to a partially sent packet (while ssl handshaking). Bytes actually sent: " + encryptedWriteBuffer.position() + ". Bytes remaining: " + encryptedWriteBuffer.remaining() + ".\n"); -// }//if// - - //Leave the data in the encrypted write buffer for the writing operation to send it.// - sendMore = false; - }//if// - - //Update the SSL needs wrap flag.// - if(handshakeResult.getHandshakeStatus() != HandshakeStatus.NEED_WRAP) { - sslNeedsWrap = false; - }//if// - }//else if// - else { - sslNeedsWrap = false; - }//else// - }//else if// - else { - //Should never happen.// - Debug.log(new RuntimeException("Unexpected ssl engine status code.")); - }//else// - }//while// - - if(sendMore) { - //Prepare a new message for sending.// - if(outboundMessage == null && outboundMessages.getSize() > 0) { - outboundMessage = (MessageBuffer) outboundMessages.dequeue(); - }//if// - - //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(!outboundMessage.getBuffer().hasRemaining()) { - if(!outboundMessage.loadBuffer()) { + int remaining = encryptedWriteBuffer.remaining(); + + //Write the bytes to the stream.// + ((SocketChannel) key.channel()).write(encryptedWriteBuffer); + +// if(getWebServer().debug) { +// debugBuffer.append("Wrote " + (remaining - encryptedWriteBuffer.remaining()) + " encrypted bytes to the stream. " + encryptedWriteBuffer.remaining() + " remain.\n"); +// }//if// + + //Check to see if we failed to send the whole frame.// + if(encryptedWriteBuffer.hasRemaining()) { sendMore = false; }//if// - - if(outboundMessage.getBuffer() == null) { - outboundMessage = null; - }//if// }//if// - //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// - while(key.channel().isOpen() && sendMore && outboundMessage != null && outboundMessage.getBuffer().hasRemaining()) { - SSLEngineResult encryptResult; - //Reset the encrypted write buffer.// - encryptedWriteBuffer.compact(); - //Encrypt the next message frame.// - encryptResult = sslEngine.wrap(outboundMessage.getBuffer(), encryptedWriteBuffer); + while(sendMore && sslNeedsWrap) { + SSLEngineResult handshakeResult; + + //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()); + //Generate the handshake message.// + handshakeResult = sslEngine.wrap(ByteBuffer.allocate(0), encryptedWriteBuffer); encryptedWriteBuffer.flip(); - if(encryptResult.getStatus() == Status.BUFFER_OVERFLOW) { + if(handshakeResult.getStatus() == Status.BUFFER_OVERFLOW) { //Should never happen.// Debug.log(new RuntimeException("Unexpected ssl engine buffer overflow.")); }//if// - else if(encryptResult.getStatus() == Status.BUFFER_UNDERFLOW) { + else if(handshakeResult.getStatus() == Status.BUFFER_UNDERFLOW) { //Should never happen.// Debug.log(new RuntimeException("Unexpected ssl engine buffer underflow.")); }//else if// - else if(encryptResult.getStatus() == Status.CLOSED) { + else if(handshakeResult.getStatus() == Status.CLOSED) { + //Should never happen.// + 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 {key.channel().close();}catch(Throwable e2) {} }//else if// - else if(encryptResult.getStatus() == Status.OK) { - //Write the bytes to the stream.// - try { + else if(handshakeResult.getStatus() == Status.OK) { + if(handshakeResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + //Should never happen.// + 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); - }//try// - catch(IOException e) { - //Caught if the channel is forcably closed by the client. We will ignore it.// - }//catch// - - //If not all the bytes could be written then we will need to wait until we can write more.// - if(encryptedWriteBuffer.hasRemaining()) { - //Leave the data in the encrypted write buffer for the writing operation to send it.// - sendMore = false; - }//if// + +// if(getWebServer().debug) { +// debugBuffer.append("Sent " + (remaining - encryptedWriteBuffer.remaining()) + " encrypted bytes.\n"); +// }//if// + + //If not all the bytes could be written then we will need to wait until we can write more.// + if(encryptedWriteBuffer.hasRemaining()) { +// if(getWebServer().debug) { +// debugBuffer.append("Pausing due to a partially sent packet (while ssl handshaking). Bytes actually sent: " + encryptedWriteBuffer.position() + ". Bytes remaining: " + encryptedWriteBuffer.remaining() + ".\n"); +// }//if// + + //Leave the data in the encrypted write buffer for the writing operation to send it.// + sendMore = false; + }//if// + + //Update the SSL needs wrap flag.// + if(handshakeResult.getHandshakeStatus() != HandshakeStatus.NEED_WRAP) { + sslNeedsWrap = false; + }//if// + }//else if// + else { + sslNeedsWrap = false; + }//else// }//else if// else { //Should never happen.// Debug.log(new RuntimeException("Unexpected ssl engine status code.")); }//else// - - //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() && outboundMessage != null) { - if(!outboundMessage.loadBuffer()) { - //Wait until additional message bytes are available.// - sendMore = false; - }//if// - - //If the message end has been reached then the buffer will be null.// - if(outboundMessage.getBuffer() == null) { - outboundMessage = null; - }//if// - }//if// }//while// - }//if// - }//try// - catch(ClosedChannelException e) { - close(); - }//catch// - catch(SSLException e) { - if(getWebServer().debug) { - Debug.log(e); + +// if(getWebServer().debug) { +// debugBuffer.append("End Handshaking SSL\n"); +// }//if// }//if// - close(); - }//catch// - catch(IOException e) { - if(getWebServer().debug) { - Debug.log(e); - }//if// - - close(); - }//catch// - - return sendMore; -}//writeClientBoundSslMessage()// -/** - * 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 writeClientBoundPlainMessage() { - boolean sendMore = true; - - try { - //Prepare a new message for sending.// - if(outboundMessage == null && outboundMessages.getSize() > 0) { - outboundMessage = (MessageBuffer) outboundMessages.dequeue(); - }//if// - - //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(!outboundMessage.getBuffer().hasRemaining()) { - if(!outboundMessage.loadBuffer()) { - sendMore = false; + if(sendMore && currentOutboundMessage != null) { + //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// + }//if// + + if(currentOutboundMessage.getBuffer() == null) { + currentOutboundMessage = null; + lastOutboundMessage = null; + }//if// }//if// - if(outboundMessage.getBuffer() == null) { - outboundMessage = null; + //If we have an application response pending then send it now.// + if(sendMore && currentOutboundMessage.getBuffer().hasRemaining()) { + if(sslEngine != null) { + //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// + while(key.channel().isOpen() && sendMore && (currentOutboundMessage != null) && currentOutboundMessage.getBuffer().hasRemaining()) { + SSLEngineResult encryptResult; +// int offset = pendingOutboundMessage.getBuffer().position(); +//TODO: Comment me. +//int rem = pendingOutboundMessage.getBuffer().remaining(); + //Reset the encrypted write buffer.// + encryptedWriteBuffer.compact(); + //Encrypt the next message frame.// + encryptResult = sslEngine.wrap(currentOutboundMessage.getBuffer(), encryptedWriteBuffer); + encryptedWriteBuffer.flip(); +//TODO: Comment me. +//Debug.log("Encrypting/Sending to client from Git " + (rem - pendingOutboundMessage.getBuffer().remaining()) + " bytes."); + +// if(getWebServer().debug) { +// sentBytes += (pendingOutboundMessage.position() - offset); +// debugBuffer.append("Encrypted: " + (pendingOutboundMessage.position() - offset) + ". Total Encrypted: " + sentBytes + ". Encrypted size: " + encryptedWriteBuffer.limit() + ".\n"); +// }//if// + + if(encryptResult.getStatus() == Status.BUFFER_OVERFLOW) { + //Should never happen.// + Debug.log(new RuntimeException("Unexpected ssl engine buffer overflow.")); + }//if// + else if(encryptResult.getStatus() == Status.BUFFER_UNDERFLOW) { + //Should never happen.// + Debug.log(new RuntimeException("Unexpected ssl engine buffer underflow.")); + }//else if// + else if(encryptResult.getStatus() == Status.CLOSED) { + //Should never happen.// +// 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) {} + }//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// + }//try// + catch(IOException e) { + //Caught if the channel is forcably closed by the client. We will ignore it.// + }//catch// + + //If not all the bytes could be written then we will need to wait until we can write more.// + 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 { + //Should never happen.// + Debug.log(new RuntimeException("Unexpected ssl engine status code.")); + }//else// + + //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(!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// + }//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// + }//if// + else { + //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// + while(sendMore && (currentOutboundMessage != null) && currentOutboundMessage.getBuffer().hasRemaining()) { + //Write the bytes to the stream.// + ((SocketChannel) key.channel()).write(currentOutboundMessage.getBuffer()); + +// if(getWebServer().debug) { +// sentBytes += pendingOutboundMessage.position(); +// debugBuffer.append("Wrote " + pendingOutboundMessage.position() + " bytes to the client. Total sent: " + sentBytes + "\n"); +// }//if// + + //If not all the bytes could be written then we will need to wait until we can write more.// + if(currentOutboundMessage.getBuffer().hasRemaining()) { + sendMore = false; + }//if// + else { + 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// + }//if// + + //If the message end has been reached then the buffer will be null.// + if(currentOutboundMessage.getBuffer() == null) { + currentOutboundMessage = null; + lastOutboundMessage = null; + }//if// + }//else// + }//while// + }//else// }//if// }//if// - - //Keep sending encrypted frames until the output buffer is full, or we run out of message to send.// - while(sendMore && outboundMessage != null && outboundMessage.getBuffer().hasRemaining()) { - //Write the bytes to the stream.// - ((SocketChannel) key.channel()).write(outboundMessage.getBuffer()); - - //If not all the bytes could be written then we will need to wait until we can write more.// - if(outboundMessage.getBuffer().hasRemaining()) { - sendMore = false; - }//if// - else { - if(!outboundMessage.loadBuffer()) { - //Wait until additional message bytes are available.// - sendMore = false; - }//if// - - //If the message end has been reached then the buffer will be null.// - if(outboundMessage.getBuffer() == null) { - outboundMessage = null; - }//if// - }//else// - }//while// }//try// catch(ClosedChannelException e) { close(); @@ -1008,7 +1028,7 @@ private boolean writeClientBoundPlainMessage() { }//catch// return sendMore; -}//writeClientBoundPlainMessage()// +}//writeClientBoundMessage()// /* (non-Javadoc) * @see com.foundation.web.server.WebServer.AbstractSocketContext#processRequest() */ @@ -2523,8 +2543,7 @@ private int indexOf(byte[] source, byte[] pattern, int fromOffset) { * @see com.foundation.web.server.WebServer.AbstractSocketContext#hasPendingWrite() */ protected boolean hasPendingWrite() { - //return currentOutboundMessage != null || (encryptedWriteBuffer != null && encryptedWriteBuffer.hasRemaining()); - return outboundMessage != null || outboundMessages.getSize() > 0 || (encryptedWriteBuffer != null && encryptedWriteBuffer.hasRemaining()); + return currentOutboundMessage != null || (encryptedWriteBuffer != null && encryptedWriteBuffer.hasRemaining()); }//hasPendingWrite()// /* (non-Javadoc) * @see com.foundation.web.interfaces.IConnectionContext#upgradeToWebsocket(java.lang.String, long, com.foundation.web.interfaces.WebsocketHandler)