Added HttpMessageBuffer and modified MessageBuffer to be identical to the code in the master branch. Modified SocketContext.queueOutboundClientMessage() to be identical to the master branch. Changed SocketContext.sendHttpResponse() to call the queueOutboundClientMessage() passing a new HttpMessageBuffer. Fixed errors as necessary to make it all compile.
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
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.comparison.Comparator;
|
||||
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;
|
||||
import com.foundation.web.interfaces.ISession;
|
||||
|
||||
public class HttpMessageBuffer extends MessageBuffer {
|
||||
/** The optional response the message is based upon. */
|
||||
private Response response = null;
|
||||
/** The content if there is any. */
|
||||
private IContent content = null;
|
||||
/** Flag indicating if initialization is required. */
|
||||
private boolean isInitialized = false;
|
||||
/**
|
||||
* HttpMessageBuffer constructor.
|
||||
* @param socketContext The socket context associated with this message buffer.
|
||||
* @param response The HTTP response object.
|
||||
*/
|
||||
public HttpMessageBuffer(AbstractSocketContext socketContext, Response response) {
|
||||
super(socketContext);
|
||||
this.response = response;
|
||||
}//HttpMessageBuffer()//
|
||||
/** Gets the response object that created the message. This will be null for pass through sockets. */
|
||||
public Response getResponse() {return response;}
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.web.server.WebServer.MessageBuffer#initialize()
|
||||
*/
|
||||
public boolean initialize() {
|
||||
if(!isInitialized) {
|
||||
isInitialized = true;
|
||||
prepareResponse(response);
|
||||
}//if//
|
||||
|
||||
return true;
|
||||
}//initialize()//
|
||||
private void writeSessionCookies(Response response, PrintStream pout) {
|
||||
ISession session = response.getSession();
|
||||
|
||||
if(session != null) {
|
||||
//Write the session id only if it has changed.//
|
||||
if(!Comparator.equals(response.getRequest().getSessionId(), session.getSessionId())) {
|
||||
pout.print("Set-Cookie: sessionId=" + session.getSessionId() + ";path=/;\r\n");
|
||||
}//if//
|
||||
|
||||
//Write the secure session id only if it has changed.//
|
||||
if(!Comparator.equals(response.getRequest().getSecureSessionId(), session.getSecureSessionId())) {
|
||||
pout.print("Set-Cookie: secureSessionId=" + (session.getSecureSessionId() == null ? "" : session.getSecureSessionId()) + ";path=/;secure\r\n");
|
||||
}//if//
|
||||
|
||||
if(response.getRequest().isLoggedIn() != session.getIsLoggedIn()) {
|
||||
pout.print("Set-Cookie: isLoggedIn=" + session.getIsLoggedIn() + ";path=/;\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
}//writeSessionCookies()//
|
||||
/**
|
||||
* Processes the next response in the sequence.
|
||||
* <p>Note: The caller must synchronize on this context to prevent multiple threads from accessing the context at the same time.</p>
|
||||
* @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) {
|
||||
Request request = (Request) response.getRequest();
|
||||
byte[] headerBytes = null;
|
||||
IContent content = null;
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
try {
|
||||
//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");
|
||||
LiteList headerFieldNames = response.getHeaderFieldNames();
|
||||
LiteHashMap headerFieldMap = response.getHeaderFieldMap();
|
||||
|
||||
//Write the response line which is mapped to the null field name.//
|
||||
pout.print(headerFieldMap.get(null));
|
||||
pout.print("\r\n");
|
||||
|
||||
//Write the rest of the response header lines in order.//
|
||||
for(int index = 0; index < headerFieldNames.getSize(); index++) {
|
||||
String headerFieldName = (String) headerFieldNames.get(index);
|
||||
String headerFieldValue = (String) headerFieldMap.get(headerFieldName);
|
||||
|
||||
if(headerFieldName.equals("Server")) {
|
||||
pout.print("Server: DE/1.0");
|
||||
}//if//
|
||||
else {
|
||||
pout.print(headerFieldName);
|
||||
pout.print(": ");
|
||||
pout.print(headerFieldValue);
|
||||
}//else//
|
||||
|
||||
pout.print("\r\n");
|
||||
}//for//
|
||||
|
||||
//Write out any cookies necessary to retain our session.//
|
||||
writeSessionCookies(response, pout);
|
||||
|
||||
//End the header.//
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
|
||||
//Prepare the content for delivery.//
|
||||
content = response.getContent();
|
||||
}//if//
|
||||
else if(response.getForwardUri() != null) {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
//String today = format.format(new Date());
|
||||
|
||||
//The 303 code may not be fully supported by browsers.//
|
||||
//if(request.getHttpVersion().equalsIgnoreCase("HTTP/1.1") || request.getHttpVersion().equalsIgnoreCase("HTTP/1.2")) {
|
||||
// pout.print("HTTP/1.1 303 Forwarded\r\n");
|
||||
//}//if//
|
||||
//else {
|
||||
pout.print("HTTP/1.1 302 Moved Temporarily\r\n");
|
||||
//}//else//
|
||||
|
||||
writeSessionCookies(response, pout);
|
||||
|
||||
pout.print("Location: " + response.getForwardUri() + "\r\n");
|
||||
|
||||
//Note: Encoded URL's will have their parameters in the content, not in the URL.//
|
||||
if(response.getContent() == null) {
|
||||
pout.print("Content-Length: 0\r\n");
|
||||
}//if//
|
||||
else {
|
||||
pout.print("Content-Length: " + response.getContent().getSize() + "\r\n");
|
||||
pout.print("Content-Type: application/x-www-form-urlencoded\r\n");
|
||||
}//else//
|
||||
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
}//else if//
|
||||
else if((content = response.getContent()) != null) { //Convert the result into a stream of bytes.//
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
Date lastModifiedDate = content.getLastModifiedDate();
|
||||
String cacheDirective = null;
|
||||
IMimeType mimeType = content.getMimeType(response.getApplication() != null ? response.getApplication().getMimeTypeProvider() : null);
|
||||
boolean isDownloaded = content != null && (content.getIsDownloaded() == null ? mimeType != null && mimeType.isDownloaded() : content.getIsDownloaded().booleanValue());
|
||||
boolean compress = response.getCompress().booleanValue() && (mimeType == null || mimeType.isCompressable());
|
||||
int compressionType = 0; //0: none, 1: gzip, ..//
|
||||
|
||||
if(compress) {
|
||||
//Check for an encoding allowed by the client that we know how to use.//
|
||||
if(request.getAllowedEncodings() == null || request.getAllowedEncodings().length < 1) {
|
||||
compress = false;
|
||||
}//if//
|
||||
else {
|
||||
compress = false;
|
||||
|
||||
//Ensure we have an allowed encoding we know how to use.//
|
||||
for(int index = 0; !compress && index < request.getAllowedEncodings().length; index++) {
|
||||
String encoding = request.getAllowedEncodings()[index];
|
||||
|
||||
if(encoding.equalsIgnoreCase("gzip")) {
|
||||
compress = true;
|
||||
compressionType = 1;
|
||||
}//if//
|
||||
}//for//
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
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(response, 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: " + getSocketContext().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 = getSocketContext().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 {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
|
||||
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 {
|
||||
Debug.log(new RuntimeException("The response to: " + response.getRequest().getHeaderText() + " had no response content!"));
|
||||
pout.print("HTTP/1.1 200 OK\r\n");
|
||||
}//else//
|
||||
|
||||
writeSessionCookies(response, pout);
|
||||
pout.print("Content-Length: 0\r\n");
|
||||
pout.print("Server: DE/1.0\r\n");
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
}//else//
|
||||
|
||||
buffer = ByteBuffer.allocate(headerBytes.length > 2000 ? headerBytes.length : 2000);
|
||||
buffer.put(headerBytes);
|
||||
|
||||
if(getWebServer().debug()) {
|
||||
//Test code...
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(headerBytes.length);
|
||||
buffer2.put(headerBytes);
|
||||
buffer2.flip();
|
||||
CharBuffer ch = getSocketContext().decoder.decode(buffer2);
|
||||
// debugBuffer.append("Sending message:\n");
|
||||
// debugBuffer.append(ch.toString());
|
||||
// debugBuffer.append("\nResponse Size: " + (headerBytes.length + (content != null ? content.getSize() : 0)) + "\n");
|
||||
Debug.log(ch.toString());
|
||||
}//if//
|
||||
|
||||
//Ignore the content if we are only accessing the header.//
|
||||
// if(content != null && request.getRequestType() != Request.TYPE_HEAD) {
|
||||
// content.get(buffer);
|
||||
// }//if//
|
||||
|
||||
//Save the buffer as the current pending outbound message for this socket context.//
|
||||
//currentOutboundMessage = new MessageBuffer(buffer, response, content != null && request.getRequestType() != Request.TYPE_HEAD ? content : null);
|
||||
// queueOutboundClientMessage(new MessageBuffer(buffer, response, content != null && request.getRequestType() != Request.TYPE_HEAD ? content : null));
|
||||
setBuffer(buffer);
|
||||
|
||||
if(content != null && request.getRequestType() != Request.TYPE_HEAD) {
|
||||
this.content = content;
|
||||
}//if//
|
||||
|
||||
//Fill the remaining buffer space with the content.//
|
||||
if(this.content != null) {
|
||||
this.content.get(buffer);
|
||||
}//if//
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log("Fatal Error: Failed to build and send the response message due to an exception.", e);
|
||||
//Force the channel to close.//
|
||||
try {getSocketContext().close();} catch(Throwable e2) {}
|
||||
//Clean up after the request and response.//
|
||||
try {response.close();} catch(Throwable e2) {}
|
||||
}//catch//
|
||||
}//prepareResponse()//
|
||||
/**
|
||||
* Closes the message buffer.
|
||||
*/
|
||||
public void close() {
|
||||
super.close();
|
||||
|
||||
if(response != null) {
|
||||
response.close();
|
||||
response = null;
|
||||
}//if//
|
||||
}//close()//
|
||||
/**
|
||||
* 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;
|
||||
ByteBuffer buffer = getBuffer();
|
||||
|
||||
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) {
|
||||
close();
|
||||
}//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.//
|
||||
close();
|
||||
result = false;
|
||||
}//else if//
|
||||
}//if//
|
||||
else {
|
||||
result = false;
|
||||
}//else//
|
||||
|
||||
return result;
|
||||
}//loadBuffer()//
|
||||
}//HttpMessageBuffer//
|
||||
@@ -8,28 +8,32 @@ import com.foundation.web.interfaces.IContent;
|
||||
* The response message buffer encapsulating the request generating the response, and the content, and chainable into a linked list.
|
||||
*/
|
||||
class MessageBuffer {
|
||||
/** The socket context that this buffer exists within. */
|
||||
private AbstractSocketContext socketContext;
|
||||
/** 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;
|
||||
|
||||
/** The optional response the message is based upon. */
|
||||
private Response response = null;
|
||||
/** The content if there is any. */
|
||||
private IContent content = null;
|
||||
|
||||
/**
|
||||
* MessageBuffer constructor.
|
||||
* @param socketContext The socket context associated with this message buffer.
|
||||
*/
|
||||
public MessageBuffer() {
|
||||
protected MessageBuffer(AbstractSocketContext socketContext) {
|
||||
this.socketContext = socketContext;
|
||||
}//MessageBuffer()//
|
||||
/**
|
||||
* MessageBuffer constructor.
|
||||
* @param socketContext The socket context associated with this message buffer.
|
||||
* @param buffer The buffer to use for assembling the message bytes.
|
||||
*/
|
||||
public MessageBuffer(ByteBuffer buffer) {
|
||||
public MessageBuffer(AbstractSocketContext socketContext, ByteBuffer buffer) {
|
||||
this.socketContext = socketContext;
|
||||
setBuffer(buffer);
|
||||
}//MessageBuffer()//
|
||||
/** Gets the web server that this message buffer exists within. */
|
||||
protected WebServer getWebServer() {return socketContext.getWebServer();}
|
||||
/** Gets the socket context that this message buffer exists within. */
|
||||
protected AbstractSocketContext getSocketContext() {return socketContext;}
|
||||
/**
|
||||
* Sets the actual underlying buffer for the message buffer.
|
||||
* @param buffer
|
||||
@@ -37,27 +41,9 @@ class MessageBuffer {
|
||||
protected void setBuffer(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
|
||||
//Flip the buffer (if not already flipped) so we can write out the bytes.//
|
||||
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.
|
||||
@@ -78,11 +64,6 @@ class MessageBuffer {
|
||||
*/
|
||||
public void close() {
|
||||
this.buffer = null;
|
||||
|
||||
if(response != null) {
|
||||
response.close();
|
||||
response = null;
|
||||
}//if//
|
||||
}//close()//
|
||||
/**
|
||||
* Gets the byte buffer containing the current portion of the message to be sent.
|
||||
@@ -90,44 +71,24 @@ class MessageBuffer {
|
||||
*/
|
||||
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.
|
||||
* Loads content into the buffer, compacts the remaining bytes in the buffer (if any), and preps the buffer for reading. Will close this MessageBuffer if there are no remaining bytes and no content to fill with.
|
||||
* @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 (if the MessageBuffer is not closed). Will always be false if there is no content to load from, though calling again is okay since it will close the MessageBuffer if the buffer is empty and there is no content.
|
||||
*/
|
||||
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) {
|
||||
close();
|
||||
}//else if//
|
||||
|
||||
if(buffer != null && buffer.position() != 0) buffer.flip();
|
||||
}//if//
|
||||
else if(!buffer.hasRemaining()) {
|
||||
if(!buffer.hasRemaining()) {
|
||||
//Clear the buffer pointer indicating the message buffer is done.//
|
||||
close();
|
||||
result = false;
|
||||
}//else if//
|
||||
}//if//
|
||||
else {
|
||||
result = false;
|
||||
buffer.compact();
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}//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//
|
||||
@@ -136,22 +136,22 @@ 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//
|
||||
// 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()//
|
||||
|
||||
@@ -233,327 +233,48 @@ private void queueOutboundClientMessage(MessageBuffer messageBuffer) {
|
||||
boolean notify = false;
|
||||
|
||||
if(messageBuffer != null) {
|
||||
if(getWebServer().debug()) {
|
||||
Debug.log(this.getId() + "|" + System.nanoTime() + "|Queuing outbound client message (response). Buffer length: " + (messageBuffer.getBuffer() != null ? messageBuffer.getBuffer().remaining() + "" : "null"));
|
||||
}//if//
|
||||
|
||||
synchronized(getLock()) {
|
||||
if(currentOutboundMessage == null) {
|
||||
if(getWebServer().debug()) {
|
||||
Debug.log(this.getId() + "|" + System.nanoTime() + "|Queuing outbound client message (response) as the only message queued.");
|
||||
}//if//
|
||||
|
||||
lastOutboundMessage = currentOutboundMessage = messageBuffer;
|
||||
notify = true;
|
||||
}//if//
|
||||
else {
|
||||
if(getWebServer().debug()) {
|
||||
Debug.log(this.getId() + "|" + System.nanoTime() + "|Queuing outbound client message (response) as the last message queued.");
|
||||
}//if//
|
||||
|
||||
lastOutboundMessage.setNext(messageBuffer);
|
||||
lastOutboundMessage = messageBuffer;
|
||||
}//else//
|
||||
}//synchronized()//
|
||||
|
||||
if(notify) {
|
||||
if(getWebServer().debug()) {
|
||||
Debug.log(this.getId() + "|" + System.nanoTime() + "|Notifying the socket listener of the pending write.");
|
||||
}//if//
|
||||
|
||||
notifyListenerOfPendingWrite();
|
||||
}//if//
|
||||
}//if//
|
||||
}//queueOutboundClientMessage()//
|
||||
private void writeSessionCookies(Response response, PrintStream pout) {
|
||||
ISession session = response.getSession();
|
||||
|
||||
if(session != null) {
|
||||
//Write the session id only if it has changed.//
|
||||
if(!Comparator.equals(response.getRequest().getSessionId(), session.getSessionId())) {
|
||||
pout.print("Set-Cookie: sessionId=" + session.getSessionId() + ";path=/;\r\n");
|
||||
}//if//
|
||||
|
||||
//Write the secure session id only if it has changed.//
|
||||
if(!Comparator.equals(response.getRequest().getSecureSessionId(), session.getSecureSessionId())) {
|
||||
pout.print("Set-Cookie: secureSessionId=" + (session.getSecureSessionId() == null ? "" : session.getSecureSessionId()) + ";path=/;secure\r\n");
|
||||
}//if//
|
||||
|
||||
if(response.getRequest().isLoggedIn() != session.getIsLoggedIn()) {
|
||||
pout.print("Set-Cookie: isLoggedIn=" + session.getIsLoggedIn() + ";path=/;\r\n");
|
||||
}//if//
|
||||
}//if//
|
||||
}//writeSessionCookies()//
|
||||
/**
|
||||
* Processes the next response in the sequence.
|
||||
* <p>Note: The caller must synchronize on this context to prevent multiple threads from accessing the context at the same time.</p>
|
||||
* @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) {
|
||||
Request request = (Request) response.getRequest();
|
||||
byte[] headerBytes = null;
|
||||
IContent content = null;
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
try {
|
||||
//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");
|
||||
LiteList headerFieldNames = response.getHeaderFieldNames();
|
||||
LiteHashMap headerFieldMap = response.getHeaderFieldMap();
|
||||
|
||||
//Write the response line which is mapped to the null field name.//
|
||||
pout.print(headerFieldMap.get(null));
|
||||
pout.print("\r\n");
|
||||
|
||||
//Write the rest of the response header lines in order.//
|
||||
for(int index = 0; index < headerFieldNames.getSize(); index++) {
|
||||
String headerFieldName = (String) headerFieldNames.get(index);
|
||||
String headerFieldValue = (String) headerFieldMap.get(headerFieldName);
|
||||
|
||||
if(headerFieldName.equals("Server")) {
|
||||
pout.print("Server: DE/1.0");
|
||||
}//if//
|
||||
else {
|
||||
pout.print(headerFieldName);
|
||||
pout.print(": ");
|
||||
pout.print(headerFieldValue);
|
||||
}//else//
|
||||
|
||||
pout.print("\r\n");
|
||||
}//for//
|
||||
|
||||
//Write out any cookies necessary to retain our session.//
|
||||
writeSessionCookies(response, pout);
|
||||
|
||||
//End the header.//
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
|
||||
//Prepare the content for delivery.//
|
||||
content = response.getContent();
|
||||
}//if//
|
||||
else if(response.getForwardUri() != null) {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
//String today = format.format(new Date());
|
||||
|
||||
//The 303 code may not be fully supported by browsers.//
|
||||
//if(request.getHttpVersion().equalsIgnoreCase("HTTP/1.1") || request.getHttpVersion().equalsIgnoreCase("HTTP/1.2")) {
|
||||
// pout.print("HTTP/1.1 303 Forwarded\r\n");
|
||||
//}//if//
|
||||
//else {
|
||||
pout.print("HTTP/1.1 302 Moved Temporarily\r\n");
|
||||
//}//else//
|
||||
|
||||
writeSessionCookies(response, pout);
|
||||
|
||||
pout.print("Location: " + response.getForwardUri() + "\r\n");
|
||||
|
||||
//Note: Encoded URL's will have their parameters in the content, not in the URL.//
|
||||
if(response.getContent() == null) {
|
||||
pout.print("Content-Length: 0\r\n");
|
||||
}//if//
|
||||
else {
|
||||
pout.print("Content-Length: " + response.getContent().getSize() + "\r\n");
|
||||
pout.print("Content-Type: application/x-www-form-urlencoded\r\n");
|
||||
}//else//
|
||||
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
}//else if//
|
||||
else if((content = response.getContent()) != null) { //Convert the result into a stream of bytes.//
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
Date lastModifiedDate = content.getLastModifiedDate();
|
||||
String cacheDirective = null;
|
||||
IMimeType mimeType = content.getMimeType(response.getApplication() != null ? response.getApplication().getMimeTypeProvider() : null);
|
||||
boolean isDownloaded = content != null && (content.getIsDownloaded() == null ? mimeType != null && mimeType.isDownloaded() : content.getIsDownloaded().booleanValue());
|
||||
boolean compress = response.getCompress().booleanValue() && (mimeType == null || mimeType.isCompressable());
|
||||
int compressionType = 0; //0: none, 1: gzip, ..//
|
||||
|
||||
if(compress) {
|
||||
//Check for an encoding allowed by the client that we know how to use.//
|
||||
if(request.getAllowedEncodings() == null || request.getAllowedEncodings().length < 1) {
|
||||
compress = false;
|
||||
}//if//
|
||||
else {
|
||||
compress = false;
|
||||
|
||||
//Ensure we have an allowed encoding we know how to use.//
|
||||
for(int index = 0; !compress && index < request.getAllowedEncodings().length; index++) {
|
||||
String encoding = request.getAllowedEncodings()[index];
|
||||
|
||||
if(encoding.equalsIgnoreCase("gzip")) {
|
||||
compress = true;
|
||||
compressionType = 1;
|
||||
}//if//
|
||||
}//for//
|
||||
}//else//
|
||||
}//if//
|
||||
|
||||
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(response, 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 {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
PrintStream pout = new PrintStream(bout, true, "ASCII");
|
||||
|
||||
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 {
|
||||
Debug.log(new RuntimeException("The response to: " + response.getRequest().getHeaderText() + " had no response content!"));
|
||||
pout.print("HTTP/1.1 200 OK\r\n");
|
||||
}//else//
|
||||
|
||||
writeSessionCookies(response, pout);
|
||||
pout.print("Content-Length: 0\r\n");
|
||||
pout.print("Server: DE/1.0\r\n");
|
||||
pout.print("\r\n");
|
||||
pout.close();
|
||||
headerBytes = bout.toByteArray();
|
||||
}//else//
|
||||
|
||||
buffer = ByteBuffer.allocate(headerBytes.length > 2000 ? headerBytes.length : 2000);
|
||||
buffer.put(headerBytes);
|
||||
|
||||
if(getWebServer().debug()) {
|
||||
//Test code...
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(headerBytes.length);
|
||||
buffer2.put(headerBytes);
|
||||
buffer2.flip();
|
||||
CharBuffer ch = decoder.decode(buffer2);
|
||||
// debugBuffer.append("Sending message:\n");
|
||||
// debugBuffer.append(ch.toString());
|
||||
// debugBuffer.append("\nResponse Size: " + (headerBytes.length + (content != null ? content.getSize() : 0)) + "\n");
|
||||
Debug.log(ch.toString());
|
||||
}//if//
|
||||
|
||||
//Ignore the content if we are only accessing the header.//
|
||||
// if(content != null && request.getRequestType() != Request.TYPE_HEAD) {
|
||||
// content.get(buffer);
|
||||
// }//if//
|
||||
|
||||
//Save the buffer as the current pending outbound message for this socket context.//
|
||||
//currentOutboundMessage = new MessageBuffer(buffer, response, content != null && request.getRequestType() != Request.TYPE_HEAD ? content : null);
|
||||
queueOutboundClientMessage(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);
|
||||
//Force the channel to close.//
|
||||
try {key.channel().close();} catch(Throwable e2) {}
|
||||
//Clean up after the request and response.//
|
||||
try {response.close();} catch(Throwable e2) {}
|
||||
}//catch//
|
||||
}//prepareResponse()//
|
||||
/**
|
||||
* Adds a HTTP response to the socket context.
|
||||
* <p>Note: We must synchronize since a socket could be used to access multiple applications and thus mutliple sessions.</p>
|
||||
* @param response The response to be added.
|
||||
* @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) {
|
||||
//Short circuit the response linked list since it shouldn't be being used.//
|
||||
// lastResponse = currentResponse = 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//
|
||||
|
||||
public void sendHttpResponse(Response response) {
|
||||
//prepareResponse(response);
|
||||
queueOutboundClientMessage(new HttpMessageBuffer(this, response));
|
||||
request = null;
|
||||
|
||||
return false;
|
||||
}//sendHttpResponse()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.web.server.WebServer.AbstractSocketContext#passThrough(java.nio.ByteBuffer)
|
||||
@@ -2326,7 +2047,8 @@ private boolean processClientRequest(final Request request, SelectionKey key) th
|
||||
else {
|
||||
response = new Response(request, null, null);
|
||||
response.setContent(content);
|
||||
result = sendHttpResponse(response);
|
||||
sendHttpResponse(response);
|
||||
result = true;
|
||||
}//else//
|
||||
}//if//
|
||||
}//try//
|
||||
@@ -2379,7 +2101,8 @@ private boolean internalProcessClientRequest(SelectionKey key, final IWebApplica
|
||||
}//else if//
|
||||
|
||||
//Convert the response into a byte stream and send it via the socket.//
|
||||
result = sendHttpResponse(response);
|
||||
sendHttpResponse(response);
|
||||
result = true;
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
|
||||
Reference in New Issue
Block a user