Fixed bugs in web server's handling of streamed content (logic was incorrectly handling non-blocking IO in the stream - interpreting it as a stream closed). Modified StreamedContent to use blocking IO instead of non-blocking since it was wasting CPU cycles (non-blocking IO done properly would require removing the write flag from the client socket and listening to the StreamedContent for the availablity of content before re-flagging the socket for writing). Using non-blocking IO here really isn't that useful since the content source should be trusted and timely, and multiple threads service clients (a few threads blocking for a few milliseconds is no big deal), and implementing it properly would significantly increase code complexity.
This commit is contained in:
@@ -716,7 +716,7 @@ public class WebServer {
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
try {
|
||||
//Wrap the response in http cloths.//
|
||||
//Wrap the response in http cloths. The HeaderFieldNames will be set if the response was provided with a completely custom HTTP header to be used.//
|
||||
if(response.getHeaderFieldNames() != null) {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
@@ -815,104 +815,93 @@ public class WebServer {
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
if(!response.isError() && response.getHeader() != null) {
|
||||
//Include all but the last end of line.//
|
||||
pout.print(response.getHeader().substring(0, response.getHeader().length() - 2));
|
||||
writeSessionCookies(pout);
|
||||
//Add a final terminating end of line.//
|
||||
pout.print("\r\n");
|
||||
if(response.isError()) {
|
||||
if(response.getHeader() != null) {
|
||||
pout.print(response.getHeader());
|
||||
}//if//
|
||||
else {
|
||||
pout.print("HTTP/1.1 404 Resource Not Found\r\n");
|
||||
}//else//
|
||||
}//if//
|
||||
else if(response.getCustomHeader() != null) {
|
||||
pout.print(response.getCustomHeader());
|
||||
}//else if//
|
||||
else if(isDownloaded && request.getRange() != null) {
|
||||
pout.print("HTTP/1.1 206 Partial Content\r\n");
|
||||
}//else if//
|
||||
else {
|
||||
if(response.isError()) {
|
||||
if(response.getHeader() != null) {
|
||||
pout.print(response.getHeader());
|
||||
}//if//
|
||||
else {
|
||||
pout.print("HTTP/1.1 404 Resource Not Found\r\n");
|
||||
}//else//
|
||||
}//if//
|
||||
else if(response.getCustomHeader() != null) {
|
||||
pout.print(response.getCustomHeader());
|
||||
}//else if//
|
||||
else if(isDownloaded && request.getRange() != null) {
|
||||
pout.print("HTTP/1.1 206 Partial Content\r\n");
|
||||
}//else if//
|
||||
else {
|
||||
pout.print("HTTP/1.1 200 OK\r\n");
|
||||
}//else//
|
||||
|
||||
pout.print("Content-Length: " + (content != null ? content.getSize() : 0) + "\r\n");
|
||||
|
||||
if(compress) {
|
||||
//TODO: Add others?
|
||||
if(compressionType == 1) {
|
||||
content = new GzipContent(content);
|
||||
pout.print("Content-Encoding: gzip\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
if(content != null) {
|
||||
//Note: The character set gives IE indigestion for some reason.//
|
||||
pout.print("Content-Type: " + (mimeType != null ? mimeType.getMimeName() : "text/html") + "; charset=" + (response.getCharacterSet() == null ? "UTF-8" : response.getCharacterSet()) + "\r\n");
|
||||
cacheDirective = content.getCacheDirective();
|
||||
|
||||
if(isDownloaded) {
|
||||
pout.print("Content-Disposition: attachment; filename=\"" + content.getDownloadName() + "\";\r\n");
|
||||
pout.print("Accept-Ranges: bytes\r\n");
|
||||
|
||||
if(request.getRange() != null) {
|
||||
// Debug.log("Sending a ranged response: " + request.getRange() + " content range: (" + content.getStart() + " - " + content.getEnd() + "/" + content.getSize() + ").");
|
||||
pout.print("Range: " + request.getRange() + "\r\n");
|
||||
pout.print("Content-Range: bytes " + content.getStart() + "-" + content.getEnd() + "/" + content.getSize() + "\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
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.//
|
||||
//private / no-cache
|
||||
|
||||
if(content.getExpiresDirective() != null) {
|
||||
pout.print("Expires: " + getHttpDateFormat().format(content.getExpiresDirective()));
|
||||
}//if//
|
||||
|
||||
if(cacheDirective != null) {
|
||||
pout.print("Cache-Control: " + cacheDirective + "\r\n");
|
||||
}//if//
|
||||
else {
|
||||
int cacheLength = content.getCacheLength() != null ? content.getCacheLength().intValue() : mimeType != null ? mimeType.getDefaultCacheLength() : IMimeType.CACHE_LENGTH_NEVER_CACHE;
|
||||
|
||||
if(cacheLength > 0) {
|
||||
pout.print("Cache-Control: public, max-age=" + cacheLength + "\r\n");
|
||||
}//if//
|
||||
else if(cacheLength == IMimeType.CACHE_LENGTH_ALWAYS_TEST) {
|
||||
pout.print("Cache-Control: public, pre-check=0, post-check=120\r\n");
|
||||
}//else if//
|
||||
else if(cacheLength == IMimeType.CACHE_LENGTH_NEVER_CACHE) {
|
||||
pout.print("Cache-Control: no-cache\r\n");
|
||||
}//else if//
|
||||
else {
|
||||
pout.print("Cache-Control: no-store\r\n");
|
||||
}//else//
|
||||
}//else//
|
||||
|
||||
//TODO: Determine if we need to use age.
|
||||
//pout.print("Age: 0\r\n");
|
||||
//TODO: Determine if we need to use ETags
|
||||
|
||||
if(lastModifiedDate != null) {
|
||||
SimpleDateFormat format = getHttpDateFormat();
|
||||
|
||||
pout.print("Last-Modified: " + format.format(lastModifiedDate) + "\r\n");
|
||||
pout.print("Date: " + format.format(new Date()) + "\r\n");
|
||||
}//if//
|
||||
|
||||
pout.print("\r\n");
|
||||
pout.print("HTTP/1.1 200 OK\r\n");
|
||||
}//else//
|
||||
|
||||
pout.print("Content-Length: " + (content != null ? content.getSize() : 0) + "\r\n");
|
||||
|
||||
if(compress) {
|
||||
//TODO: Add others?
|
||||
if(compressionType == 1) {
|
||||
content = new GzipContent(content);
|
||||
pout.print("Content-Encoding: gzip\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
if(content != null) {
|
||||
//Note: The character set gives IE indigestion for some reason.//
|
||||
pout.print("Content-Type: " + (mimeType != null ? mimeType.getMimeName() : "text/html") + "; charset=" + (response.getCharacterSet() == null ? "UTF-8" : response.getCharacterSet()) + "\r\n");
|
||||
cacheDirective = content.getCacheDirective();
|
||||
|
||||
if(isDownloaded) {
|
||||
pout.print("Content-Disposition: attachment; filename=\"" + content.getDownloadName() + "\";\r\n");
|
||||
pout.print("Accept-Ranges: bytes\r\n");
|
||||
|
||||
if(request.getRange() != null) {
|
||||
// Debug.log("Sending a ranged response: " + request.getRange() + " content range: (" + content.getStart() + " - " + content.getEnd() + "/" + content.getSize() + ").");
|
||||
pout.print("Range: " + request.getRange() + "\r\n");
|
||||
pout.print("Content-Range: bytes " + content.getStart() + "-" + content.getEnd() + "/" + content.getSize() + "\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
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.//
|
||||
//private / no-cache
|
||||
|
||||
if(content.getExpiresDirective() != null) {
|
||||
pout.print("Expires: " + getHttpDateFormat().format(content.getExpiresDirective()));
|
||||
}//if//
|
||||
|
||||
if(cacheDirective != null) {
|
||||
pout.print("Cache-Control: " + cacheDirective + "\r\n");
|
||||
}//if//
|
||||
else {
|
||||
int cacheLength = content.getCacheLength() != null ? content.getCacheLength().intValue() : mimeType != null ? mimeType.getDefaultCacheLength() : IMimeType.CACHE_LENGTH_NEVER_CACHE;
|
||||
|
||||
if(cacheLength > 0) {
|
||||
pout.print("Cache-Control: public, max-age=" + cacheLength + "\r\n");
|
||||
}//if//
|
||||
else if(cacheLength == IMimeType.CACHE_LENGTH_ALWAYS_TEST) {
|
||||
pout.print("Cache-Control: public, pre-check=0, post-check=120\r\n");
|
||||
}//else if//
|
||||
else if(cacheLength == IMimeType.CACHE_LENGTH_NEVER_CACHE) {
|
||||
pout.print("Cache-Control: no-cache\r\n");
|
||||
}//else if//
|
||||
else {
|
||||
pout.print("Cache-Control: no-store\r\n");
|
||||
}//else//
|
||||
}//else//
|
||||
|
||||
//TODO: Determine if we need to use age.
|
||||
//pout.print("Age: 0\r\n");
|
||||
//TODO: Determine if we need to use ETags
|
||||
|
||||
if(lastModifiedDate != null) {
|
||||
SimpleDateFormat format = getHttpDateFormat();
|
||||
|
||||
pout.print("Last-Modified: " + format.format(lastModifiedDate) + "\r\n");
|
||||
pout.print("Date: " + format.format(new Date()) + "\r\n");
|
||||
}//if//
|
||||
|
||||
pout.print("\r\n");
|
||||
headerBytes = bout.toByteArray();
|
||||
}//else if//
|
||||
else {
|
||||
@@ -1215,26 +1204,26 @@ public class WebServer {
|
||||
//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(result && !pendingOutboundMessage.getBuffer().hasRemaining()) {
|
||||
//Attempt to load additional message bytes into the buffer.//
|
||||
if(!pendingOutboundMessage.loadBuffer()) {
|
||||
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
||||
|
||||
//The buffer will be set to null if there are no more bytes to be read from the stream (ever).//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
//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(pendingOutboundMessage.getNext() != null) {
|
||||
pendingOutboundMessage = pendingOutboundMessage.getNext();
|
||||
//TODO: Comment me.
|
||||
//Debug.log("Getting next pending outbound message in linked list to send to the client from git.");
|
||||
}//if//
|
||||
//Tell the caller that all messages have been sent.//
|
||||
else {
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
result = true;
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
//If the message end has been reached then the buffer will be null.//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
//TODO: Comment me.
|
||||
//Debug.log("Last pending outbound message sent to client from git.");
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
}//if//
|
||||
//Note: Currently this will never happen since it is a waste of CPU cycles. See comments in StreamedContent's class javadocs. We'd need to remove the write flag from the socket and listen for a read flag on the stream before re-setting the write flag on the socket.//
|
||||
else if(!couldLoadAdditionalBytes) {
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
}//else if//
|
||||
}//if//
|
||||
|
||||
//If we have an application response pending then send it now.//
|
||||
@@ -1307,28 +1296,27 @@ public class WebServer {
|
||||
//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() && pendingOutboundMessage != null) {
|
||||
if(!pendingOutboundMessage.loadBuffer()) {
|
||||
//Attempt to load additional message bytes into the buffer.//
|
||||
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
||||
|
||||
//The buffer will be set to null if there are no more bytes to be read from the stream (ever).//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
//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(pendingOutboundMessage.getNext() != null) {
|
||||
pendingOutboundMessage = pendingOutboundMessage.getNext();
|
||||
//TODO: Comment me.
|
||||
//Debug.log("Getting next pending outbound message in linked list to send to the client from git.");
|
||||
}//if//
|
||||
//Tell the caller that all messages have been sent.//
|
||||
else {
|
||||
//TODO: Comment me.
|
||||
//Debug.log("No next pending outbound message in linked list to send to the client from git.");
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
result = true;
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
//If the message end has been reached then the buffer will be null.//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
//TODO: Comment me.
|
||||
//Debug.log("Last pending outbound message sent to client from git.");
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
}//if//
|
||||
//Note: Currently this will never happen since it is a waste of CPU cycles. See comments in StreamedContent's class javadocs. We'd need to remove the write flag from the socket and listen for a read flag on the stream before re-setting the write flag on the socket.//
|
||||
else if(!couldLoadAdditionalBytes) {
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
}//else if//
|
||||
}//if//
|
||||
}//while//
|
||||
}//if//
|
||||
@@ -1348,22 +1336,27 @@ public class WebServer {
|
||||
result = false;
|
||||
}//if//
|
||||
else {
|
||||
if(!pendingOutboundMessage.loadBuffer()) {
|
||||
//Attempt to load additional message bytes into the buffer.//
|
||||
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
||||
|
||||
//The buffer will be set to null if there are no more bytes to be read from the stream (ever).//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
//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(pendingOutboundMessage.getNext() != null) {
|
||||
pendingOutboundMessage = pendingOutboundMessage.getNext();
|
||||
}//if//
|
||||
//Tell the caller that all messages have been sent.//
|
||||
else {
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
result = true;
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
//If the message end has been reached then the buffer will be null.//
|
||||
if(pendingOutboundMessage.getBuffer() == null) {
|
||||
pendingOutboundMessage = null;
|
||||
lastAddedMessageBuffer = null;
|
||||
}//if//
|
||||
//Note: Currently this will never happen since it is a waste of CPU cycles. See comments in StreamedContent's class javadocs. We'd need to remove the write flag from the socket and listen for a read flag on the stream before re-setting the write flag on the socket.//
|
||||
else if(!couldLoadAdditionalBytes) {
|
||||
//Wait until additional message bytes are available.//
|
||||
result = false;
|
||||
}//else if//
|
||||
}//else//
|
||||
}//while//
|
||||
}//else//
|
||||
|
||||
Reference in New Issue
Block a user