Modified the web server to allow connection related data to be stored in the connection's context by the application. This modifies the contract (interfaces) between the framework and application code, requiring changes to the application (breaks backward compatibility).

This commit is contained in:
wcrisman
2014-07-11 10:39:36 -07:00
parent bb9b1f550e
commit d2027e13f9
9 changed files with 83 additions and 16 deletions

View File

@@ -1,5 +1,6 @@
package com.foundation.web; package com.foundation.web;
import com.foundation.web.interfaces.IConnectionContext;
import com.foundation.web.interfaces.IRequest; import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse; import com.foundation.web.interfaces.IResponse;
import com.foundation.web.interfaces.ISession; import com.foundation.web.interfaces.ISession;
@@ -14,14 +15,15 @@ public interface IResourceRequestHandler {
/** /**
* Gets a resource from the file system. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed. * Gets a resource from the file system. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed.
* <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p> * <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p>
* @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource. * @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource (same as calling request.getUri()).
* @param request The request metadata. * @param request The request metadata.
* @param response The response metadata. * @param response The response metadata.
* @param sessionData The non-secure session data. * @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists. * @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id. * @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
* @param connectionContext The context object for the connection (socket) between the client (web browser) and server (web server).
*/ */
public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure); public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure, IConnectionContext connectionContext);
/** /**
* Calls a handler to access the requested resource. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed. * Calls a handler to access the requested resource. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed.
* <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p> * <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p>
@@ -32,8 +34,9 @@ public void processRequest(String requestPath, IRequest request, IResponse respo
* @param sessionData The non-secure session data. * @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists. * @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id. * @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
* @param connectionContext The context object for the connection (socket) between the client (web browser) and server (web server).
*/ */
public void processRequest(IWebRequestHandler handler, String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure); public void processRequest(IWebRequestHandler handler, String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure, IConnectionContext connectionContext);
/** /**
* Gets the maximum size of message allowed by the application beyond the header (for upload only). * Gets the maximum size of message allowed by the application beyond the header (for upload only).
* @param request The request whose header has been read, but whose body has not yet been read. * @param request The request whose header has been read, but whose body has not yet been read.

View File

@@ -7,6 +7,7 @@
*/ */
package com.foundation.web; package com.foundation.web;
import com.foundation.web.interfaces.IConnectionContext;
import com.foundation.web.interfaces.IRequest; import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse; import com.foundation.web.interfaces.IResponse;
import com.foundation.web.SecureSessionData; import com.foundation.web.SecureSessionData;
@@ -22,7 +23,8 @@ public interface IWebRequestHandler {
* @param request The request metadata. * @param request The request metadata.
* @param response The response metadata. * @param response The response metadata.
* @param sessionData The application specific data object associated with this user's session. * @param sessionData The application specific data object associated with this user's session.
* @param connectionContext The context object for the connection (socket) between the client (web browser) and server (web server).
* @param secureSessionData The application specific data object associated with this user's session if the user made the request over a secure connection and used the correct secure session identifier, otherwise null. * @param secureSessionData The application specific data object associated with this user's session if the user made the request over a secure connection and used the correct secure session identifier, otherwise null.
*/ */
public void processRequest(IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData); public void processRequest(IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, IConnectionContext connectionContext);
}//IWebRequestHandler// }//IWebRequestHandler//

View File

@@ -1,5 +1,6 @@
package com.foundation.web; package com.foundation.web;
import com.foundation.web.interfaces.IConnectionContext;
import com.foundation.web.interfaces.IRequest; import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse; import com.foundation.web.interfaces.IResponse;
import com.foundation.web.interfaces.ISession; import com.foundation.web.interfaces.ISession;
@@ -10,12 +11,13 @@ import com.foundation.web.interfaces.ISession;
public interface IWebdavRequestHandler { public interface IWebdavRequestHandler {
/** /**
* Processes the incoming web dav request. * Processes the incoming web dav request.
* @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource. * @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource (same as calling request.getUri()).
* @param request The request metadata. * @param request The request metadata.
* @param response The response metadata. * @param response The response metadata.
* @param sessionData The non-secure session data. * @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists. * @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id. * @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
* @param connectionContext The context object for the connection (socket) between the client (web browser) and server (web server).
*/ */
public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure); public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure, IConnectionContext connectionContext);
}//IWebdavRequestHandler// }//IWebdavRequestHandler//

View File

@@ -9,6 +9,7 @@ import com.common.util.IIterator;
import com.common.util.IList; import com.common.util.IList;
import com.common.util.LiteHashMap; import com.common.util.LiteHashMap;
import com.foundation.event.IRequestHandler; import com.foundation.event.IRequestHandler;
import com.foundation.web.interfaces.ISessionLifecycleAware;
/** /**
* Contains the basic information about a user web session. * Contains the basic information about a user web session.

View File

@@ -714,9 +714,9 @@ protected String processResourceName(String resourceName) {
return (String) nameSubstitutionMap.get(resourceName); return (String) nameSubstitutionMap.get(resourceName);
}//processResourceName()// }//processResourceName()//
/* (non-Javadoc) /* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#processRequest(com.foundation.web.interfaces.IRequest, com.foundation.web.interfaces.IResponse, com.foundation.web.interfaces.ISession, boolean, boolean) * @see com.foundation.web.interfaces.IWebApplication#processRequest(com.foundation.web.interfaces.IRequest, com.foundation.web.interfaces.IResponse, com.foundation.web.interfaces.ISession, boolean, boolean, com.foundation.web.interfaces.IConnectionContext)
*/ */
public void processRequest(final IRequest request, final IResponse response, final ISession session, final boolean isSecure, final boolean clientHadBadSession) { public void processRequest(final IRequest request, final IResponse response, final ISession session, final boolean isSecure, final boolean clientHadBadSession, final IConnectionContext connectionContext) {
int requestType = request.getRequestType(); int requestType = request.getRequestType();
boolean isGetPostOrHead = requestType == IRequest.TYPE_GET || requestType == IRequest.TYPE_POST || requestType == IRequest.TYPE_HEAD; boolean isGetPostOrHead = requestType == IRequest.TYPE_GET || requestType == IRequest.TYPE_POST || requestType == IRequest.TYPE_HEAD;
boolean isWebdav = requestType == IRequest.TYPE_WEBDAV_COPY || requestType == IRequest.TYPE_WEBDAV_LOCK || requestType == IRequest.TYPE_WEBDAV_MKCOL || requestType == IRequest.TYPE_WEBDAV_MOVE || requestType == IRequest.TYPE_WEBDAV_PROPFIND || requestType == IRequest.TYPE_WEBDAV_PROPPATCH || requestType == IRequest.TYPE_WEBDAV_UNLOCK; boolean isWebdav = requestType == IRequest.TYPE_WEBDAV_COPY || requestType == IRequest.TYPE_WEBDAV_LOCK || requestType == IRequest.TYPE_WEBDAV_MKCOL || requestType == IRequest.TYPE_WEBDAV_MOVE || requestType == IRequest.TYPE_WEBDAV_PROPFIND || requestType == IRequest.TYPE_WEBDAV_PROPPATCH || requestType == IRequest.TYPE_WEBDAV_UNLOCK;
@@ -804,12 +804,12 @@ public void processRequest(final IRequest request, final IResponse response, fin
IRunnable runnable = new IRunnable() { IRunnable runnable = new IRunnable() {
public Object run() { public Object run() {
if(resourceRequestHandler != null) { if(resourceRequestHandler != null) {
resourceRequestHandler.processRequest(handler, requestPath, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure); resourceRequestHandler.processRequest(handler, requestPath, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure, connectionContext);
}//if// }//if//
else { else {
try { try {
//Note: Synchronization on the session is not necessary now that we single thread all requests for a given session.// //Note: Synchronization on the session is not necessary now that we single thread all requests for a given session.//
handler.processRequest(request, response, (SessionData) session.getApplicationData(), isSecure ? (SecureSessionData) session.getApplicationSecureData() : null); handler.processRequest(request, response, (SessionData) session.getApplicationData(), isSecure ? (SecureSessionData) session.getApplicationSecureData() : null, connectionContext);
}//try// }//try//
catch(Throwable e) { catch(Throwable e) {
Debug.log(e); Debug.log(e);
@@ -858,7 +858,7 @@ public void processRequest(final IRequest request, final IResponse response, fin
}//else if// }//else if//
else { else {
//TODO: Should we synchronize on the session here? The app code could interact with the session and cause problems otherwise. //TODO: Should we synchronize on the session here? The app code could interact with the session and cause problems otherwise.
resourceRequestHandler.processRequest(path, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure); resourceRequestHandler.processRequest(path, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure, connectionContext);
}//else// }//else//
}//else if// }//else if//
else if(isWebdav) { else if(isWebdav) {
@@ -867,7 +867,7 @@ public void processRequest(final IRequest request, final IResponse response, fin
}//if// }//if//
else { else {
synchronized(session) { synchronized(session) {
webdavRequestHandler.processRequest(path, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure); webdavRequestHandler.processRequest(path, request, response, (SessionData) session.getApplicationData(), (SecureSessionData) (isSecure ? session.getApplicationSecureData() : null), isSecure, connectionContext);
//Update the repository with the session changes as necessary.// //Update the repository with the session changes as necessary.//
((WebSession) session).updateRepository(); ((WebSession) session).updateRepository();
}//synchronized// }//synchronized//

View File

@@ -561,7 +561,7 @@ public class WebServer {
* Provides a place for connection oriented data. * Provides a place for connection oriented data.
* <p>Note that a client server session can have multiple socket contexts (one for each socket) and that the socket context may be used to access multiple applications hosted on this server.</p> * <p>Note that a client server session can have multiple socket contexts (one for each socket) and that the socket context may be used to access multiple applications hosted on this server.</p>
*/ */
private class SocketContext extends AbstractSocketContext implements IWebApplicationContainerProvider { private class SocketContext extends AbstractSocketContext implements IWebApplicationContainerProvider, IConnectionContext {
public final int id; public final int id;
/** The server socket reference that created the socket. This will be null if the socket was not created with a server socket (shouldn't happen in a web server). */ /** The server socket reference that created the socket. This will be null if the socket was not created with a server socket (shouldn't happen in a web server). */
public ServerSocketContext serverSocketContext = null; public ServerSocketContext serverSocketContext = null;
@@ -617,6 +617,8 @@ public class WebServer {
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. */ /** Used to pass all traffic (once unencrypted) through another socket to another process. */
private PassThroughSocketContext passThroughSocketContext = null; 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. */
private LiteHashMap applicationDataMap;
public SocketContext(ServerSocketContext serverSocketContext) { public SocketContext(ServerSocketContext serverSocketContext) {
super(); super();
@@ -625,6 +627,25 @@ public class WebServer {
}//synchronized// }//synchronized//
}//SocketContext()// }//SocketContext()//
protected synchronized void close() { protected synchronized void close() {
try {
if(applicationDataMap != null) {
for(IIterator iterator = applicationDataMap.valueIterator(); iterator.hasNext(); ) {
Object next = iterator.next();
//Ensure the code keeps going even if there is a problem cleaning up.//
try {
if(next instanceof ISessionLifecycleAware) ((ISessionLifecycleAware) next).release();
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//for//
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
try {if(key != null && key.channel() != null) key.channel().close();} catch(Throwable e) {} try {if(key != null && key.channel() != null) key.channel().close();} catch(Throwable e) {}
try {if(key != null) key.cancel();} catch(Throwable e) {} try {if(key != null) key.cancel();} catch(Throwable e) {}
//Clean up after the response and request.// //Clean up after the response and request.//
@@ -634,6 +655,22 @@ public class WebServer {
passThroughSocketContext.close(); passThroughSocketContext.close();
}//if// }//if//
}//close()// }//close()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IConnectionContext#getApplicationData(java.lang.String)
*/
public Object getApplicationData(String key) {
return applicationDataMap == null ? null : applicationDataMap.get(key);
}//getApplicationData()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IConnectionContext#setApplicationData(java.lang.String, java.lang.Object)
*/
public void setApplicationData(String key, Object applicationData) {
if(applicationDataMap == null) {
applicationDataMap = new LiteHashMap(10);
}//if//
applicationDataMap.put(key, applicationData);
}//setApplicationData()//
/* (non-Javadoc) /* (non-Javadoc)
* @see com.foundation.web.server.WebServer.IWebApplicationContainerProvider#getWebApplicationContainer() * @see com.foundation.web.server.WebServer.IWebApplicationContainerProvider#getWebApplicationContainer()
*/ */
@@ -3045,7 +3082,7 @@ private boolean internalProcessClientRequest(final SocketContext context, Select
}//if// }//if//
//Send the request to the application to be processed if we are not dealing with an error.// //Send the request to the application to be processed if we are not dealing with an error.//
else if(application != null) { else if(application != null) {
application.processRequest(request, response, session, allowSecureAccess, clientHadBadSession); application.processRequest(request, response, session, allowSecureAccess, clientHadBadSession, context);
}//else if// }//else if//
//Convert the response into a byte stream and send it via the socket.// //Convert the response into a byte stream and send it via the socket.//

View File

@@ -0,0 +1,21 @@
package com.foundation.web.interfaces;
/**
* The context object available to the web application where connection related data can be stored and released within the context of a single connection between the client and server.
* Used to store data related to a single socket between the web browser and web server, such as a socket to a service that the server needs in order to service the client's requests.
* The data will be given an opportunity to be cleaned up upon the socket's closure if it implements com.foundation.web.interfaces.ISessionLifecycleAware.
*/
public interface IConnectionContext {
/**
* Gets the application data in the connection context's application data map by the given key.
* @return The application specific data element.
*/
public Object getApplicationData(String key);
/**
* Stores the application data in the connection context's application data map by the given key.
* The application data may implement ISessionLifecycleAware if it should be called when the session is being released. This will only be called for a normal closing of the session. The session may still be restored at some time in the future.
* @param key The key for the data. If the key is logically equal (equivalent) to an existing key, then the existing data will be replaced and returned.
* @param applicationData The application specific data element.
*/
public void setApplicationData(String key, Object applicationData);
}//IConnectionContext//

View File

@@ -1,4 +1,4 @@
package com.foundation.web; package com.foundation.web.interfaces;
/** /**
* An interface implemented by objects stored in the SessionData's application data mapping (as the value) when the object wants notification that the session has been released. * An interface implemented by objects stored in the SessionData's application data mapping (as the value) when the object wants notification that the session has been released.

View File

@@ -168,8 +168,9 @@ public void createSecureSession(ISession session);
* @param session The non-secure session. This may be null if the application does not use a session object. * @param session The non-secure session. This may be null if the application does not use a session object.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id. * @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
* @param clientHadBadSession Whether the client's request contained a session reference that could not be found on the server. * @param clientHadBadSession Whether the client's request contained a session reference that could not be found on the server.
* @param connectionContext The context object for the connection (socket) between the client (web browser) and server (web server).
*/ */
public void processRequest(IRequest request, IResponse response, ISession session, boolean isSecure, boolean clientHadBadSession); public void processRequest(IRequest request, IResponse response, ISession session, boolean isSecure, boolean clientHadBadSession, IConnectionContext connectionContext);
/** /**
* Releases any resources associated with the web server application. * Releases any resources associated with the web server application.
*/ */