Har! More progress mateys.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-05-29 03:27:59 +00:00
parent 6717f8d6e5
commit 33d93d3374
26 changed files with 947 additions and 124 deletions
@@ -0,0 +1,26 @@
//
// $Id: Authenticator.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import com.samskivert.cocktail.cher.net.AuthRequest;
import com.samskivert.cocktail.cher.net.AuthResponse;
/**
* The authenticator is a pluggable component of the authentication
* framework. It is provided with an auth request object and is expected
* to return an auth response object indicating whether or not the client
* is authenticated. <code>authenticate()</code> is invoked on the authmgr
* thread which means that it can access databases or other external
* (slow) information sources without concern for unduly blocking the
* normal operation of the server. Of course, authentication requests are
* processed serially, so they shouldn't take <em>too</em> long.
*/
public interface Authenticator
{
/**
* Requests that the authenticator process the supplied request and
* provide a response object indicating success or failure.
*/
public AuthResponse process (AuthRequest request);
}
@@ -0,0 +1,23 @@
//
// $Id: DummyAuthenticator.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.net.*;
import com.samskivert.cocktail.cher.server.net.Authenticator;
import com.samskivert.cocktail.cher.util.Codes;
public class DummyAuthenticator implements Authenticator
{
/**
* We just accept all authentication requests.
*/
public AuthResponse process (AuthRequest req)
{
Log.info("Accepting request: " + req);
AuthResponseData rdata = new AuthResponseData();
rdata.code = Codes.SUCCESS;
return new AuthResponse(rdata);
}
}
@@ -0,0 +1,64 @@
//
// $Id: PresentsServer.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.server.net.AuthManager;
import com.samskivert.cocktail.cher.server.net.ConnectionManager;
/**
* The cher server provides a central point of access to the various
* facilities that make up the cher layer of the system.
*/
public class CherServer
{
/** The authentication manager. */
public static AuthManager authmgr;
/** The manager of network connections. */
public static ConnectionManager cmgr;
/**
* Initializes all of the server services and prepares for operation.
*/
public static void init ()
{
try {
// create our authentication manager
authmgr = new AuthManager(new DummyAuthenticator());
// create our connection manager
cmgr = new ConnectionManager(authmgr);
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
}
}
/**
* Starts up all of the server services and enters the main server
* event loop.
*/
public static void start ()
{
// start up the auth manager
authmgr.start();
// start up the connection manager
cmgr.start();
// for now, just block because we've nothing to do
try {
synchronized (CherServer.class) {
CherServer.class.wait();
}
} catch (InterruptedException ie) {
}
}
public static void main (String[] args)
{
init();
start();
}
}
@@ -0,0 +1,65 @@
//
// $Id: AuthManager.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import com.samskivert.util.LoopingThread;
import com.samskivert.util.Queue;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.net.AuthRequest;
import com.samskivert.cocktail.cher.net.AuthResponse;
/**
* The authentication manager takes care of the authentication process.
* Authentication happens on multiple threads. The conmgr thread parses
* the authentication request and passes it on to the authmgr thread. The
* authmgr thread invokes the (pluggable) authenticator to do the actual
* authentication. Then the response message is queued up to be delivered
* by the conmgr thread.
*
* <p> This structure prevents authentication to take place
* asynchronously, but in a controlled manner. Only one authentication
* will be processed at a time, but the dobj and conmgr threads will
* continue to operate independent of the authentication process.
*/
public class AuthManager extends LoopingThread
{
public AuthManager (Authenticator author)
{
_author = author;
}
/**
* Puts an authenticating connection on the queue. The connection will
* be authenticated and an auth response delivered.
*/
public void postAuthingConnection (AuthingConnection aconn)
{
_authq.append(aconn);
}
/**
* Process auth requests.
*/
protected void iterate ()
{
// grab the next authing connection from the queue
AuthingConnection aconn = (AuthingConnection)_authq.get();
try {
// instruct the authenticator to process the auth request
AuthResponse rsp = _author.process(aconn.getAuthRequest());
// now ship the response back
aconn.postMessage(rsp);
} catch (Exception e) {
Log.warning("Failure processing authreq [conn=" + aconn + "].");
Log.logStackTrace(e);
}
}
protected Authenticator _author;
protected Queue _authq = new Queue();
}
@@ -0,0 +1,63 @@
//
// $Id: AuthingConnection.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import java.io.IOException;
import ninja2.core.io_core.nbio.NonblockingSocket;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.net.UpstreamMessage;
import com.samskivert.cocktail.cher.net.AuthRequest;
/**
* The authing connection manages the client connection until
* authentication has completed (for better or for worse).
*/
public class AuthingConnection extends Connection
{
/**
* Creates a new authing connection object that will manage the
* authentication process for the suppled client socket.
*/
public AuthingConnection (ConnectionManager cmgr,
NonblockingSocket socket)
throws IOException
{
super(cmgr, socket);
}
/**
* Called when a new message has arrived from the client.
*/
public void handleMessage (UpstreamMessage msg)
{
try {
// keep a handle on our auth request
_authreq = (AuthRequest)msg;
// post ourselves for processing by the authmgr
_cmgr.getAuthManager().postAuthingConnection(this);
} catch (ClassCastException cce) {
Log.warning("Received non-authreq message during " +
"authentication process [conn=" + this +
", msg=" + msg + "].");
}
}
/**
* Returns a reference to the auth request currently being processed
* by this authing connection.
*/
public AuthRequest getAuthRequest ()
{
return _authreq;
}
protected int _state = AWAITING_AUTH_REQUEST;
protected AuthRequest _authreq;
protected static final int AWAITING_AUTH_REQUEST = 0;
protected static final int PROCESSING_AUTH_REQUEST = 1;
}
@@ -0,0 +1,115 @@
//
// $Id: Connection.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import java.io.*;
import ninja2.core.io_core.nbio.*;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.io.FramedInputStream;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
import com.samskivert.cocktail.cher.net.UpstreamMessage;
import com.samskivert.cocktail.cher.net.DownstreamMessage;
/**
* The base connection class implements the net event handler interface
* and processes raw incoming network data into a stream of parsed
* <code>UpstreamMessage</code> objects. It also provides the means to
* send messages to the client and facilities for checking delinquency.
*/
public abstract class Connection implements NetEventHandler
{
/**
* Constructs a connection object that is associated with the supplied
* socket.
*
* @param cmgr The connection manager with which this connection is
* associated.
* @param socket The socket from which we'll be reading messages.
*/
public Connection (ConnectionManager cmgr, NonblockingSocket socket)
throws IOException
{
_cmgr = cmgr;
_socket = socket;
// create a select item that will allow us to be incorporated into
// the main event polling loop
_selitem = new SelectItem(_socket, this, Selectable.READ_READY);
// create our input streams
_in = _socket.getInputStream();
_out = _socket.getOutputStream();
_fin = new FramedInputStream();
_din = new DataInputStream(_fin);
}
/**
* Returns the select item associated with this connection.
*/
public SelectItem getSelectItem ()
{
return _selitem;
}
/**
* Returns the output stream associated with this connection. This
* should only be used by the connection manager.
*/
public OutputStream getOutputStream ()
{
return _out;
}
/**
* Called when our client socket has data available for reading.
*/
public void handleEvent (Selectable source, short events)
{
try {
// read the available data and see if we have a whole frame
if (_fin.readFrame(_in)) {
// parse the message and pass it on
handleMessage((UpstreamMessage)
TypedObjectFactory.readFrom(_din));
}
} catch (EOFException eofe) {
// let the connection manager know that we done went away
_cmgr.connectionClosed(this);
} catch (IOException ioe) {
Log.warning("Error reading message from socket " +
"[socket=" + _socket + ", error=" + ioe + "].");
// let the connection manager know that something when awry
_cmgr.connectionFailed(this, ioe);
}
}
/**
* Called when a complete message has been parsed from incoming
* network data.
*/
public abstract void handleMessage (UpstreamMessage msg);
/**
* Posts a downstream message for delivery to this connection. The
* message will be delivered by the conmgr thread as soon as it gets
* to it.
*/
public void postMessage (DownstreamMessage msg)
{
// pass this along to the connection manager
_cmgr.postMessage(this, msg);
}
protected ConnectionManager _cmgr;
protected NonblockingSocket _socket;
protected SelectItem _selitem;
protected InputStream _in;
protected OutputStream _out;
protected FramedInputStream _fin;
protected DataInputStream _din;
}
@@ -0,0 +1,226 @@
//
// $Id: ConnectionManager.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import java.io.DataOutputStream;
import java.io.IOException;
import ninja2.core.io_core.nbio.*;
import com.samskivert.util.LoopingThread;
import com.samskivert.util.Tuple;
import com.samskivert.util.Queue;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.io.FramingOutputStream;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
import com.samskivert.cocktail.cher.net.DownstreamMessage;
import com.samskivert.cocktail.cher.net.Registry;
/**
* The connection manager manages the socket on which connections are
* received. It creates connection objects to manage each individual
* connection, but those connection objects interact closely with the
* connection manager because network I/O is done via a poll()-like
* mechanism rather than via threads.
*/
public class ConnectionManager extends LoopingThread
{
/**
* Constructs and initialized a connection manager (binding the socket
* on which it will listen for client connections).
*
* @param authmgr The authentication manager to use when
* authenticating client connections.
*/
public ConnectionManager (AuthManager authmgr)
throws IOException
{
int port = DEFAULT_CM_PORT;
// keep a handle on our authentication manager
_authmgr = authmgr;
// we use this to wait for activity on our sockets
_selset = new SelectSet();
// create our listening socket and add it to the select set
_listener = new NonblockingServerSocket(port);
_litem = new SelectItem(_listener, Selectable.ACCEPT_READY);
// when an ACCEPT_READY event happens, we do this:
_litem.obj = new NetEventHandler() {
public void handleEvent (Selectable item, short events) {
acceptConnection();
}
};
_selset.add(_litem);
// we'll use these for sending messages to clients
_framer = new FramingOutputStream();
_dout = new DataOutputStream(_framer);
}
/**
* Returns a reference to the auth manager being used to authenticate
* connections.
*/
public AuthManager getAuthManager ()
{
return _authmgr;
}
protected void willStart ()
{
Log.info("Connection Manager thread running.");
}
/**
* Performs the select loop. This is the body of the conmgr thread.
*/
protected void iterate ()
{
// send any messages that are waiting on the outgoing queue
Tuple tup;
while ((tup = (Tuple)_outq.getNonBlocking()) != null) {
Connection conn = (Connection)tup.left;
DownstreamMessage outmsg = (DownstreamMessage)tup.right;
try {
// first flatten the message (and frame it)
TypedObjectFactory.writeTo(_dout, outmsg);
// then write framed message to real output stream
_framer.writeFrameAndReset(conn.getOutputStream());
} catch (IOException ioe) {
connectionFailed(conn, ioe);
}
}
// check for incoming network events
int ecount = _selset.select(SELECT_LOOP_TIME);
if (ecount == 0) {
return;
}
// process those events
SelectItem[] active = _selset.getEvents();
for (int i = 0; i < active.length; i++) {
try {
SelectItem item = active[i];
NetEventHandler handler = (NetEventHandler)item.obj;
handler.handleEvent(item.getSelectable(), item.revents);
} catch (Exception e) {
Log.warning("Error processing network data.");
Log.logStackTrace(e);
}
}
}
protected void didShutdown ()
{
Log.info("Connection Manager thread exited.");
}
/**
* Called by our net event handler when a new connection is ready to
* be accepted on our listening socket.
*/
protected void acceptConnection ()
{
NonblockingSocket socket = null;
try {
socket = _listener.nbAccept();
if (socket == null) {
// in theory this shouldn't happen because we got an
// ACCEPT_READY event, but better safe than sorry
Log.info("Psych! Got ACCEPT_READY, but no connection.");
return;
}
// create a new authing connection object to manage the
// authentication of this client connection
AuthingConnection acon = new AuthingConnection(this, socket);
// wire this connection into the select set
_selset.add(acon.getSelectItem());
} catch (IOException ioe) {
Log.warning("Failure accepting new connection: " + ioe);
// make sure we don't leak a socket if something went awry
if (socket != null) {
try {
socket.close();
} catch (IOException ioe2) {
Log.warning("Failed closing aborted connection: " + ioe2);
}
}
}
}
/**
* Called by a connection when it has a downstream message that needs
* to be delivered.
*/
void postMessage (Connection conn, DownstreamMessage msg)
{
// sanity check
if (conn == null || msg == null) {
Log.warning("Bogosity.");
Thread.dumpStack();
} else {
// slap both these suckers onto the outgoing message queue
_outq.append(new Tuple(conn, msg));
}
}
/**
* Called by a connection if it experiences a network failure.
*/
void connectionFailed (Connection conn, IOException ioe)
{
// remove this connection from the select set
_selset.remove(conn.getSelectItem());
// let our observers know what's up
}
/**
* Called by a connection when it discovers that it's closed.
*/
void connectionClosed (Connection conn)
{
Log.info("Removing closed connection: " + conn);
// remove this connection from the select set
_selset.remove(conn.getSelectItem());
// let our observers know what's up
}
protected AuthManager _authmgr;
protected SelectSet _selset;
protected NonblockingServerSocket _listener;
protected SelectItem _litem;
protected Queue _outq = new Queue();
protected FramingOutputStream _framer;
protected DataOutputStream _dout;
/** The default port on which we listen for connections. */
protected static final int DEFAULT_CM_PORT = 4007;
/**
* How long we wait for network events before checking our running
* flag to see if we should still be running.
*/
protected static final int SELECT_LOOP_TIME = 30 * 1000;
// register our shared objects
static {
Registry.registerTypedObjects();
}
}
@@ -0,0 +1,45 @@
//
// $Id: ConnectionObserver.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import java.io.IOException;
/**
* A connection observer can be registered with the connection manager to
* hear about new connections and to be notified when connections fail or
* are closed. Only fully authenticated connections will be passed on to
* the connection observer. Connections that fail to authenticate will be
* handled entirely within the confines of the connection manager.
*
* @see ConnectionManager
* @see Connection
*/
public interface ConnectionObserver
{
/**
* Called when a new connection is established with the connection
* manager.
*
* @param conn The newly established connection.
*/
public void connectionEstablished (Connection conn);
/**
* Called if a connection fails for any reason. If a connection fails,
* <code>connectionClosed</code> will not be called. This call to
* <code>connectionFailued</code> is the last the observers will hear
* about it.
*
* @param conn The connection in that failed.
* @param fault The exception associated with the failure.
*/
public void connectionFailed (Connection conn, IOException fault);
/**
* Called when a connection has been closed in an orderly manner.
*
* @param conn The recently closed connection.
*/
public void connectionClosed (Connection conn);
}
@@ -0,0 +1,27 @@
//
// $Id: NetEventHandler.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import ninja2.core.io_core.nbio.Selectable;
/**
* When a network event arrives on a particular <code>Selectable</code>,
* the connection manager calls the net event handler associated with that
* selectable to process the event. There are only a few handlers (and
* probably only ever will be): the one that accepts new connections, the
* one that deals with a connection while the client is authenticating and
* the one that processes messages from authenticated connections.
* Providing this interface prevents us from having to do a bunch of
* inefficient and ugly comparisons; instead we can simply call through an
* interface method to the proper code.
*/
public interface NetEventHandler
{
/**
* Called when a network event has occurred on the supplied source.
* The <code>events</code> parameter indicates which event or events
* have occurred.
*/
public void handleEvent (Selectable source, short events);
}
@@ -0,0 +1,33 @@
//
// $Id: RunningConnection.java,v 1.1 2001/05/29 03:27:59 mdb Exp $
package com.samskivert.cocktail.cher.server.net;
import java.io.IOException;
import ninja2.core.io_core.nbio.NonblockingSocket;
import com.samskivert.cocktail.cher.net.UpstreamMessage;
/**
* A running connection object represents a fully operational client
* connection to the server.
*/
public class RunningConnection extends Connection
{
/**
* Constructs a new running connection object to manage the supplied
* client socket.
*/
public RunningConnection (ConnectionManager cmgr,
NonblockingSocket socket)
throws IOException
{
super(cmgr, socket);
}
/**
* Called when a new message has arrived from the client.
*/
public void handleMessage (UpstreamMessage msg)
{
}
}