|
|
|
@@ -16,6 +16,7 @@ import java.io.PrintStream;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
|
|
|
|
import java.net.URLDecoder;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.CharBuffer;
|
|
|
|
import java.nio.CharBuffer;
|
|
|
|
import java.nio.channels.ClosedChannelException;
|
|
|
|
import java.nio.channels.ClosedChannelException;
|
|
|
|
@@ -98,7 +99,7 @@ public class WebServer {
|
|
|
|
/** Flag indicating whether the web server is active. The attributes cannot be changed in a thread unsafe manner when this is set. */
|
|
|
|
/** Flag indicating whether the web server is active. The attributes cannot be changed in a thread unsafe manner when this is set. */
|
|
|
|
private volatile boolean isStarted = false;
|
|
|
|
private volatile boolean isStarted = false;
|
|
|
|
/** Whether to report all errors. If false, many networking errors (usually results of browsers terminating connections) will not be reported. */
|
|
|
|
/** Whether to report all errors. If false, many networking errors (usually results of browsers terminating connections) will not be reported. */
|
|
|
|
private boolean debug = false;
|
|
|
|
private boolean debug = true;
|
|
|
|
/** The handler called on to get a result for a bad request. */
|
|
|
|
/** The handler called on to get a result for a bad request. */
|
|
|
|
private IWebServerErrorHandler errorHandler = null;
|
|
|
|
private IWebServerErrorHandler errorHandler = null;
|
|
|
|
/** The maximum length of any request's content block. */
|
|
|
|
/** The maximum length of any request's content block. */
|
|
|
|
@@ -224,6 +225,7 @@ public class WebServer {
|
|
|
|
|
|
|
|
|
|
|
|
//Flip the buffer (if not already flipped) so we can write out the bytes.//
|
|
|
|
//Flip the buffer (if not already flipped) so we can write out the bytes.//
|
|
|
|
if(buffer.position() != 0) buffer.flip();
|
|
|
|
if(buffer.position() != 0) buffer.flip();
|
|
|
|
|
|
|
|
this.response = response;
|
|
|
|
}//MessageBuffer()//
|
|
|
|
}//MessageBuffer()//
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* MessageBuffer constructor.
|
|
|
|
* MessageBuffer constructor.
|
|
|
|
@@ -283,7 +285,16 @@ public class WebServer {
|
|
|
|
private abstract class AbstractSocketContext extends ChannelContext {
|
|
|
|
private abstract class AbstractSocketContext extends ChannelContext {
|
|
|
|
/** The key that represents the connection between the channel (socket) and the selector used to multiplex the listener. */
|
|
|
|
/** The key that represents the connection between the channel (socket) and the selector used to multiplex the listener. */
|
|
|
|
public SelectionKey key = null;
|
|
|
|
public SelectionKey key = null;
|
|
|
|
|
|
|
|
// /** The SelectionKey flags that will be used when the NetworkListener thread finishes processing the input/output of a message. The thread can safely change these flags without synchronizing. */
|
|
|
|
|
|
|
|
// private int flags = 0;
|
|
|
|
|
|
|
|
/** A socket context related to this one (when two are tied together such that data from one immediately is sent to the other). */
|
|
|
|
|
|
|
|
protected AbstractSocketContext relatedSocketContext = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Whether this socket context should be synchronized on before the related socket context (if one exists). This simply allows locking to always occur in the same order, preventing deadlocking in the case of related sockets and bad behavior (or SPEEDY like pipelining).
|
|
|
|
|
|
|
|
* @return Whether this context is to be locked before the related context (if it exists).
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected abstract boolean lockFirst();
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Processes the next response in the sequence.
|
|
|
|
* Processes the next response in the sequence.
|
|
|
|
* @throws IOException
|
|
|
|
* @throws IOException
|
|
|
|
@@ -304,11 +315,23 @@ public class WebServer {
|
|
|
|
* Closes the socket context and cleans up.
|
|
|
|
* Closes the socket context and cleans up.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected abstract void close();
|
|
|
|
protected abstract void close();
|
|
|
|
|
|
|
|
protected AbstractSocketContext getRelatedSocketContext() {
|
|
|
|
|
|
|
|
return relatedSocketContext;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
protected void setFlags(int flags) {
|
|
|
|
|
|
|
|
this.flags = flags;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
protected int getFlags() {
|
|
|
|
|
|
|
|
return flags;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Updates the write flag status.
|
|
|
|
* Updates the write flag status.
|
|
|
|
* @param requiresWrite Whether a write is required.
|
|
|
|
* @param requiresWrite Whether a write is required.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected void flagWrite(boolean requiresWrite) {
|
|
|
|
protected void flagWrite(boolean requiresWrite) {
|
|
|
|
|
|
|
|
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
int ops = key.interestOps();
|
|
|
|
int ops = key.interestOps();
|
|
|
|
boolean hasWrite = (ops & SelectionKey.OP_WRITE) != 0;
|
|
|
|
boolean hasWrite = (ops & SelectionKey.OP_WRITE) != 0;
|
|
|
|
@@ -324,12 +347,26 @@ public class WebServer {
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// boolean hasWrite = (flags & SelectionKey.OP_WRITE) != 0;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// if(requiresWrite) {
|
|
|
|
|
|
|
|
// if(!hasWrite) {
|
|
|
|
|
|
|
|
// flags |= SelectionKey.OP_WRITE;
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// else {
|
|
|
|
|
|
|
|
// if(hasWrite) {
|
|
|
|
|
|
|
|
// flags ^= SelectionKey.OP_WRITE;
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// }//else//
|
|
|
|
}//flagWrite()//
|
|
|
|
}//flagWrite()//
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Updates the read flag status.
|
|
|
|
* Updates the read flag status.
|
|
|
|
* @param requiresRead Whether a read is required.
|
|
|
|
* @param requiresRead Whether a read is required.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected void flagRead(boolean requiresRead) {
|
|
|
|
protected void flagRead(boolean requiresRead) {
|
|
|
|
|
|
|
|
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
int ops = key.interestOps();
|
|
|
|
int ops = key.interestOps();
|
|
|
|
boolean hasRead = (ops & SelectionKey.OP_READ) != 0;
|
|
|
|
boolean hasRead = (ops & SelectionKey.OP_READ) != 0;
|
|
|
|
@@ -345,21 +382,68 @@ public class WebServer {
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// boolean hasRead = (flags & SelectionKey.OP_READ) != 0;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// if(requiresRead) {
|
|
|
|
|
|
|
|
// if(!hasRead) {
|
|
|
|
|
|
|
|
// flags |= SelectionKey.OP_READ;
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// else {
|
|
|
|
|
|
|
|
// if(hasRead) {
|
|
|
|
|
|
|
|
// flags ^= SelectionKey.OP_READ;
|
|
|
|
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
// }//else//
|
|
|
|
}//flagWrite()//
|
|
|
|
}//flagWrite()//
|
|
|
|
|
|
|
|
protected void flagReadWrite(boolean requiresRead, boolean requiresWrite) {
|
|
|
|
|
|
|
|
synchronized(key) {
|
|
|
|
|
|
|
|
int ops = key.interestOps();
|
|
|
|
|
|
|
|
boolean hasRead = (ops & SelectionKey.OP_READ) != 0;
|
|
|
|
|
|
|
|
boolean hasWrite = (ops & SelectionKey.OP_WRITE) != 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(requiresRead) {
|
|
|
|
|
|
|
|
if(!hasRead) {
|
|
|
|
|
|
|
|
ops |= SelectionKey.OP_READ;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
if(hasRead) {
|
|
|
|
|
|
|
|
ops ^= SelectionKey.OP_READ;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(requiresWrite) {
|
|
|
|
|
|
|
|
if(!hasWrite) {
|
|
|
|
|
|
|
|
ops |= SelectionKey.OP_WRITE;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
if(hasWrite) {
|
|
|
|
|
|
|
|
ops ^= SelectionKey.OP_WRITE;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
key.interestOps(ops);
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//flagReadWrite()//
|
|
|
|
protected void flagReadWrite() {
|
|
|
|
protected void flagReadWrite() {
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
|
|
|
|
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
// flags = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
|
|
|
|
}//flagReadWrite()//
|
|
|
|
}//flagReadWrite()//
|
|
|
|
protected void flagWriteOnly() {
|
|
|
|
protected void flagWriteOnly() {
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
key.interestOps(SelectionKey.OP_WRITE);
|
|
|
|
key.interestOps(SelectionKey.OP_WRITE);
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
// flags = SelectionKey.OP_WRITE;
|
|
|
|
}//flagWriteOnly()//
|
|
|
|
}//flagWriteOnly()//
|
|
|
|
protected void flagReadOnly() {
|
|
|
|
protected void flagReadOnly() {
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
key.interestOps(SelectionKey.OP_READ);
|
|
|
|
key.interestOps(SelectionKey.OP_READ);
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
// flags = SelectionKey.OP_READ;
|
|
|
|
}//flagReadOnly()//
|
|
|
|
}//flagReadOnly()//
|
|
|
|
}//AbstractSocketContext//
|
|
|
|
}//AbstractSocketContext//
|
|
|
|
|
|
|
|
|
|
|
|
@@ -406,14 +490,13 @@ public class WebServer {
|
|
|
|
* Allows the web server to act as an SSL front to another web server or service.
|
|
|
|
* Allows the web server to act as an SSL front to another web server or service.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
private class PassThroughSocketContext extends AbstractSocketContext {
|
|
|
|
private class PassThroughSocketContext extends AbstractSocketContext {
|
|
|
|
private SocketContext linkedClientContext;
|
|
|
|
|
|
|
|
private MessageBuffer pendingMessageBuffer = null;
|
|
|
|
private MessageBuffer pendingMessageBuffer = null;
|
|
|
|
private MessageBuffer lastAddedMessageBuffer = null;
|
|
|
|
private MessageBuffer lastAddedMessageBuffer = null;
|
|
|
|
/** The byte buffer used to read data from the socket. */
|
|
|
|
/** The byte buffer used to read data from the socket. */
|
|
|
|
public ByteBuffer socketReadBuffer = ByteBuffer.allocate(BUFFER_SIZE);
|
|
|
|
public ByteBuffer socketReadBuffer = ByteBuffer.allocate(BUFFER_SIZE);
|
|
|
|
|
|
|
|
|
|
|
|
public PassThroughSocketContext(SocketContext linkedClientContext, String address, int port) throws IOException {
|
|
|
|
public PassThroughSocketContext(SocketContext linkedClientContext, String address, int port) throws IOException {
|
|
|
|
this.linkedClientContext = linkedClientContext;
|
|
|
|
this.relatedSocketContext = linkedClientContext;
|
|
|
|
SocketChannel channel = SocketChannel.open();
|
|
|
|
SocketChannel channel = SocketChannel.open();
|
|
|
|
RegisterKeyRunnable runnable;
|
|
|
|
RegisterKeyRunnable runnable;
|
|
|
|
|
|
|
|
|
|
|
|
@@ -428,13 +511,19 @@ public class WebServer {
|
|
|
|
linkedClientContext.key.selector().wakeup();
|
|
|
|
linkedClientContext.key.selector().wakeup();
|
|
|
|
runnable.waitForRun();
|
|
|
|
runnable.waitForRun();
|
|
|
|
key = runnable.getKey();
|
|
|
|
key = runnable.getKey();
|
|
|
|
flagRead(true);
|
|
|
|
flagReadOnly();
|
|
|
|
}//PassThroughSocketContext()//
|
|
|
|
}//PassThroughSocketContext()//
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#lockFirst()
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected boolean lockFirst() {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}//lockFirst()//
|
|
|
|
/* (non-Javadoc)
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processResponses()
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processResponses()
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected synchronized void processResponses() throws IOException {
|
|
|
|
protected synchronized void processResponses() throws IOException {
|
|
|
|
//Actually this is called when a request is being sent via the pass through socket (sending the request to the remote process).//
|
|
|
|
//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.//
|
|
|
|
//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;
|
|
|
|
boolean result = true;
|
|
|
|
|
|
|
|
|
|
|
|
@@ -481,9 +570,13 @@ public class WebServer {
|
|
|
|
}//while//
|
|
|
|
}//while//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
if(result) {
|
|
|
|
//If we were able to send all the message data, then flag to read the response from the remote server (if more data comes in from the client, we will change this flag).//
|
|
|
|
flagWrite(true);
|
|
|
|
if(pendingMessageBuffer == null) {
|
|
|
|
|
|
|
|
flagReadOnly();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
flagReadWrite();
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
}//processResponses()//
|
|
|
|
}//processResponses()//
|
|
|
|
/* (non-Javadoc)
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processRequest()
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processRequest()
|
|
|
|
@@ -506,12 +599,12 @@ public class WebServer {
|
|
|
|
//TODO: Comment me.
|
|
|
|
//TODO: Comment me.
|
|
|
|
//Debug.log("Git Closed Socket");
|
|
|
|
//Debug.log("Git Closed Socket");
|
|
|
|
//The socket has been closed by the client.//
|
|
|
|
//The socket has been closed by the client.//
|
|
|
|
linkedClientContext.close();
|
|
|
|
try {relatedSocketContext.close();} catch(Throwable e) {}
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else if(socketReadBuffer.hasRemaining()) {
|
|
|
|
else if(socketReadBuffer.hasRemaining()) {
|
|
|
|
//TODO: Comment me.
|
|
|
|
//TODO: Comment me.
|
|
|
|
//Debug.log("Git Sent " + count + " bytes.");
|
|
|
|
//Debug.log("Git Sent " + count + " bytes.");
|
|
|
|
result = linkedClientContext.passThrough(socketReadBuffer);
|
|
|
|
result = relatedSocketContext.passThrough(socketReadBuffer);
|
|
|
|
socketReadBuffer.compact();
|
|
|
|
socketReadBuffer.compact();
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
@@ -520,7 +613,7 @@ public class WebServer {
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//while//
|
|
|
|
}//while//
|
|
|
|
|
|
|
|
|
|
|
|
//Always keep the read flag up for pass through sockets.//
|
|
|
|
//Always keep the read flag up for pass through sockets (leave the write flag alone).//
|
|
|
|
flagRead(true);
|
|
|
|
flagRead(true);
|
|
|
|
}//processRequest()//
|
|
|
|
}//processRequest()//
|
|
|
|
/* (non-Javadoc)
|
|
|
|
/* (non-Javadoc)
|
|
|
|
@@ -540,7 +633,7 @@ public class WebServer {
|
|
|
|
//TODO: Comment me.
|
|
|
|
//TODO: Comment me.
|
|
|
|
//Debug.log("Posting request content to Git.");
|
|
|
|
//Debug.log("Posting request content to Git.");
|
|
|
|
pendingMessageBuffer = lastAddedMessageBuffer = message;
|
|
|
|
pendingMessageBuffer = lastAddedMessageBuffer = message;
|
|
|
|
flagWrite(true);
|
|
|
|
flagReadWrite();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
//TODO: Comment me.
|
|
|
|
//TODO: Comment me.
|
|
|
|
@@ -615,10 +708,10 @@ public class WebServer {
|
|
|
|
private int sentBytes = 0;
|
|
|
|
private int sentBytes = 0;
|
|
|
|
/** Tracks the debug output for the current request/response cycle. This is only used when debugging. */
|
|
|
|
/** Tracks the debug output for the current request/response cycle. This is only used when debugging. */
|
|
|
|
public StringBuffer debugBuffer = null; //debug ? new StringBuffer(1000) : null;
|
|
|
|
public StringBuffer debugBuffer = null; //debug ? new StringBuffer(1000) : null;
|
|
|
|
/** Used to pass all traffic (once unencrypted) through another socket to another process. */
|
|
|
|
|
|
|
|
private PassThroughSocketContext passThroughSocketContext = null;
|
|
|
|
|
|
|
|
/** The optional application specific data indexed by an application spectific key. Elements (values, not keys) which implement ISessionLifecycleAware will be released when the socket context is released. */
|
|
|
|
/** The optional application specific data indexed by an application spectific key. Elements (values, not keys) which implement ISessionLifecycleAware will be released when the socket context is released. */
|
|
|
|
private LiteHashMap applicationDataMap;
|
|
|
|
private LiteHashMap applicationDataMap;
|
|
|
|
|
|
|
|
/** Used to identify the first unencrypted message (ignored if ssl is being used) so that forwarding to a remote server can be accomplished. */
|
|
|
|
|
|
|
|
private boolean isFirstUnencryptedMessage = true;
|
|
|
|
|
|
|
|
|
|
|
|
public SocketContext(ServerSocketContext serverSocketContext) {
|
|
|
|
public SocketContext(ServerSocketContext serverSocketContext) {
|
|
|
|
super();
|
|
|
|
super();
|
|
|
|
@@ -626,6 +719,19 @@ public class WebServer {
|
|
|
|
this.id = nextSocketContextId++;
|
|
|
|
this.id = nextSocketContextId++;
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
}//SocketContext()//
|
|
|
|
}//SocketContext()//
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Gets the pass through socket context associated with this socket context, or null if none exists.
|
|
|
|
|
|
|
|
* @return The socket context for the pass through socket used to handle all incoming requests from the client on this socket.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected PassThroughSocketContext getPassThroughSocketContext() {
|
|
|
|
|
|
|
|
return (PassThroughSocketContext) getRelatedSocketContext();
|
|
|
|
|
|
|
|
}//getPassThroughSocketContext()//
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#lockFirst()
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected boolean lockFirst() {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}//lockFirst()//
|
|
|
|
protected synchronized void close() {
|
|
|
|
protected synchronized void close() {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if(applicationDataMap != null) {
|
|
|
|
if(applicationDataMap != null) {
|
|
|
|
@@ -651,8 +757,8 @@ public class WebServer {
|
|
|
|
//Clean up after the response and request.//
|
|
|
|
//Clean up after the response and request.//
|
|
|
|
try {if(currentResponse != null) currentResponse.close();} catch(Throwable e2) {}
|
|
|
|
try {if(currentResponse != null) currentResponse.close();} catch(Throwable e2) {}
|
|
|
|
|
|
|
|
|
|
|
|
if(passThroughSocketContext != null) {
|
|
|
|
if(getPassThroughSocketContext() != null) {
|
|
|
|
passThroughSocketContext.close();
|
|
|
|
getPassThroughSocketContext().close();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//close()//
|
|
|
|
}//close()//
|
|
|
|
/* (non-Javadoc)
|
|
|
|
/* (non-Javadoc)
|
|
|
|
@@ -1020,12 +1126,12 @@ public class WebServer {
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processResponses()
|
|
|
|
* @see com.foundation.web.server.WebServer.AbstractSocketContext#processResponses()
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected void processResponses() throws IOException {
|
|
|
|
protected void processResponses() throws IOException {
|
|
|
|
if(passThroughSocketContext != null) {
|
|
|
|
if(getPassThroughSocketContext() != null) {
|
|
|
|
//Synchronized to avoid multiple threads accessing the pendingOutboundMessage chain at one time and updating the write flag out of order (could happen if we enabled request chaining over a single socket).//
|
|
|
|
//Synchronized to avoid multiple threads accessing the pendingOutboundMessage chain at one time and updating the write flag out of order (could happen if we enabled request chaining over a single socket).//
|
|
|
|
synchronized(this) {
|
|
|
|
synchronized(this) {
|
|
|
|
writeClientResponse();
|
|
|
|
writeClientResponse();
|
|
|
|
//Set the write flag if we have more to write.//
|
|
|
|
//Set the write flag if we have more to write.//
|
|
|
|
flagWrite(pendingOutboundMessage != null);
|
|
|
|
flagReadWrite(true, pendingOutboundMessage != null);
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
@@ -1036,7 +1142,7 @@ public class WebServer {
|
|
|
|
flagReadOnly();
|
|
|
|
flagReadOnly();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
flagWriteOnly();
|
|
|
|
flagReadWrite();
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//processCurrentResponse()//
|
|
|
|
}//processCurrentResponse()//
|
|
|
|
@@ -1203,6 +1309,22 @@ public class WebServer {
|
|
|
|
if(result && pendingOutboundMessage != null) {
|
|
|
|
if(result && pendingOutboundMessage != 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.//
|
|
|
|
//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()) {
|
|
|
|
if(result && !pendingOutboundMessage.getBuffer().hasRemaining()) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
if(!pendingOutboundMessage.loadBuffer()) {
|
|
|
|
|
|
|
|
if(pendingOutboundMessage.getNext() != null) {
|
|
|
|
|
|
|
|
pendingOutboundMessage = pendingOutboundMessage.getNext();
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
result = false;
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(pendingOutboundMessage.getBuffer() == null) {
|
|
|
|
|
|
|
|
pendingOutboundMessage = null;
|
|
|
|
|
|
|
|
lastAddedMessageBuffer = null;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
//Attempt to load additional message bytes into the buffer.//
|
|
|
|
//Attempt to load additional message bytes into the buffer.//
|
|
|
|
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
|
|
|
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1336,6 +1458,22 @@ public class WebServer {
|
|
|
|
result = false;
|
|
|
|
result = false;
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
if(!pendingOutboundMessage.loadBuffer()) {
|
|
|
|
|
|
|
|
if(pendingOutboundMessage.getNext() != null) {
|
|
|
|
|
|
|
|
pendingOutboundMessage = pendingOutboundMessage.getNext();
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
result = false;
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(pendingOutboundMessage.getBuffer() == null) {
|
|
|
|
|
|
|
|
pendingOutboundMessage = null;
|
|
|
|
|
|
|
|
lastAddedMessageBuffer = null;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
//Attempt to load additional message bytes into the buffer.//
|
|
|
|
//Attempt to load additional message bytes into the buffer.//
|
|
|
|
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
|
|
|
boolean couldLoadAdditionalBytes = pendingOutboundMessage.loadBuffer();
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1468,7 +1606,7 @@ public class WebServer {
|
|
|
|
IPassThroughDomain passThroughDomain = ((IPassThroughDomain) application);
|
|
|
|
IPassThroughDomain passThroughDomain = ((IPassThroughDomain) application);
|
|
|
|
|
|
|
|
|
|
|
|
//Setup the pass through socket context (and socket channel). All data will be sent to this context to be sent to the remote process.//
|
|
|
|
//Setup the pass through socket context (and socket channel). All data will be sent to this context to be sent to the remote process.//
|
|
|
|
passThroughSocketContext = new PassThroughSocketContext(this, passThroughDomain.getAddress(), passThroughDomain.getPort());
|
|
|
|
relatedSocketContext = new PassThroughSocketContext(this, passThroughDomain.getAddress(), passThroughDomain.getPort());
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
@@ -1557,7 +1695,7 @@ public class WebServer {
|
|
|
|
sslNeedsWrap = true;
|
|
|
|
sslNeedsWrap = true;
|
|
|
|
|
|
|
|
|
|
|
|
//Need to synchronize if this is a pass through socket so that multiple threads don't access pendingOutboundMessage or lastAddedMessageBuffer (via a call to passThrough(ByteBuffer) on another thread).//
|
|
|
|
//Need to synchronize if this is a pass through socket so that multiple threads don't access pendingOutboundMessage or lastAddedMessageBuffer (via a call to passThrough(ByteBuffer) on another thread).//
|
|
|
|
if(passThroughSocketContext == null) {
|
|
|
|
if(getPassThroughSocketContext() == null) {
|
|
|
|
result = writeClientResponse();
|
|
|
|
result = writeClientResponse();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
@@ -1570,14 +1708,14 @@ public class WebServer {
|
|
|
|
//If bytes were produced then process them.//
|
|
|
|
//If bytes were produced then process them.//
|
|
|
|
if(sslResult.bytesProduced() > 0) {
|
|
|
|
if(sslResult.bytesProduced() > 0) {
|
|
|
|
//If we are not passing all content to another process then handle it by calling processClientRequest, otherwise pass it through.//
|
|
|
|
//If we are not passing all content to another process then handle it by calling processClientRequest, otherwise pass it through.//
|
|
|
|
if(passThroughSocketContext == null) {
|
|
|
|
if(getPassThroughSocketContext() == null) {
|
|
|
|
result = WebServer.this.processClientRequest((SocketContext) this, unencryptedReadBuffer, key);
|
|
|
|
result = WebServer.this.processClientRequest((SocketContext) this, unencryptedReadBuffer, key);
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
//TODO: Comment me.
|
|
|
|
//TODO: Comment me.
|
|
|
|
//Debug.log("Receiving message (" + unencryptedReadBuffer.remaining() + " bytes) from client for git.");
|
|
|
|
//Debug.log("Receiving message (" + unencryptedReadBuffer.remaining() + " bytes) from client for git.");
|
|
|
|
//Queue the data for sending to the remote process via the pass through socket context.//
|
|
|
|
//Queue the data for sending to the remote process via the pass through socket context.//
|
|
|
|
passThroughSocketContext.passThrough(unencryptedReadBuffer);
|
|
|
|
getPassThroughSocketContext().passThrough(unencryptedReadBuffer);
|
|
|
|
result = true;
|
|
|
|
result = true;
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
@@ -1605,15 +1743,41 @@ public class WebServer {
|
|
|
|
//Note: We are throddling this for active connections to prevent a single connection from hogging all the resources.//
|
|
|
|
//Note: We are throddling this for active connections to prevent a single connection from hogging all the resources.//
|
|
|
|
while(loopCount < 10 && result && count > 0) {
|
|
|
|
while(loopCount < 10 && result && count > 0) {
|
|
|
|
loopCount++;
|
|
|
|
loopCount++;
|
|
|
|
socketReadBuffer.position(0);
|
|
|
|
//Allow data to be left on the socket read buffer.//
|
|
|
|
socketReadBuffer.limit(socketReadBuffer.capacity());
|
|
|
|
if(socketReadBuffer.position() != 0) socketReadBuffer.compact();
|
|
|
|
count = channel.read(socketReadBuffer);
|
|
|
|
count = channel.read(socketReadBuffer);
|
|
|
|
socketReadBuffer.flip();
|
|
|
|
socketReadBuffer.flip();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Setup the pass through socket if the application is an instance of IPassThroughDomain.//
|
|
|
|
|
|
|
|
if(count != -1 && isFirstUnencryptedMessage) {
|
|
|
|
|
|
|
|
//Read enough of the header to identify the application.//
|
|
|
|
|
|
|
|
if(WebServer.this.processRequestedHost((SocketContext) this, socketReadBuffer, key)) {
|
|
|
|
|
|
|
|
//Create a pass through socket and context and attach it to this context if the application is setup as a pass through to another process.//
|
|
|
|
|
|
|
|
IWebApplication application = webApplicationContainer.getWebApplication();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(application instanceof IPassThroughDomain) {
|
|
|
|
|
|
|
|
IPassThroughDomain passThroughDomain = ((IPassThroughDomain) application);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Setup the pass through socket context (and socket channel). All data will be sent to this context to be sent to the remote process.//
|
|
|
|
|
|
|
|
relatedSocketContext = new PassThroughSocketContext(this, passThroughDomain.getAddress(), passThroughDomain.getPort());
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isFirstUnencryptedMessage = false;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
//We couldn't even read the host from the first bytes sent by the client - very unusual (it should be in the first couple hundred bytes - less than a single packet).//
|
|
|
|
|
|
|
|
//TODO: We could cycle and wait for the next packet. For now just close the socket since this should never really happen in the first place.
|
|
|
|
|
|
|
|
close();
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
if(count == -1) {
|
|
|
|
if(count == -1) {
|
|
|
|
//The socket has been closed by the client.//
|
|
|
|
//The socket has been closed by the client.//
|
|
|
|
close();
|
|
|
|
close();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else if(relatedSocketContext != null) {
|
|
|
|
|
|
|
|
relatedSocketContext.passThrough(socketReadBuffer);
|
|
|
|
|
|
|
|
}//else if//
|
|
|
|
else if(socketReadBuffer.hasRemaining()) {
|
|
|
|
else if(socketReadBuffer.hasRemaining()) {
|
|
|
|
result = WebServer.this.processClientRequest((SocketContext) this, socketReadBuffer, key);
|
|
|
|
result = WebServer.this.processClientRequest((SocketContext) this, socketReadBuffer, key);
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
@@ -1627,7 +1791,7 @@ public class WebServer {
|
|
|
|
flagReadOnly();
|
|
|
|
flagReadOnly();
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
flagWriteOnly();
|
|
|
|
flagReadWrite();
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//processRequest()//
|
|
|
|
}//processRequest()//
|
|
|
|
}//SocketContext//
|
|
|
|
}//SocketContext//
|
|
|
|
@@ -1801,8 +1965,16 @@ public class WebServer {
|
|
|
|
|
|
|
|
|
|
|
|
//Toggle the write or read flag.//
|
|
|
|
//Toggle the write or read flag.//
|
|
|
|
synchronized(key) {
|
|
|
|
synchronized(key) {
|
|
|
|
//Enabling pipelining of messages (some HTTP protocols allow it now - see Google Speedy), and pass through might require it.//
|
|
|
|
// //Save the ops that will be set when the processing is complete.//
|
|
|
|
|
|
|
|
// ((AbstractSocketContext) context).setFlags(key.interestOps());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Notes: Java (pre-jdk7) does not have the ability to read and write to a socket at the same time (two threads, one socket). Post jdk7 there is AsynchronousSocketChannel and AsynchronousServerSocketChannel which could be used to send/receive at the same time.
|
|
|
|
|
|
|
|
//Truely enabling Speedy would require a thread to read which when finished would flag read again BEFORE processing the message and BEFORE sending a response.
|
|
|
|
|
|
|
|
//For now (so we don't have to require jdk7 yet) we will simply allow Speedy to queue up messages, but only read, process, and then write them one at a time. Most of the speed loss is in the waiting for the WRITE to finish before handling the next request (due to it being broken into packets and the mechanics of TCP), and that is generally minimal (speed lose) since usually the bottleneck in speed is the browser's connection to the internet (most of us haven't got Gigabit Ethernet at home). Anyone with enough home juice to have this be a problem would only notice the difference for really porky websites (which is a problem in and of its self).
|
|
|
|
|
|
|
|
|
|
|
|
key.interestOps(key.interestOps() ^ (isWrite ? SelectionKey.OP_WRITE : SelectionKey.OP_READ));
|
|
|
|
key.interestOps(key.interestOps() ^ (isWrite ? SelectionKey.OP_WRITE : SelectionKey.OP_READ));
|
|
|
|
|
|
|
|
//Not allowing either reads or writes to continue until all processing of this message is done.//
|
|
|
|
|
|
|
|
// key.interestOps(0);
|
|
|
|
}//synchronized//
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@@ -1820,17 +1992,53 @@ public class WebServer {
|
|
|
|
// Debug.log("Socket is write available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
|
|
|
|
// Debug.log("Socket is write available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
|
|
|
|
// }//if//
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
|
|
|
|
//Process the pending write to the socket as much as is possible, then return.//
|
|
|
|
//Lock on both contexts if there is a related socket - do it deadlock safe.//
|
|
|
|
((AbstractSocketContext) context).processResponses();
|
|
|
|
if(((AbstractSocketContext) context).getRelatedSocketContext() == null) {
|
|
|
|
|
|
|
|
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
|
|
|
|
|
|
|
|
synchronized(context) {
|
|
|
|
|
|
|
|
//Process the pending write to the socket as much as is possible, then return.//
|
|
|
|
|
|
|
|
((AbstractSocketContext) context).processResponses();
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
Object lockFirst = ((AbstractSocketContext) context).lockFirst() ? context : ((AbstractSocketContext) context).getRelatedSocketContext();
|
|
|
|
|
|
|
|
Object lockSecond = ((AbstractSocketContext) context).lockFirst() ? ((AbstractSocketContext) context).getRelatedSocketContext() : context;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
|
|
|
|
|
|
|
|
synchronized(lockFirst) {
|
|
|
|
|
|
|
|
synchronized(lockSecond) {
|
|
|
|
|
|
|
|
//Process the pending write to the socket as much as is possible, then return.//
|
|
|
|
|
|
|
|
((AbstractSocketContext) context).processResponses();
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
// if(debug) {
|
|
|
|
// if(debug) {
|
|
|
|
// ((SocketContext) context).debugBuffer.append("Socket is now read available.\n");
|
|
|
|
// ((SocketContext) context).debugBuffer.append("Socket is now read available.\n");
|
|
|
|
// Debug.log("Socket is read available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
|
|
|
|
// Debug.log("Socket is read available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
|
|
|
|
// }//if//
|
|
|
|
// }//if//
|
|
|
|
|
|
|
|
|
|
|
|
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
|
|
|
|
//Lock on both contexts if there is a related socket - do it deadlock safe.//
|
|
|
|
((AbstractSocketContext) context).processRequest();
|
|
|
|
if(((AbstractSocketContext) context).getRelatedSocketContext() == null) {
|
|
|
|
|
|
|
|
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
|
|
|
|
|
|
|
|
synchronized(context) {
|
|
|
|
|
|
|
|
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
|
|
|
|
|
|
|
|
((AbstractSocketContext) context).processRequest();
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
Object lockFirst = ((AbstractSocketContext) context).lockFirst() ? context : ((AbstractSocketContext) context).getRelatedSocketContext();
|
|
|
|
|
|
|
|
Object lockSecond = ((AbstractSocketContext) context).lockFirst() ? ((AbstractSocketContext) context).getRelatedSocketContext() : context;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
|
|
|
|
|
|
|
|
synchronized(lockFirst) {
|
|
|
|
|
|
|
|
synchronized(lockSecond) {
|
|
|
|
|
|
|
|
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
|
|
|
|
|
|
|
|
((AbstractSocketContext) context).processRequest();
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//else//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
}//try//
|
|
|
|
}//try//
|
|
|
|
@@ -1839,9 +2047,7 @@ public class WebServer {
|
|
|
|
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
|
|
|
|
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
|
|
|
|
}//catch//
|
|
|
|
}//catch//
|
|
|
|
catch(Throwable e) {
|
|
|
|
catch(Throwable e) {
|
|
|
|
if(debug) {
|
|
|
|
if(debug) Debug.log(e);
|
|
|
|
Debug.log(e);
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Force the socket to be closed (for sure).//
|
|
|
|
//Force the socket to be closed (for sure).//
|
|
|
|
try {((SocketChannel) channel).close();}catch(Throwable e2) {}
|
|
|
|
try {((SocketChannel) channel).close();}catch(Throwable e2) {}
|
|
|
|
@@ -1854,6 +2060,10 @@ public class WebServer {
|
|
|
|
if(channel != null && !socketClosed && channel.isOpen() && key != null && context != null) {
|
|
|
|
if(channel != null && !socketClosed && channel.isOpen() && key != null && context != null) {
|
|
|
|
requiresWakeup = true;
|
|
|
|
requiresWakeup = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// //Set the flags (either READ/WRITE) for the socket.//
|
|
|
|
|
|
|
|
// synchronized(key) {
|
|
|
|
|
|
|
|
// key.interestOps(((AbstractSocketContext) context).getFlags());
|
|
|
|
|
|
|
|
// }//synchronized//
|
|
|
|
}//if//
|
|
|
|
}//if//
|
|
|
|
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
|
|
|
|
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
|
|
|
|
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
|
|
|
|
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
|
|
|
|
@@ -2416,6 +2626,84 @@ private boolean parseFirstTlsMessage(SocketContext context, SocketChannel channe
|
|
|
|
private boolean isCompleteHeader(StringBuffer buffer) {
|
|
|
|
private boolean isCompleteHeader(StringBuffer buffer) {
|
|
|
|
return (buffer.length() > 4) && (buffer.charAt(buffer.length() - 4) == '\r') && (buffer.charAt(buffer.length() - 3) == '\n') && (buffer.charAt(buffer.length() - 2) == '\r') && (buffer.charAt(buffer.length() - 1) == '\n');
|
|
|
|
return (buffer.length() > 4) && (buffer.charAt(buffer.length() - 4) == '\r') && (buffer.charAt(buffer.length() - 3) == '\n') && (buffer.charAt(buffer.length() - 2) == '\r') && (buffer.charAt(buffer.length() - 1) == '\n');
|
|
|
|
}//isCompleteHeader()//
|
|
|
|
}//isCompleteHeader()//
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Processes enough of the header of this first request to identify the application and set it for the socket. Used to forward unencrypted message to a remote server.
|
|
|
|
|
|
|
|
* @param context
|
|
|
|
|
|
|
|
* @param fragment
|
|
|
|
|
|
|
|
* @param key
|
|
|
|
|
|
|
|
* @return Whether enough of the request could be read to identify the application. The caller should ignore the result if key.channel() is closed since the request was incomplete, incorrectly formatted, or the socket failed.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private boolean processRequestedHost(SocketContext context, ByteBuffer fragment, SelectionKey key) throws IOException {
|
|
|
|
|
|
|
|
boolean result = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
fragment.mark();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Check whether we already read this message's header and we are simply appending to the end of it.//
|
|
|
|
|
|
|
|
if(context.request == null) {
|
|
|
|
|
|
|
|
String host = null;
|
|
|
|
|
|
|
|
StringBuilder buffer = new StringBuilder(4096);
|
|
|
|
|
|
|
|
int totalHeaderSize = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while(fragment.hasRemaining() && host == null) {
|
|
|
|
|
|
|
|
//Keep adding ASCII characters to the message header fragment until the next line is read or we exceed the maximum length of the header.//
|
|
|
|
|
|
|
|
while(fragment.hasRemaining() && (!(buffer.length() > 1 && (buffer.charAt(buffer.length() - 2) == '\r') && (buffer.charAt(buffer.length() - 1) == '\n')))) {
|
|
|
|
|
|
|
|
if(totalHeaderSize == 4096) {
|
|
|
|
|
|
|
|
//Force the connection to the client to be closed.//
|
|
|
|
|
|
|
|
try {key.channel().close();}catch(Throwable e2) {}
|
|
|
|
|
|
|
|
//Throw an exception that should not be logged. This happens occationally when an attacker tries to exploit any buffer overrun weaknesses.//
|
|
|
|
|
|
|
|
throw new IgnoredIOException(null);
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
buffer.append((char) fragment.get());
|
|
|
|
|
|
|
|
totalHeaderSize++;
|
|
|
|
|
|
|
|
}//while//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//If we have the minimum number of bytes and the last bytes are a line end, then check the line for "Host: xxxxxxx\r\n"
|
|
|
|
|
|
|
|
String line = buffer.toString().substring(0, buffer.length() - 2).trim();
|
|
|
|
|
|
|
|
if(line.startsWith("Host: ")) {
|
|
|
|
|
|
|
|
host = line.substring(6).trim();
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(host == null) {
|
|
|
|
|
|
|
|
if(buffer.length() == 2) {
|
|
|
|
|
|
|
|
//End of the header reached. No host provided. Kill the connection?//
|
|
|
|
|
|
|
|
//Force the connection to the client to be closed.//
|
|
|
|
|
|
|
|
try {key.channel().close();}catch(Throwable e2) {}
|
|
|
|
|
|
|
|
//Throw an exception that should not be logged. This happens occationally when an attacker tries to exploit any header reading weaknesses (all major browsers send a host header).//
|
|
|
|
|
|
|
|
throw new IgnoredIOException(null);
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
//Clear the line.//
|
|
|
|
|
|
|
|
buffer.setLength(0);
|
|
|
|
|
|
|
|
}//else//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//while//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//If we found the complete first line of the header before running out of bytes, then identify the application.//
|
|
|
|
|
|
|
|
if(host != null) {
|
|
|
|
|
|
|
|
context.domain = host.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Get the web application for the given domain.//
|
|
|
|
|
|
|
|
//Synchronize to prevent another thread from altering the service's web applications while we are accessing it.//
|
|
|
|
|
|
|
|
synchronized(WebServer.this) {
|
|
|
|
|
|
|
|
context.webApplicationContainer = context.serverSocketContext.serviceListener.getWebApplicationContainer(context.domain);
|
|
|
|
|
|
|
|
}//synchronized//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = true;
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
}//if//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fragment.reset();
|
|
|
|
|
|
|
|
}//try//
|
|
|
|
|
|
|
|
catch(IgnoredIOException e) {}
|
|
|
|
|
|
|
|
catch(Throwable e) {
|
|
|
|
|
|
|
|
Debug.log(e);
|
|
|
|
|
|
|
|
throw new IOException("Failed to process the request headers.");
|
|
|
|
|
|
|
|
}//catch//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}//processRequestedHost()//
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Processes the client request given the latest fragment of a message.
|
|
|
|
* Processes the client request given the latest fragment of a message.
|
|
|
|
* @param context The client context.
|
|
|
|
* @param context The client context.
|
|
|
|
|