Convert Narya (most of the way) over to a Maven Ant task based build. The
ActionScript bits remain belligerent, but the Java stuff is mostly shipshape. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* A base class that is used to wire up signal handling in one of a couple of possible ways.
|
||||
*/
|
||||
public abstract class AbstractSignalHandler
|
||||
{
|
||||
/**
|
||||
* Initializes this signal handler.
|
||||
*/
|
||||
public boolean init ()
|
||||
{
|
||||
return registerHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal handler implementations should wire themselves up in the call to this method.
|
||||
*
|
||||
* @return true if the handlers were successfully wired up, false if they were not able to be
|
||||
* wired up.
|
||||
*/
|
||||
protected abstract boolean registerHandlers ();
|
||||
|
||||
/**
|
||||
* Implementations should call this method when a SIGTERM is received.
|
||||
*/
|
||||
protected void termReceived ()
|
||||
{
|
||||
log.info("Shutdown initiated by TERM signal.");
|
||||
_server.queueShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations should call this method when a SIGINT is received.
|
||||
*/
|
||||
protected void intReceived ()
|
||||
{
|
||||
log.info("Shutdown initiated by INT signal.");
|
||||
_server.queueShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations should call this method when a SIGHUP is received.
|
||||
*/
|
||||
protected void hupReceived ()
|
||||
{
|
||||
log.info(_repmgr.generateReport(ReportManager.DEFAULT_TYPE));
|
||||
}
|
||||
|
||||
protected void usr2Received ()
|
||||
{
|
||||
if (_usr2receiver != null) {
|
||||
_usr2receiver.received();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(optional=true) protected SignalReceiver _usr2receiver;
|
||||
@Inject protected PresentsServer _server;
|
||||
@Inject protected ReportManager _repmgr;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The authenticator is a pluggable component of the authentication framework. The base class
|
||||
* handles the basic mechanics of authentication and a system would extend the base authenticator
|
||||
* and add code that does the actual client authentication.
|
||||
*/
|
||||
public abstract class Authenticator
|
||||
{
|
||||
/**
|
||||
* Called by the connection management code when an authenticating connection has received its
|
||||
* authentication request from the client.
|
||||
*/
|
||||
public void authenticateConnection (Invoker invoker, final AuthingConnection conn,
|
||||
final ResultListener<AuthingConnection> onComplete)
|
||||
{
|
||||
final AuthRequest req = conn.getAuthRequest();
|
||||
final AuthResponseData rdata = createResponseData();
|
||||
final AuthResponse rsp = new AuthResponse(rdata);
|
||||
|
||||
invoker.postUnit(new Invoker.Unit("authenticateConnection") {
|
||||
@Override
|
||||
public boolean invoke () {
|
||||
try {
|
||||
processAuthentication(conn, rsp);
|
||||
if (AuthResponseData.SUCCESS.equals(rdata.code) &&
|
||||
conn.getAuthName() == null) { // fail early, fail (less) often
|
||||
throw new IllegalStateException("Authenticator failed to provide authname");
|
||||
}
|
||||
} catch (AuthException e) {
|
||||
rdata.code = e.getMessage();
|
||||
} catch (Exception e) {
|
||||
log.warning("Error authenticating user", "areq", req, e);
|
||||
rdata.code = AuthCodes.SERVER_ERROR;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult () {
|
||||
// stuff a reference to the auth response into the connection so that we have
|
||||
// access to it later in the authentication process
|
||||
conn.setAuthResponse(rsp);
|
||||
|
||||
// send the response back to the client
|
||||
conn.postMessage(rsp);
|
||||
|
||||
// if the authentication request was granted, let the connection manager know that
|
||||
// we just authed
|
||||
if (AuthResponseData.SUCCESS.equals(rdata.code)) {
|
||||
onComplete.requestCompleted(conn);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new AuthResponseData instance to use for authenticating a connection.
|
||||
*/
|
||||
protected AuthResponseData createResponseData ()
|
||||
{
|
||||
return new AuthResponseData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the authentication for the specified connection. The method may return after it has
|
||||
* stuffed a valid response code in rsp.getData().code.
|
||||
*
|
||||
* @param conn The client connection.
|
||||
* @param rsp The response to the client, which will already contain an AuthResponseData
|
||||
* created by {@link #createResponseData}.
|
||||
*/
|
||||
protected abstract void processAuthentication (AuthingConnection conn, AuthResponse rsp)
|
||||
throws Exception;
|
||||
|
||||
/** An exception that can be thrown during {@link #processAuthentication}. The results of
|
||||
* {@link #getMessage} string will be filled in as the auth failure code. */
|
||||
protected static class AuthException extends Exception
|
||||
{
|
||||
public AuthException (String code) {
|
||||
super(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
|
||||
/**
|
||||
* Handles certain special kinds of authentications and passes the remainder through to the default
|
||||
* authenticator.
|
||||
*/
|
||||
public abstract class ChainedAuthenticator extends Authenticator
|
||||
{
|
||||
/**
|
||||
* Derived classes should implement this method and return true if the supplied connection is
|
||||
* one that they should authenticate.
|
||||
*/
|
||||
public abstract boolean shouldHandleConnection (AuthingConnection conn);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
|
||||
/**
|
||||
* Contains information about a client only tracked on the server. This is configured as a local
|
||||
* attribute on the {@link ClientObject}.
|
||||
*
|
||||
* <p> Note: this object implements streamable so that it can be cleanly passed between servers in
|
||||
* a peered environment. It is never sent to the client.
|
||||
*/
|
||||
public class ClientLocal extends SimpleStreamableObject
|
||||
{
|
||||
// nothing to track at this level
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Lifecycle;
|
||||
import com.samskivert.util.ObserverList;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.annotation.EventThread;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
import com.threerings.presents.server.net.Connection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The client manager is responsible for managing client sessions which are slightly more than just
|
||||
* connections. Clients persist in the absence of connections in case a user goes bye bye
|
||||
* unintentionally and wants to reconnect and continue their session.
|
||||
*
|
||||
* <p> The client manager operates with thread safety because it is called both from the conmgr
|
||||
* thread (to notify of connections showing up or going away) and from the dobjmgr thread (when
|
||||
* clients are given the boot for application-defined reasons).
|
||||
*/
|
||||
@Singleton
|
||||
public class ClientManager
|
||||
implements ClientResolutionListener, ReportManager.Reporter, Lifecycle.Component
|
||||
{
|
||||
/**
|
||||
* Used by {@link ClientManager#applyToClient}.
|
||||
*/
|
||||
public static interface ClientOp
|
||||
{
|
||||
/**
|
||||
* Called with the resolved client object.
|
||||
*/
|
||||
void apply (ClientObject clobj);
|
||||
|
||||
/**
|
||||
* Called if the client resolution fails.
|
||||
*/
|
||||
void resolutionFailed (Exception e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by entities that wish to track when clients initiate and end sessions on this server.
|
||||
*/
|
||||
public static interface ClientObserver
|
||||
{
|
||||
/**
|
||||
* Called when a client has authenticated and been resolved and has started their session.
|
||||
*/
|
||||
void clientSessionDidStart (PresentsSession session);
|
||||
|
||||
/**
|
||||
* Called when a client has logged off or been forcibly logged off due to inactivity and
|
||||
* has thus ended their session.
|
||||
*/
|
||||
void clientSessionDidEnd (PresentsSession session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a client manager that will interact with the supplied connection manager.
|
||||
*/
|
||||
@Inject public ClientManager (ReportManager repmgr, Lifecycle cycle)
|
||||
{
|
||||
repmgr.registerReporter(this);
|
||||
cycle.addComponent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the injector we'll use to resolve dependencies for {@link PresentsSession}
|
||||
* instances.
|
||||
*/
|
||||
public void setInjector (Injector injector)
|
||||
{
|
||||
_injector = injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default factory for creating {@link PresentsSession} and {@link
|
||||
* ClientResolver} classes for authenticated client connections. All factories added via {@link
|
||||
* #addSessionFactory} will be offered a chance to handle sessions before this factory of last
|
||||
* resort.
|
||||
*/
|
||||
public void setDefaultSessionFactory (SessionFactory factory)
|
||||
{
|
||||
_factories.set(_factories.size()-1, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a session factory to the chain. This factory will be offered a chance to resolve
|
||||
* sessions before passing the buck to the next factory in the chain.
|
||||
*/
|
||||
public void addSessionFactory (SessionFactory factory)
|
||||
{
|
||||
_factories.add(0, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of client resolutions are currently happening.
|
||||
*/
|
||||
public int getOutstandingResolutionCount ()
|
||||
{
|
||||
return _penders.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of client sessions (some may be disconnected).
|
||||
*/
|
||||
public int getClientCount ()
|
||||
{
|
||||
synchronized (_usermap) {
|
||||
return _usermap.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all sessions logged in from the given IP in the form returned from
|
||||
* {@link InetAddress#getAddress()}.
|
||||
*/
|
||||
public List<PresentsSession> getSessionsForAddress (byte[] addr)
|
||||
{
|
||||
List<PresentsSession> sessions = Lists.newArrayListWithExpectedSize(1);
|
||||
synchronized (_usermap) {
|
||||
for (PresentsSession session : _usermap.values()) {
|
||||
InetAddress sessionAddr = session.getInetAddress();
|
||||
if (sessionAddr == null) {
|
||||
continue;
|
||||
}
|
||||
if (Arrays.equals(addr, sessionAddr.getAddress())) {
|
||||
sessions.add(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connected clients.
|
||||
*/
|
||||
public int getConnectionCount ()
|
||||
{
|
||||
return _conmap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable over all active client objects.
|
||||
*/
|
||||
public Iterable<ClientObject> clientObjects ()
|
||||
{
|
||||
return _objmap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates all active client objects.
|
||||
*/
|
||||
public Iterator<ClientObject> enumerateClientObjects ()
|
||||
{
|
||||
return _objmap.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an observer that will be notified when clients start and end their sessions.
|
||||
*/
|
||||
public void addClientObserver (ClientObserver observer)
|
||||
{
|
||||
_clobservers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an observer previously registered with {@link #addClientObserver}.
|
||||
*/
|
||||
public void removeClientObserver (ClientObserver observer)
|
||||
{
|
||||
_clobservers.remove(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client instance that manages the client session for the specified authentication
|
||||
* username or null if that client is not currently connected to the server.
|
||||
*/
|
||||
public PresentsSession getClient (Name authUsername)
|
||||
{
|
||||
synchronized (_usermap) {
|
||||
return _usermap.get(authUsername);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client object associated with the specified username. This will return null
|
||||
* unless the client object is resolved for some reason (like they are logged on).
|
||||
*/
|
||||
public ClientObject getClientObject (Name username)
|
||||
{
|
||||
return _objmap.get(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified client, applies the supplied client operation to them and releases
|
||||
* the client.
|
||||
*/
|
||||
public void applyToClient (Name username, ClientOp clop)
|
||||
{
|
||||
resolveClientObject(username, new ClientOpResolver(clop));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the client object for the specified user be resolved. <em>Note:</em> this
|
||||
* <b>must</b> be paired with a call to {@link #releaseClientObject} when the caller is
|
||||
* finished with the client object.
|
||||
*/
|
||||
public synchronized void resolveClientObject (
|
||||
final Name username, final ClientResolutionListener listener)
|
||||
{
|
||||
// look to see if the client object is already resolved
|
||||
final ClientObject clobj = _objmap.get(username);
|
||||
if (clobj != null) {
|
||||
// report that the client is resolved on the dobjmgr thread to provide equivalent
|
||||
// behavior to the case where we actually have to do the resolution
|
||||
clobj.reference();
|
||||
_omgr.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
listener.clientResolved(username, clobj);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if it's currently being resolved
|
||||
ClientResolver clr = _penders.get(username);
|
||||
if (clr != null) {
|
||||
// throw this guy onto the bandwagon
|
||||
clr.addResolutionListener(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
// figure out our client resolver class
|
||||
Class<? extends ClientResolver> resolverClass = null;
|
||||
for (SessionFactory factory : _factories) {
|
||||
if ((resolverClass = factory.getClientResolverClass(username)) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// create a client resolver instance which will create our client object, populate it
|
||||
// and notify the listeners
|
||||
clr = _injector.getInstance(resolverClass);
|
||||
clr.init(username);
|
||||
clr.addResolutionListener(this);
|
||||
clr.addResolutionListener(listener);
|
||||
_penders.put(username, clr);
|
||||
|
||||
// create and register our client object and give it back to the client resolver; we
|
||||
// need to do this on the dobjmgr thread since we're registering an object
|
||||
final ClientResolver fclr = clr;
|
||||
_omgr.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
ClientObject clobj = fclr.createClientObject();
|
||||
clobj.setLocal(ClientLocal.class, fclr.createLocalAttribute());
|
||||
clobj.setPermissionPolicy(fclr.createPermissionPolicy());
|
||||
fclr.objectAvailable(_omgr.registerObject(clobj));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
// let the listener know that we're hosed
|
||||
listener.resolutionFailed(username, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a client object that was obtained via a call to {@link #resolveClientObject}. If
|
||||
* this caller is the last reference, the object will be flushed and destroyed.
|
||||
*/
|
||||
public void releaseClientObject (Name username)
|
||||
{
|
||||
ClientObject clobj = _objmap.get(username);
|
||||
if (clobj == null) {
|
||||
log.info("Requested to release unmapped client object", "username", username);
|
||||
return;
|
||||
}
|
||||
|
||||
// decrement the reference count and stop here if there are remaining references
|
||||
if (clobj.release()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Destroying client " + clobj.who() + ".");
|
||||
|
||||
// we're all clear to go; remove the mapping
|
||||
_objmap.remove(username);
|
||||
|
||||
// and destroy the object itself
|
||||
_omgr.destroyObject(clobj.getOid());
|
||||
}
|
||||
|
||||
// from interface Lifecycle.Component
|
||||
public void init ()
|
||||
{
|
||||
// start up an interval that will check for and flush expired sessions (this will be
|
||||
// canceled when the omgr shuts down)
|
||||
_omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
flushSessions();
|
||||
}
|
||||
}).schedule(SESSION_FLUSH_INTERVAL, true);
|
||||
}
|
||||
|
||||
// from interface Lifecycle.Component
|
||||
public void shutdown ()
|
||||
{
|
||||
log.info("Client manager shutting down", "ccount", _usermap.size());
|
||||
|
||||
// inform all of our clients that they are being shut down
|
||||
synchronized (_usermap) {
|
||||
for (PresentsSession pc : _usermap.values()) {
|
||||
try {
|
||||
pc.shutdown();
|
||||
} catch (Exception e) {
|
||||
log.warning("Client choked in shutdown()",
|
||||
"client", StringUtil.safeToString(pc), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a currently connected client from <code>oldname</code> to <code>newname</code>.
|
||||
*
|
||||
* @return true if the client was found and renamed.
|
||||
*/
|
||||
protected boolean renameClientObject (Name oldname, Name newname)
|
||||
{
|
||||
ClientObject clobj = _objmap.remove(oldname);
|
||||
if (clobj == null) {
|
||||
log.warning("Requested to rename unmapped client object", "username", oldname,
|
||||
new Exception());
|
||||
return false;
|
||||
}
|
||||
_objmap.put(newname, clobj);
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface ClientResolutionListener
|
||||
public synchronized void clientResolved (Name username, ClientObject clobj)
|
||||
{
|
||||
// because we added ourselves as a client resolution listener, the client object reference
|
||||
// count was increased, but we're not a real resolver (who would have to call
|
||||
// releaseClientObject() to release their reference), so we release our reference
|
||||
// immediately
|
||||
clobj.release();
|
||||
|
||||
// stuff the object into the mapping table
|
||||
_objmap.put(username, clobj);
|
||||
|
||||
// and remove the resolution listener
|
||||
_penders.remove(username);
|
||||
}
|
||||
|
||||
// documentation inherited from interface ClientResolutionListener
|
||||
public synchronized void resolutionFailed (Name username, Exception reason)
|
||||
{
|
||||
// clear out their pending record
|
||||
_penders.remove(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection manager to let us know when a new connection has been established.
|
||||
*/
|
||||
public synchronized void connectionEstablished (
|
||||
Connection conn, Name authname, AuthRequest req, AuthResponse rsp)
|
||||
{
|
||||
String type = authname.getClass().getSimpleName();
|
||||
|
||||
// see if a session is already registered with this name
|
||||
PresentsSession session = getClient(authname);
|
||||
|
||||
if (session != null) {
|
||||
log.info("Resuming session", "type", type, "who", authname, "conn", conn);
|
||||
session.resumeSession(req, conn);
|
||||
|
||||
} else {
|
||||
log.info("Session initiated", "type", type, "who", authname, "conn", conn);
|
||||
// figure out our session class
|
||||
Class<? extends PresentsSession> sessionClass = null;
|
||||
for (SessionFactory factory : _factories) {
|
||||
if ((sessionClass = factory.getSessionClass(req)) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// create a new session and stick'em in the table
|
||||
session = _injector.getInstance(sessionClass);
|
||||
session.startSession(authname, req, conn, rsp.authdata);
|
||||
|
||||
// map their session instance
|
||||
synchronized (_usermap) {
|
||||
// we refetch the authname from the session for use in the map in case it decides
|
||||
// to do something crazy like rewrite it in startSession()
|
||||
_usermap.put(session.getAuthName(), session);
|
||||
}
|
||||
}
|
||||
|
||||
// map this connection to this session
|
||||
_conmap.put(conn, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection manager to let us know when a connection has failed.
|
||||
*/
|
||||
public synchronized void connectionFailed (Connection conn, IOException fault)
|
||||
{
|
||||
// remove the session from the connection map
|
||||
PresentsSession session = _conmap.remove(conn);
|
||||
if (session != null) {
|
||||
log.info("Unmapped failed session", "session", session, "conn", conn, "fault", fault);
|
||||
// let the session know the connection went away
|
||||
session.wasUnmapped();
|
||||
// and let the session know things went haywire
|
||||
session.connectionFailed(fault);
|
||||
|
||||
} else if (!(conn instanceof AuthingConnection)) {
|
||||
log.info("Unmapped connection failed?", "conn", conn, "fault", fault, new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection manager to let us know when a connection has been closed.
|
||||
*/
|
||||
public synchronized void connectionClosed (Connection conn)
|
||||
{
|
||||
// remove the session from the connection map
|
||||
PresentsSession session = _conmap.remove(conn);
|
||||
if (session != null) {
|
||||
log.debug("Unmapped session", "session", session, "conn", conn);
|
||||
// let the session know the connection went away
|
||||
session.wasUnmapped();
|
||||
|
||||
} else {
|
||||
log.info("Closed unmapped connection '" + conn + "'. " +
|
||||
"Session probably not yet authenticated.");
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface ReportManager.Reporter
|
||||
public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
|
||||
{
|
||||
report.append("* presents.ClientManager:\n");
|
||||
report.append("- Sessions: ");
|
||||
synchronized (_usermap) {
|
||||
report.append(_usermap.size()).append(" total, ");
|
||||
}
|
||||
report.append(_conmap.size()).append(" connected, ");
|
||||
report.append(_penders.size()).append(" pending\n");
|
||||
report.append("- Mapped users: ").append(_objmap.size()).append("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PresentsSession when it has started its session.
|
||||
*/
|
||||
@EventThread
|
||||
protected void clientSessionDidStart (final PresentsSession session)
|
||||
{
|
||||
// let the observers know
|
||||
_clobservers.apply(new ObserverList.ObserverOp<ClientObserver>() {
|
||||
public boolean apply (ClientObserver observer) {
|
||||
observer.clientSessionDidStart(session);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PresentsSession when it has ended its session.
|
||||
*/
|
||||
@EventThread
|
||||
protected void clientSessionDidEnd (final PresentsSession session)
|
||||
{
|
||||
// notify the observers that the session is ended
|
||||
_clobservers.apply(new ObserverList.ObserverOp<ClientObserver>() {
|
||||
public boolean apply (ClientObserver observer) {
|
||||
observer.clientSessionDidEnd(session);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PresentsSession to let us know that we can clear it entirely out of the system.
|
||||
*/
|
||||
protected void clearSession (PresentsSession session)
|
||||
{
|
||||
// remove the session from the username map
|
||||
PresentsSession rc;
|
||||
synchronized (_usermap) {
|
||||
rc = _usermap.remove(session.getAuthName());
|
||||
}
|
||||
|
||||
// sanity check just because we can
|
||||
if (rc == null) {
|
||||
log.info("Cleared session: unregistered!", "session", session);
|
||||
} else if (rc != session) {
|
||||
log.info("Cleared session: multiple!", "s1", rc, "s2", session);
|
||||
} else {
|
||||
log.info("Cleared session", "session", session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once per minute to check for sessions that have been disconnected too long and
|
||||
* forcibly end their sessions.
|
||||
*/
|
||||
protected void flushSessions ()
|
||||
{
|
||||
List<PresentsSession> victims = Lists.newArrayList();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// first build a list of our victims
|
||||
synchronized (_usermap) {
|
||||
for (PresentsSession session : _usermap.values()) {
|
||||
if (session.checkExpired(now)) {
|
||||
victims.add(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now end their sessions
|
||||
for (PresentsSession session : victims) {
|
||||
try {
|
||||
log.info("Session expired, ending session", "session", session,
|
||||
"dtime", (now-session.getNetworkStamp()) + "ms].");
|
||||
session.endSession();
|
||||
} catch (Exception e) {
|
||||
log.warning("Choke while flushing session", "victim", session, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Used by {@link ClientManager#applyToClient}. */
|
||||
protected class ClientOpResolver
|
||||
implements ClientResolutionListener
|
||||
{
|
||||
public ClientOpResolver (ClientOp clop) {
|
||||
_clop = clop;
|
||||
}
|
||||
|
||||
public void clientResolved (Name username, ClientObject clobj) {
|
||||
try {
|
||||
_clop.apply(clobj);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Client op failed", "username", username, "clop", _clop, e);
|
||||
|
||||
} finally {
|
||||
releaseClientObject(username);
|
||||
}
|
||||
}
|
||||
|
||||
public void resolutionFailed (Name username, Exception reason) {
|
||||
_clop.resolutionFailed(reason);
|
||||
}
|
||||
|
||||
protected ClientOp _clop;
|
||||
}
|
||||
|
||||
/** Used to resolve dependencies in {@link PresentsSession} instances that we create. */
|
||||
protected Injector _injector;
|
||||
|
||||
/** A mapping from auth username to session instances. */
|
||||
protected Map<Name, PresentsSession> _usermap = Maps.newHashMap();
|
||||
|
||||
/** A mapping from connections to session instances. */
|
||||
protected Map<Connection, PresentsSession> _conmap = Maps.newHashMap();
|
||||
|
||||
/** A mapping from usernames to client object instances. */
|
||||
protected Map<Name, ClientObject> _objmap = Maps.newHashMap();
|
||||
|
||||
/** A mapping of pending client resolvers. */
|
||||
protected Map<Name, ClientResolver> _penders = Maps.newHashMap();
|
||||
|
||||
/** Lets us know what sort of session classes to use. */
|
||||
protected List<SessionFactory> _factories = Lists.newArrayList(SessionFactory.DEFAULT);
|
||||
|
||||
/** Tracks registered {@link ClientObserver}s. */
|
||||
protected ObserverList<ClientObserver> _clobservers = ObserverList.newSafeInOrder();
|
||||
|
||||
// our injected dependencies
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
|
||||
/** The frequency with which we check for expired sessions. */
|
||||
protected static final long SESSION_FLUSH_INTERVAL = 60 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
|
||||
/**
|
||||
* Entites that wish to resolve client objects must implement this
|
||||
* interface so as to partake in the asynchronous process of client
|
||||
* object resolution.
|
||||
*/
|
||||
public interface ClientResolutionListener
|
||||
{
|
||||
/**
|
||||
* Called when resolution completed successfully.
|
||||
*/
|
||||
void clientResolved (Name username, ClientObject clobj);
|
||||
|
||||
/**
|
||||
* Called when resolution fails.
|
||||
*/
|
||||
void resolutionFailed (Name username, Exception reason);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.PermissionPolicy;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Used to resolve client data when a user starts a session (or when some other entity needs access
|
||||
* to a client object). Implementations will want to extend this class and override {@link
|
||||
* #resolveClientData}, making the necessary database calls and populating the client object
|
||||
* appropriately.
|
||||
*/
|
||||
public class ClientResolver extends Invoker.Unit
|
||||
{
|
||||
/**
|
||||
* Thrown during resolution if the client disconnects.
|
||||
*/
|
||||
public static class ClientDisconnectedException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this instance.
|
||||
*
|
||||
* @param username the username of the user to be resolved.
|
||||
*/
|
||||
public void init (Name username)
|
||||
{
|
||||
_username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resolution listener to this active resolver.
|
||||
*/
|
||||
public void addResolutionListener (ClientResolutionListener listener)
|
||||
{
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link ClientObject} derived class that should be created to kick off the
|
||||
* resolution process.
|
||||
*/
|
||||
public ClientObject createClientObject ()
|
||||
{
|
||||
return new ClientObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a record that will be maintained only on the server to track client related bits.
|
||||
*/
|
||||
public ClientLocal createLocalAttribute ()
|
||||
{
|
||||
return new ClientLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a permission policy for use by our client.
|
||||
*/
|
||||
public PermissionPolicy createPermissionPolicy ()
|
||||
{
|
||||
return new PermissionPolicy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once our client object is registered with the distributed object system.
|
||||
*/
|
||||
public void objectAvailable (ClientObject object)
|
||||
{
|
||||
// we've got our object, so shunt ourselves over to the invoker thread to perform database
|
||||
// loading
|
||||
_clobj = object;
|
||||
_invoker.postUnit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invoke ()
|
||||
{
|
||||
try {
|
||||
// allow our derived class to do its database loads
|
||||
resolveClientData(_clobj);
|
||||
} catch (Exception cause) {
|
||||
// keep this around until we're back on the dobj thread
|
||||
_failure = cause;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult ()
|
||||
{
|
||||
// if we haven't failed, finish resolution on the dobj thread
|
||||
if (_failure == null) {
|
||||
try {
|
||||
finishResolution(_clobj);
|
||||
} catch (Exception e) {
|
||||
_failure = e;
|
||||
}
|
||||
}
|
||||
|
||||
// if we still haven't failed, then we're good to go
|
||||
if (_failure == null) {
|
||||
// and let the listeners in on the secret as well
|
||||
for (int ii = 0, ll = _listeners.size(); ii < ll; ii++) {
|
||||
ClientResolutionListener crl = _listeners.get(ii);
|
||||
try {
|
||||
// add a reference for each listener
|
||||
_clobj.reference();
|
||||
crl.clientResolved(_username, _clobj);
|
||||
} catch (Exception e) {
|
||||
log.warning("Client resolution listener choked in clientResolved() " + crl, e);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// destroy the dangling user object
|
||||
_omgr.destroyObject(_clobj.getOid());
|
||||
|
||||
// let our listener know that we're hosed
|
||||
reportFailure(_failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "ClientResolver:" + _username;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on the invoker thread which means that it can do things like blocking
|
||||
* database requests and generally whatever is necessary to load up all the client data that is
|
||||
* desired by the implentation system. Any exceptions that are thrown will be caught and
|
||||
* reported as a failure to the client resolution listener.
|
||||
*/
|
||||
protected void resolveClientData (ClientObject clobj)
|
||||
throws Exception
|
||||
{
|
||||
// fill in the username
|
||||
clobj.username = _username;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on the dobj thread after resolveClientData returns normally, it should
|
||||
* finish populating the client object with any data that is NOT loaded from a database.
|
||||
*/
|
||||
protected void finishResolution (ClientObject clobj)
|
||||
{
|
||||
// nothing to do by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports failure to our resolution listeners.
|
||||
*/
|
||||
protected void reportFailure (Exception cause)
|
||||
{
|
||||
for (int ii = 0, ll = _listeners.size(); ii < ll; ii++) {
|
||||
ClientResolutionListener crl = _listeners.get(ii);
|
||||
try {
|
||||
crl.resolutionFailed(_username, cause);
|
||||
} catch (Exception e) {
|
||||
log.warning("Client resolution listener choked in resolutionFailed()", "crl", crl,
|
||||
"username", _username, "cause", cause, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the client being resolved is no longer connected.
|
||||
*/
|
||||
protected void enforceConnected ()
|
||||
throws ClientDisconnectedException
|
||||
{
|
||||
if (_clmgr.getClient(_username) == null) {
|
||||
throw new ClientDisconnectedException();
|
||||
}
|
||||
}
|
||||
|
||||
/** The name of the user whose client object is being resolved. */
|
||||
protected Name _username;
|
||||
|
||||
/** The entities to notify of success or failure. */
|
||||
protected List<ClientResolutionListener> _listeners = Lists.newArrayList();
|
||||
|
||||
/** The resolving client object. */
|
||||
protected ClientObject _clobj;
|
||||
|
||||
/** A place to keep an exception around for a moment. */
|
||||
protected Exception _failure;
|
||||
|
||||
// dependencies
|
||||
protected @Inject @MainInvoker Invoker _invoker;
|
||||
protected @Inject RootDObjectManager _omgr;
|
||||
protected @Inject ClientManager _clmgr;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.Credentials;
|
||||
import com.threerings.presents.net.UsernamePasswordCreds;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* A simple authenticator implementation that simply accepts all authentication requests.
|
||||
*/
|
||||
public class DummyAuthenticator extends Authenticator
|
||||
{
|
||||
@Override
|
||||
protected void processAuthentication (AuthingConnection conn, AuthResponse rsp)
|
||||
throws PersistenceException
|
||||
{
|
||||
log.info("Accepting request: " + conn.getAuthRequest());
|
||||
|
||||
// we need to provide some sort of authentication username
|
||||
Credentials creds = conn.getAuthRequest().getCredentials();
|
||||
if (creds instanceof UsernamePasswordCreds) {
|
||||
conn.setAuthName(((UsernamePasswordCreds)creds).getUsername());
|
||||
} else {
|
||||
conn.setAuthName(new Name(conn.getInetAddress().getHostAddress()));
|
||||
}
|
||||
|
||||
rsp.getData().code = AuthResponseData.SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Provides the base class via which invocation service requests are dispatched.
|
||||
*/
|
||||
public abstract class InvocationDispatcher<T extends InvocationMarshaller>
|
||||
{
|
||||
/** The invocation provider for whom we're dispatching. */
|
||||
public InvocationProvider provider;
|
||||
|
||||
/**
|
||||
* Creates an instance of the appropriate {@link InvocationMarshaller} derived class for use
|
||||
* with this dispatcher.
|
||||
*/
|
||||
public abstract T createMarshaller ();
|
||||
|
||||
/**
|
||||
* Dispatches the specified method to our provider.
|
||||
*/
|
||||
public void dispatchRequest (ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
log.warning("Requested to dispatch unknown method", "provider", provider,
|
||||
"sourceOid", source.getOid(), "methodId", methodId, "args", args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.Permission;
|
||||
|
||||
/**
|
||||
* Used to report failures when executing service requests.
|
||||
*/
|
||||
public class InvocationException extends Exception
|
||||
{
|
||||
/**
|
||||
* Requires that the specified client have the specified permissions.
|
||||
*
|
||||
* @throws InvocationException if they do not.
|
||||
*/
|
||||
public static void requireAccess (ClientObject clobj, Permission perm, Object context)
|
||||
throws InvocationException
|
||||
{
|
||||
String errmsg = clobj.checkAccess(perm, context);
|
||||
if (errmsg != null) {
|
||||
throw new InvocationException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of {@link #requireAccess(ClientObject,Permission,Object)} that takes no context.
|
||||
*/
|
||||
public static void requireAccess (ClientObject clobj, Permission perm)
|
||||
throws InvocationException
|
||||
{
|
||||
requireAccess(clobj, perm, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an invocation exception with the supplied cause code string.
|
||||
*/
|
||||
public InvocationException (String cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an invocation exception with the supplied cause code
|
||||
* string and qualifying message bundle. The error code will be
|
||||
* qualified with the message bundle (see {@link MessageBundle#qualify}).
|
||||
*/
|
||||
public InvocationException (String bundle, String code)
|
||||
{
|
||||
this(MessageBundle.qualify(bundle, code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.IntMap;
|
||||
import com.samskivert.util.IntMaps;
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.data.InvocationMarshaller.ListenerMarshaller;
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.EventListener;
|
||||
import com.threerings.presents.dobj.InvocationRequestEvent;
|
||||
import com.threerings.presents.net.Transport;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The invocation services provide client to server invocations (service requests) and server to
|
||||
* client invocations (responses and notifications). Via this mechanism, the client can make
|
||||
* requests of the server, be notified of its response and the server can asynchronously invoke
|
||||
* code on the client.
|
||||
*
|
||||
* <p> Invocations are like remote procedure calls in that they are named and take arguments. All
|
||||
* arguments must be {@link Streamable} objects, primitive types, or String objects. All arguments
|
||||
* are passed by value (by serializing and unserializing the arguments); there is no special
|
||||
* facility provided for referencing non-local objects (it is assumed that the distributed object
|
||||
* facility will already be in use for any objects that should be shared).
|
||||
*
|
||||
* <p> The server invocation manager listens for invocation requests from the client and passes
|
||||
* them on to the invocation provider registered for the requested invocation module. It also
|
||||
* provides a mechanism by which responses and asynchronous notification invocations can be
|
||||
* delivered to the client.
|
||||
*/
|
||||
@Singleton
|
||||
public class InvocationManager
|
||||
implements EventListener
|
||||
{
|
||||
/**
|
||||
* Constructs an invocation manager which will use the supplied distributed object manager to
|
||||
* operate its invocation services. Generally only one invocation manager should be operational
|
||||
* in a particular system.
|
||||
*/
|
||||
@Inject public InvocationManager (PresentsDObjectMgr omgr)
|
||||
{
|
||||
_omgr = omgr;
|
||||
_omgr._invmgr = this;
|
||||
|
||||
// create the object on which we'll listen for invocation requests
|
||||
DObject invobj = _omgr.registerObject(new DObject());
|
||||
invobj.addListener(this);
|
||||
_invoid = invobj.getOid();
|
||||
|
||||
log.debug("Created invocation service object", "oid", _invoid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object id of the invocation services object.
|
||||
*/
|
||||
public int getOid ()
|
||||
{
|
||||
return _invoid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the supplied invocation dispatcher, returning a marshaller that can be used to
|
||||
* send requests to the provider for whom the dispatcher is proxying.
|
||||
*
|
||||
* @param dispatcher the dispatcher to be registered.
|
||||
*/
|
||||
public <T extends InvocationMarshaller> T registerDispatcher (
|
||||
InvocationDispatcher<T> dispatcher)
|
||||
{
|
||||
return registerDispatcher(dispatcher, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Deprecated use {@link #registerDispatcher(InvocationDispatcher)}.
|
||||
*/
|
||||
public <T extends InvocationMarshaller> T registerDispatcher (
|
||||
InvocationDispatcher<T> dispatcher, boolean bootstrap)
|
||||
{
|
||||
return registerDispatcher(dispatcher, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the supplied invocation dispatcher, returning a marshaller that can be used to
|
||||
* send requests to the provider for whom the dispatcher is proxying.
|
||||
*
|
||||
* @param dispatcher the dispatcher to be registered.
|
||||
* @param group the bootstrap group in which this marshaller is to be registered, or null if it
|
||||
* is not a bootstrap service. <em>Do not:</em> register a dispatcher with multiple boot
|
||||
* groups. You must collect shared dispatchers into as fine grained a set of groups as
|
||||
* necessary and have different types of clients specify the list of groups they need.
|
||||
*/
|
||||
public <T extends InvocationMarshaller> T registerDispatcher (
|
||||
InvocationDispatcher<T> dispatcher, String group)
|
||||
{
|
||||
_omgr.requireEventThread(); // sanity check
|
||||
|
||||
// get the next invocation code
|
||||
int invCode = nextInvCode();
|
||||
|
||||
// create the marshaller and initialize it
|
||||
T marsh = dispatcher.createMarshaller();
|
||||
marsh.init(_invoid, invCode);
|
||||
|
||||
// register the dispatcher
|
||||
_dispatchers.put(invCode, dispatcher);
|
||||
|
||||
// if it's a bootstrap service, slap it in the list
|
||||
if (group != null) {
|
||||
_bootlists.put(group, marsh);
|
||||
}
|
||||
|
||||
_recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName());
|
||||
|
||||
log.debug("Registered service", "code", invCode, "marsh", marsh);
|
||||
return marsh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out a dispatcher registration. This should be called to free up resources when an
|
||||
* invocation service is no longer going to be used.
|
||||
*/
|
||||
public void clearDispatcher (InvocationMarshaller marsh)
|
||||
{
|
||||
_omgr.requireEventThread(); // sanity check
|
||||
|
||||
if (marsh == null) {
|
||||
log.warning("Refusing to unregister null marshaller.", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dispatchers.remove(marsh.getInvocationCode()) == null) {
|
||||
log.warning("Requested to remove unregistered marshaller?", "marsh", marsh,
|
||||
new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a list of all bootstrap services registered in any of the supplied groups.
|
||||
*/
|
||||
public List<InvocationMarshaller> getBootstrapServices (String[] bootGroups)
|
||||
{
|
||||
List<InvocationMarshaller> services = Lists.newArrayList();
|
||||
for (String group : bootGroups) {
|
||||
services.addAll(_bootlists.get(group));
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class that is being used to dispatch the specified invocation code, for
|
||||
* informational purposes.
|
||||
*
|
||||
* @return the Class, or null if no dispatcher is registered with
|
||||
* the specified code.
|
||||
*/
|
||||
public Class<?> getDispatcherClass (int invCode)
|
||||
{
|
||||
Object dispatcher = _dispatchers.get(invCode);
|
||||
return (dispatcher == null) ? null : dispatcher.getClass();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void eventReceived (DEvent event)
|
||||
{
|
||||
log.debug("Event received", "event", event);
|
||||
|
||||
if (event instanceof InvocationRequestEvent) {
|
||||
InvocationRequestEvent ire = (InvocationRequestEvent)event;
|
||||
dispatchRequest(ire.getSourceOid(), ire.getInvCode(),
|
||||
ire.getMethodId(), ire.getArgs(), ire.getTransport());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we receive an invocation request message. Dispatches the request to the
|
||||
* appropriate invocation provider via the registered invocation dispatcher.
|
||||
*/
|
||||
protected void dispatchRequest (
|
||||
int clientOid, int invCode, int methodId, Object[] args, Transport transport)
|
||||
{
|
||||
// make sure the client is still around
|
||||
ClientObject source = (ClientObject)_omgr.getObject(clientOid);
|
||||
if (source == null) {
|
||||
log.info("Client no longer around for invocation request", "clientOid", clientOid,
|
||||
"code", invCode, "methId", methodId, "args", args);
|
||||
return;
|
||||
}
|
||||
|
||||
// look up the dispatcher
|
||||
InvocationDispatcher<?> disp = _dispatchers.get(invCode);
|
||||
if (disp == null) {
|
||||
log.info("Received invocation request but dispatcher registration was already cleared",
|
||||
"code", invCode, "methId", methodId, "args", args,
|
||||
"marsh", _recentRegServices.get(Integer.valueOf(invCode)));
|
||||
return;
|
||||
}
|
||||
|
||||
// scan the args, initializing any listeners and keeping track of the "primary" listener
|
||||
ListenerMarshaller rlist = null;
|
||||
int acount = args.length;
|
||||
for (int ii = 0; ii < acount; ii++) {
|
||||
Object arg = args[ii];
|
||||
if (arg instanceof ListenerMarshaller) {
|
||||
ListenerMarshaller list = (ListenerMarshaller)arg;
|
||||
list.callerOid = clientOid;
|
||||
list.omgr = _omgr;
|
||||
list.transport = transport;
|
||||
// keep track of the listener we'll inform if anything
|
||||
// goes horribly awry
|
||||
if (rlist == null) {
|
||||
rlist = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Dispatching invreq", "caller", source.who(), "disp", disp,
|
||||
"methId", methodId, "args", args);
|
||||
|
||||
// dispatch the request
|
||||
try {
|
||||
if (rlist != null) {
|
||||
rlist.setInvocationId(StringUtil.shortClassName(disp) +
|
||||
", methodId=" + methodId);
|
||||
}
|
||||
disp.dispatchRequest(source, methodId, args);
|
||||
|
||||
} catch (InvocationException ie) {
|
||||
if (rlist != null) {
|
||||
rlist.requestFailed(ie.getMessage());
|
||||
|
||||
} else {
|
||||
log.warning("Service request failed but we've got no listener to inform of " +
|
||||
"the failure", "caller", source.who(), "code", invCode,
|
||||
"dispatcher", disp, "methodId", methodId, "args", args, "error", ie);
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.warning("Dispatcher choked", "disp", disp, "caller", source.who(),
|
||||
"methId", methodId, "args", args, t);
|
||||
|
||||
// avoid logging an error when the listener notices that it's been ignored.
|
||||
if (rlist != null) {
|
||||
rlist.setNoResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate monotonically increasing provider ids.
|
||||
*/
|
||||
protected synchronized int nextInvCode ()
|
||||
{
|
||||
return _invCode++;
|
||||
}
|
||||
|
||||
/** The object id of the object on which we receive invocation service requests. */
|
||||
protected int _invoid = -1;
|
||||
|
||||
/** Used to generate monotonically increasing provider ids. */
|
||||
protected int _invCode;
|
||||
|
||||
/** The distributed object manager we're working with. */
|
||||
protected PresentsDObjectMgr _omgr;
|
||||
|
||||
/** A table of invocation dispatchers each mapped by a unique code. */
|
||||
protected IntMap<InvocationDispatcher<?>> _dispatchers = IntMaps.newHashIntMap();
|
||||
|
||||
/** Maps bootstrap group to lists of services to be provided to clients at boot time. */
|
||||
protected Multimap<String, InvocationMarshaller> _bootlists = ArrayListMultimap.create();
|
||||
|
||||
/** Tracks recently registered services so that we can complain informatively if a request
|
||||
* comes in on a service we don't know about. */
|
||||
protected final Map<Integer, String> _recentRegServices =
|
||||
new LRUHashMap<Integer, String>(10000);
|
||||
|
||||
/** The text appended to the procedure name when generating a failure response. */
|
||||
protected static final String FAILED_SUFFIX = "Failed";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
/**
|
||||
* All invocation providers must implement this placeholder interface.
|
||||
*/
|
||||
public interface InvocationProvider
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.presents.client.InvocationReceiver.Registration;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.InvocationNotificationEvent;
|
||||
import com.threerings.presents.net.Transport;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Provides basic functionality used by all invocation sender classes.
|
||||
*/
|
||||
public abstract class InvocationSender
|
||||
{
|
||||
/**
|
||||
* Requests that the specified invocation notification be packaged up and sent to the supplied
|
||||
* target client.
|
||||
*/
|
||||
public static void sendNotification (
|
||||
ClientObject target, String receiverCode, int methodId, Object[] args)
|
||||
{
|
||||
sendNotification(target, receiverCode, methodId, args, Transport.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the specified invocation notification be packaged up and sent to the supplied
|
||||
* target client.
|
||||
*/
|
||||
public static void sendNotification (
|
||||
ClientObject target, String receiverCode, int methodId, Object[] args, Transport transport)
|
||||
{
|
||||
// convert the receiver hash id into the code used on this
|
||||
// specific client
|
||||
Registration rreg = target.receivers.get(receiverCode);
|
||||
if (rreg == null) {
|
||||
log.warning("Unable to locate receiver for invocation service notification",
|
||||
"clobj", target.who(), "code", receiverCode, "methId", methodId,
|
||||
"args", args, new Exception());
|
||||
|
||||
} else {
|
||||
// log.info("Sending notification", "target", target, "code", receiverCode,
|
||||
// "methodId", methodId, "args", args);
|
||||
|
||||
// create and dispatch an invocation notification event
|
||||
target.postEvent(
|
||||
new InvocationNotificationEvent(
|
||||
target.getOid(), rreg.receiverId, methodId, args, transport));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
/**
|
||||
* A special version of the distributed object manager, modified to operate on the AWT thread so
|
||||
* that it can run in a client with a GUI and provide a "light" server for local operation of a
|
||||
* normally distributed application.
|
||||
*/
|
||||
@Singleton
|
||||
public class LocalDObjectMgr extends PresentsDObjectMgr
|
||||
{
|
||||
/**
|
||||
* Creates the dobjmgr and prepares it for operation.
|
||||
*/
|
||||
@Inject public LocalDObjectMgr (ReportManager repmgr)
|
||||
{
|
||||
super(repmgr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DObjectManager} that posts directly to this local object manager, but first
|
||||
* sets the source oid of all events to properly identify them with the supplied client oid.
|
||||
* Normally this oid setting happens when an event is received on the server over the network,
|
||||
* but in local mode we have to do it by hand.
|
||||
*/
|
||||
public DObjectManager getClientDObjectMgr (final int clientOid)
|
||||
{
|
||||
return new DObjectManager() {
|
||||
public boolean isManager (DObject object) {
|
||||
return LocalDObjectMgr.this.isManager(object);
|
||||
}
|
||||
public <T extends DObject> void subscribeToObject (int oid, Subscriber<T> target) {
|
||||
LocalDObjectMgr.this.subscribeToObject(oid, target);
|
||||
}
|
||||
public <T extends DObject> void unsubscribeFromObject (int oid, Subscriber<T> target) {
|
||||
LocalDObjectMgr.this.unsubscribeFromObject(oid, target);
|
||||
}
|
||||
public void postEvent (DEvent event) {
|
||||
event.setSourceOid(clientOid);
|
||||
LocalDObjectMgr.this.postEvent(event);
|
||||
}
|
||||
public void removedLastSubscriber (DObject obj, boolean deathWish) {
|
||||
LocalDObjectMgr.this.removedLastSubscriber(obj, deathWish);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isDispatchThread ()
|
||||
{
|
||||
return EventQueue.isDispatchThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postEvent (final DEvent event)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
processUnit(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRunnable (Runnable unit)
|
||||
{
|
||||
// we just pass this right on to the AWT event queue rather than running them through
|
||||
// processUnit() which would basically just call run() though we lose a tiny bit of
|
||||
// inconsequential accounting data
|
||||
EventQueue.invokeLater(unit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.util.signal.SignalManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Handles signals using Narya's native libsignal.
|
||||
*/
|
||||
public class NativeSignalHandler extends AbstractSignalHandler
|
||||
implements SignalManager.SignalHandler
|
||||
{
|
||||
// from interface SignalManager.SignalHandler
|
||||
public boolean signalReceived (int signo)
|
||||
{
|
||||
switch (signo) {
|
||||
case SignalManager.SIGTERM:
|
||||
termReceived();
|
||||
break;
|
||||
case SignalManager.SIGINT:
|
||||
intReceived();
|
||||
break;
|
||||
case SignalManager.SIGHUP:
|
||||
hupReceived();
|
||||
break;
|
||||
case SignalManager.SIGUSR2:
|
||||
usr2Received();
|
||||
break;
|
||||
default:
|
||||
log.warning("Received unknown signal", "signo", signo);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean registerHandlers ()
|
||||
{
|
||||
if (!SignalManager.servicesAvailable()) {
|
||||
return false;
|
||||
}
|
||||
SignalManager.registerSignalHandler(SignalManager.SIGTERM, this);
|
||||
SignalManager.registerSignalHandler(SignalManager.SIGINT, this);
|
||||
SignalManager.registerSignalHandler(SignalManager.SIGHUP, this);
|
||||
SignalManager.registerSignalHandler(SignalManager.SIGUSR2, this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
/**
|
||||
* A separate invoker thread on which we perform client authentication. This allows the normal
|
||||
* server operation to proceed even in the event that our authentication services have gone down
|
||||
* and attempts to authenticate cause long timeouts and blockage.
|
||||
*/
|
||||
@Singleton
|
||||
public class PresentsAuthInvoker extends ReportingInvoker
|
||||
{
|
||||
@Inject public PresentsAuthInvoker (PresentsDObjectMgr omgr, ReportManager repmgr)
|
||||
{
|
||||
super("presents.AuthInvoker", omgr, repmgr);
|
||||
setDaemon(true);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,321 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
import com.threerings.presents.server.PresentsDObjectMgr.LongRunnable;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Extends the generic {@link Invoker} and integrates it a bit more into the Presents system.
|
||||
*/
|
||||
@Singleton
|
||||
public class PresentsInvoker extends ReportingInvoker
|
||||
implements Lifecycle.ShutdownComponent
|
||||
{
|
||||
@Inject public PresentsInvoker (PresentsDObjectMgr omgr, Lifecycle cycle, ReportManager repmgr)
|
||||
{
|
||||
super("presents.Invoker", omgr, repmgr);
|
||||
cycle.addComponent(this);
|
||||
_omgr = omgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an invoker that may post to the PresentsInvoker and the DObjectManager and may be
|
||||
* posted to by the PresentsInvoker and DObjectManager. This invoker will be taken into
|
||||
* consideration by {@link #postRunnableWhenEmpty(Runnable)} and in waiting for invokers to
|
||||
* empty out on shutdown.
|
||||
*/
|
||||
public void addInterdependentInvoker (Invoker invoker)
|
||||
{
|
||||
_interdependentInvokers.add(invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts the given runnable to this invoker when it, the DObjectManager and any interdependent
|
||||
* invokers are all empty.
|
||||
*/
|
||||
public void postRunnableWhenEmpty (Runnable onEmpty)
|
||||
{
|
||||
postUnit(new EmptyingUnit(onEmpty));
|
||||
}
|
||||
|
||||
@Override // from Invoker, Lifecycle.ShutdownComponent
|
||||
public void shutdown ()
|
||||
{
|
||||
// this will do a sophisticated shutdown of both ourself and the dobjmgr; note: we
|
||||
// specifically avoid setting _shutdownRequested as it's OK for units to be posted to the
|
||||
// PresentsInvoker during the shutdown phase, we just delay shutdown until we're able to
|
||||
// make it to the shutdown unit without queueing up more units for processing
|
||||
postRunnableWhenEmpty(new Runnable() {
|
||||
public void run () {
|
||||
_omgr.harshShutdown(); // end the dobj thread
|
||||
|
||||
// Now that things have emptied out, set _shutdownRequested and commit suicide
|
||||
PresentsInvoker.super.shutdown();
|
||||
}});
|
||||
}
|
||||
|
||||
@Override // from Invoker
|
||||
protected void didShutdown ()
|
||||
{
|
||||
_server.invokerDidShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets posted to this invoker over and over again until it, any interdependent invokers
|
||||
* and the DObjectManager are all empty.
|
||||
*/
|
||||
protected class EmptyingUnit extends Unit {
|
||||
public EmptyingUnit (Runnable onEmpty) {
|
||||
_onEmpty = onEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invoke ()
|
||||
{
|
||||
if (_loopCount > MAX_LOOPS) {
|
||||
log.warning("Emptying waiter looped on invoker 10000 times without finishing, "
|
||||
+ "running onEmpty while items remain in the queue.");
|
||||
_onEmpty.run();
|
||||
return false;
|
||||
|
||||
// if the invoker queue is not empty, we put ourselves back on it
|
||||
} else if (getPendingUnits() > 0) {
|
||||
_loopCount++;
|
||||
postUnit(this);
|
||||
return false;
|
||||
|
||||
} else if (++_passCount >= MAX_PASSES) {
|
||||
log.warning("Emptying waiter passed 50 times without finishing, running onEmpty "
|
||||
+ "while items remain in queue.");
|
||||
_onEmpty.run();
|
||||
return false;
|
||||
|
||||
} else {
|
||||
_loopCount = 0;
|
||||
// The invoker is empty and running this. Check if everything else is empty.
|
||||
List<BlockingUnit> checkers =
|
||||
Lists.newArrayListWithCapacity(_interdependentInvokers.size() + 1);
|
||||
for (Invoker invoker : _interdependentInvokers) {
|
||||
checkers.add(new BlockingUnit(invoker));
|
||||
}
|
||||
checkers.add(new BlockingUnit());
|
||||
long checkStart = System.currentTimeMillis();
|
||||
while (true) {
|
||||
synchronized (_checkMonitor) {
|
||||
boolean unchecked = false;
|
||||
for (BlockingUnit checker : checkers) {
|
||||
if (!checker.run) {
|
||||
unchecked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (unchecked) {
|
||||
long timeChecking = System.currentTimeMillis() - checkStart;
|
||||
if (timeChecking >= CHECK_TIMEOUT) {
|
||||
log.warning("Waited 5 minutes for all the blocking units to "
|
||||
+ "no avail. Running onEmpty while items may remain in "
|
||||
+ "the queue.");
|
||||
releaseCheckers(checkers);
|
||||
_onEmpty.run();
|
||||
return false;
|
||||
}
|
||||
// At least one checker hasn't started running yet. We need to wait
|
||||
// till they're all in place before looking at emptiness.
|
||||
try {
|
||||
_checkMonitor.wait(CHECK_TIMEOUT - timeChecking);
|
||||
} catch (InterruptedException e) {
|
||||
// Not a problem, we'll just check on the checkers again
|
||||
}
|
||||
|
||||
} else {
|
||||
// All the checkers are blocking their respective threads. That means
|
||||
// if their queues are empty, nothing else is running.
|
||||
for (BlockingUnit checker : checkers) {
|
||||
if (!checker.isEmpty()) {
|
||||
// Balls, we found an invoker with items in its queue. Let
|
||||
// everybody get back to work.
|
||||
releaseCheckers(checkers);
|
||||
// Post on the busy unit, so when it clears we'll post back
|
||||
// here
|
||||
checker.post(new Runnable() {
|
||||
public void run () {
|
||||
PresentsInvoker.this.postUnit(EmptyingUnit.this);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// All of the checkers are clean, so they can go back to their business
|
||||
releaseCheckers(checkers);
|
||||
if (getPendingUnits() > 0) {
|
||||
// While we were waiting on the various invokers, one of them gave
|
||||
// us something to do. We need to process that and try again.
|
||||
postUnit(this);
|
||||
} else {
|
||||
// Everything is gloriously clean. Shut down this invoker and the
|
||||
// DObjectManager
|
||||
_onEmpty.run();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongThreshold ()
|
||||
{
|
||||
return 60 * 1000;
|
||||
}
|
||||
|
||||
protected void releaseCheckers (List<BlockingUnit> checkers)
|
||||
{
|
||||
for (BlockingUnit checker : checkers) {
|
||||
synchronized (checker) {
|
||||
checker.released = true;
|
||||
checker.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The runnable to execute when all associated queues are empty or we've given up. */
|
||||
protected Runnable _onEmpty;
|
||||
|
||||
/** The number of times we've been passed to the object manager. */
|
||||
protected int _passCount;
|
||||
|
||||
/** How many times we've looped on the thread we're currently on. */
|
||||
protected int _loopCount;
|
||||
|
||||
/** The maximum number of passes we allow before just ending things. */
|
||||
protected static final int MAX_PASSES = 50;
|
||||
|
||||
/** The maximum number of loops we allow before just ending things. */
|
||||
protected static final int MAX_LOOPS = 10000;
|
||||
|
||||
protected static final long CHECK_TIMEOUT = 5 * 60 * 1000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in an Invoker or the DObjectManager and blocks it until released by EmptyingUnit.
|
||||
*/
|
||||
protected class BlockingUnit extends Unit implements LongRunnable {
|
||||
/**
|
||||
* If the run method of this checker has been entered. If this is true while released is
|
||||
* false, this checker is blocking its thread.
|
||||
*/
|
||||
public boolean run;
|
||||
|
||||
/** If this checker no longer needs to block its thread. */
|
||||
public boolean released;
|
||||
|
||||
public BlockingUnit ()
|
||||
{
|
||||
_omgr.postRunnable(this);
|
||||
}
|
||||
|
||||
public BlockingUnit (Invoker invoker)
|
||||
{
|
||||
_invoker = invoker;
|
||||
_invoker.postUnit(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty ()
|
||||
{
|
||||
return _invoker == null ? _omgr.queueIsEmpty() : (_invoker.getPendingUnits() == 0);
|
||||
}
|
||||
|
||||
public void post (Runnable runnable)
|
||||
{
|
||||
if (_invoker != null) {
|
||||
_invoker.postRunnable(runnable);
|
||||
} else {
|
||||
_omgr.postRunnable(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Override Invoker.Unit's run altogether to allow this to be posted as a Runnable to
|
||||
// the DObjectMgr as well.
|
||||
run = true;
|
||||
synchronized (_checkMonitor) {
|
||||
_checkMonitor.notify();
|
||||
}
|
||||
synchronized (this) {
|
||||
while (!released) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Not a problem, we'll check our invariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invoke ()
|
||||
{
|
||||
run();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongThreshold ()
|
||||
{
|
||||
// Don't bitch about being a long invoker unless this has been blocking for longer than
|
||||
// the EmptyingUnit timeout
|
||||
return EmptyingUnit.CHECK_TIMEOUT;
|
||||
}
|
||||
|
||||
protected Invoker _invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes between EmptyingUnit and its BlockingUnits when checking that all
|
||||
* interdependent invokers are blocked.
|
||||
*/
|
||||
protected Object _checkMonitor = new Object();
|
||||
|
||||
/** Invokers that may post to Presents and may be posted to by Presents. */
|
||||
protected List<Invoker> _interdependentInvokers = Lists.newArrayList();
|
||||
|
||||
/** The distributed object manager with which we interoperate. */
|
||||
protected PresentsDObjectMgr _omgr;
|
||||
|
||||
/** The server we're working for. */
|
||||
@Inject protected PresentsServer _server;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.AccessController;
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.InvocationRequestEvent;
|
||||
import com.threerings.presents.dobj.MessageEvent;
|
||||
import com.threerings.presents.dobj.NamedEvent;
|
||||
import com.threerings.presents.dobj.ProxySubscriber;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Defines the various object access controllers used by the Presents server.
|
||||
*/
|
||||
public class PresentsObjectAccess
|
||||
{
|
||||
/**
|
||||
* Our default access controller. Disallows modification of any object but allows anyone to
|
||||
* subscribe.
|
||||
*/
|
||||
public static AccessController DEFAULT = new AccessController()
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public boolean allowSubscribe (DObject object, Subscriber<?> subscriber)
|
||||
{
|
||||
// allow anyone to subscribe
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean allowDispatch (DObject object, DEvent event)
|
||||
{
|
||||
// if the event came from the server, it's cool
|
||||
if (event.getSourceOid() == -1) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// if it came from the client, it better be a non-modification event
|
||||
return (event instanceof MessageEvent || event instanceof InvocationRequestEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides access control for client objects.
|
||||
*/
|
||||
public static AccessController CLIENT = new AccessController()
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public boolean allowSubscribe (DObject object, Subscriber<?> sub)
|
||||
{
|
||||
// if the subscriber is a client, ensure that they are this same user
|
||||
if (sub instanceof ProxySubscriber) {
|
||||
ClientObject clobj = ((ProxySubscriber)sub).getClientObject();
|
||||
if (clobj != object) {
|
||||
log.warning("Refusing ClientObject subscription request",
|
||||
"obj", ((ClientObject)object).who(), "sub", clobj.who());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean allowDispatch (DObject object, DEvent event)
|
||||
{
|
||||
// if the event came from the server, it's cool
|
||||
if (event.getSourceOid() == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// the client is only allowed to modify the RECEIVERS field
|
||||
return ((event instanceof NamedEvent) &&
|
||||
((NamedEvent)event).getName().equals(ClientObject.RECEIVERS));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
import com.samskivert.util.RunQueue;
|
||||
import com.samskivert.util.SystemInfo;
|
||||
|
||||
import com.threerings.presents.annotation.AuthInvoker;
|
||||
import com.threerings.presents.annotation.EventQueue;
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.dobj.AccessController;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr.LongRunnable;
|
||||
import com.threerings.presents.server.net.BindingConnectionManager;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
|
||||
import com.threerings.crowd.server.PlaceManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The presents server provides a central point of access to the various facilities that make up
|
||||
* the presents framework. To facilitate extension and customization, a single instance of the
|
||||
* presents server should be created and initialized in a process. To facilitate easy access to the
|
||||
* services provided by the presents server, static references to the various managers are made
|
||||
* available in the <code>PresentsServer</code> class. These will be configured when the singleton
|
||||
* instance is initialized.
|
||||
*/
|
||||
@Singleton
|
||||
public class PresentsServer
|
||||
{
|
||||
/** Configures dependencies needed by the Presents services. */
|
||||
public static class Module extends AbstractModule
|
||||
{
|
||||
@Override protected void configure () {
|
||||
bindInvokers();
|
||||
bindConnectionManager();
|
||||
bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class);
|
||||
bind(DObjectManager.class).to(PresentsDObjectMgr.class);
|
||||
bind(RootDObjectManager.class).to(PresentsDObjectMgr.class);
|
||||
bind(Lifecycle.class).toInstance(new Lifecycle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds just the connection manager so this can be overridden if desired.
|
||||
*/
|
||||
protected void bindConnectionManager()
|
||||
{
|
||||
bind(ConnectionManager.class).to(BindingConnectionManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds just the invokers so this can be overridden if desired.
|
||||
*/
|
||||
protected void bindInvokers() {
|
||||
bind(Invoker.class).annotatedWith(MainInvoker.class).to(PresentsInvoker.class);
|
||||
bind(Invoker.class).annotatedWith(AuthInvoker.class).to(PresentsAuthInvoker.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default entry point for the server.
|
||||
*/
|
||||
public static void main (String[] args)
|
||||
{
|
||||
Injector injector = Guice.createInjector(new Module());
|
||||
PresentsServer server = injector.getInstance(PresentsServer.class);
|
||||
try {
|
||||
// initialize the server
|
||||
server.init(injector);
|
||||
|
||||
// check to see if we should load and invoke a test module before running the server
|
||||
String testmod = System.getProperty("test_module");
|
||||
if (testmod != null) {
|
||||
try {
|
||||
log.info("Invoking test module", "mod", testmod);
|
||||
Class<?> tmclass = Class.forName(testmod);
|
||||
Runnable trun = (Runnable)tmclass.newInstance();
|
||||
trun.run();
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to invoke test module '" + testmod + "'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// start the server to running (this method call won't return until the server is shut
|
||||
// down)
|
||||
server.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to initialize server.", e);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Legacy static reference to the main distributed object manager. Don't use this. If you're
|
||||
* writing a game, use {@link PlaceManager#_omgr}. */
|
||||
@Deprecated public static PresentsDObjectMgr omgr;
|
||||
|
||||
/** Legacy static reference to the invocation manager. Don't use this. If you're
|
||||
* writing a game, use {@link PlaceManager#_invmgr}. */
|
||||
@Deprecated public static InvocationManager invmgr;
|
||||
|
||||
/**
|
||||
* Initializes all of the server services and prepares for operation.
|
||||
*/
|
||||
public void init (Injector injector)
|
||||
throws Exception
|
||||
{
|
||||
// output general system information
|
||||
SystemInfo si = new SystemInfo();
|
||||
log.info("Starting up server", "os", si.osToString(), "jvm", si.jvmToString());
|
||||
|
||||
registerSignalHandlers(injector);
|
||||
|
||||
// initialize our deprecated legacy static references
|
||||
omgr = _omgr;
|
||||
invmgr = _invmgr;
|
||||
|
||||
// configure the dobject manager with our access controller
|
||||
_omgr.setDefaultAccessController(createDefaultObjectAccessController());
|
||||
|
||||
// start the main and auth invoker threads
|
||||
_invoker.start();
|
||||
_authInvoker.start();
|
||||
((PresentsInvoker)_invoker).addInterdependentInvoker(_authInvoker);
|
||||
|
||||
// provide our client manager with the injector it needs
|
||||
_clmgr.setInjector(injector);
|
||||
|
||||
// if we have a connection manager that handles its own socket accepting, initialize it
|
||||
// with the hostname and ports on which to listen
|
||||
if (_conmgr instanceof BindingConnectionManager) {
|
||||
((BindingConnectionManager)_conmgr).init(
|
||||
getBindHostname(), getDatagramHostname(), getListenPorts(), getDatagramPorts());
|
||||
}
|
||||
|
||||
// initialize the time base services
|
||||
TimeBaseProvider.init(_invmgr, _omgr);
|
||||
|
||||
// make the client manager shut down before the invoker and dobj threads; this helps
|
||||
// application code to avoid long chains of shutdown constraints
|
||||
_lifecycle.addShutdownConstraint(
|
||||
_clmgr, Lifecycle.Constraint.RUNS_BEFORE, (PresentsInvoker)_invoker);
|
||||
|
||||
// initialize all of our registered components
|
||||
_lifecycle.init();
|
||||
}
|
||||
|
||||
protected void registerSignalHandlers (Injector injector)
|
||||
{
|
||||
// register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers
|
||||
boolean registered = false;
|
||||
try {
|
||||
registered = injector.getInstance(SunSignalHandler.class).init();
|
||||
} catch (Throwable t) {
|
||||
log.warning("Unable to register Sun signal handlers", "error", t);
|
||||
}
|
||||
if (!registered) {
|
||||
injector.getInstance(NativeSignalHandler.class).init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up all of the server services and enters the main server event loop.
|
||||
*/
|
||||
public void run ()
|
||||
{
|
||||
// wait till everything in the dobjmgr queue and invokers are processed and then start the
|
||||
// connection manager
|
||||
((PresentsInvoker)_invoker).postRunnableWhenEmpty(new Runnable() {
|
||||
public void run () {
|
||||
// Take as long as you like opening to the public
|
||||
_omgr.postRunnable(new LongRunnable() {
|
||||
public void run () {
|
||||
openToThePublic();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// invoke the dobjmgr event loop
|
||||
_omgr.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the server for connections after the event thread is running and all the invokers are
|
||||
* clear after starting up.
|
||||
*/
|
||||
protected void openToThePublic ()
|
||||
{
|
||||
_conmgr.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a request to shutdown on the event thread. This method may be safely called from
|
||||
* any thread.
|
||||
*/
|
||||
public void queueShutdown ()
|
||||
{
|
||||
_omgr.postRunnable(new PresentsDObjectMgr.LongRunnable() {
|
||||
public void run () {
|
||||
_lifecycle.shutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the default object access policy for all {@link DObject} instances. The default
|
||||
* default policy is to allow all subscribers but reject all modifications by the client.
|
||||
*/
|
||||
protected AccessController createDefaultObjectAccessController ()
|
||||
{
|
||||
return PresentsObjectAccess.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname on which the connection manager will listen for TCP traffic, or
|
||||
* <code>null</code> to bind to the wildcard address.
|
||||
*/
|
||||
protected String getBindHostname ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname on which the connection will listen for datagram traffic, or
|
||||
* <code>null</code> to bind to the wildcard address.
|
||||
*/
|
||||
protected String getDatagramHostname ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port on which the connection manager will listen for client connections.
|
||||
*/
|
||||
protected int[] getListenPorts ()
|
||||
{
|
||||
return Client.DEFAULT_SERVER_PORTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ports on which the connection manager will listen for datagrams.
|
||||
*/
|
||||
protected int[] getDatagramPorts ()
|
||||
{
|
||||
return Client.DEFAULT_DATAGRAM_PORTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once the invoker and distributed object manager have both completed processing all
|
||||
* remaining events and are fully shutdown. <em>Note:</em> this is called as the last act of
|
||||
* the invoker <em>on the invoker thread</em>. In theory no other (important) threads are
|
||||
* running, so thread safety should not be an issue, but be careful!
|
||||
*/
|
||||
protected void invokerDidShutdown ()
|
||||
{
|
||||
}
|
||||
|
||||
/** The manager of distributed objects. */
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
|
||||
/** The manager of network connections. */
|
||||
@Inject protected ConnectionManager _conmgr;
|
||||
|
||||
/** The manager of clients. */
|
||||
@Inject protected ClientManager _clmgr;
|
||||
|
||||
/** The manager of invocation services. */
|
||||
@Inject protected InvocationManager _invmgr;
|
||||
|
||||
/** Handles orderly initialization and shutdown of our managers, etc. */
|
||||
@Inject protected Lifecycle _lifecycle;
|
||||
|
||||
/** Used to invoke background tasks that should not be allowed to tie up the distributed object
|
||||
* manager thread (generally talking to databases and other relatively slow entities). */
|
||||
@Inject @MainInvoker protected Invoker _invoker;
|
||||
|
||||
/** Used to invoke authentication tasks. */
|
||||
@Inject @AuthInvoker protected Invoker _authInvoker;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import com.samskivert.util.Calendars;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.ObserverList;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Calendars.Builder;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Handles scheduling and execution of automated server reboots. Note that this service simply
|
||||
* shuts down the server and assumes it will be automatically restarted by some external entity. It
|
||||
* is generally useful to run a server from a script that automatically restarts it when it
|
||||
* terminates.
|
||||
*/
|
||||
public abstract class RebootManager
|
||||
{
|
||||
/**
|
||||
* An interface for receiving notifications about pending automated shutdowns.
|
||||
*/
|
||||
public static interface PendingShutdownObserver
|
||||
{
|
||||
/**
|
||||
* Called prior to an automated shutdown. NOTE that the shutdown is not guaranteed to
|
||||
* happen, this interface is merely for notification purposes.
|
||||
*
|
||||
* @param warningsLeft The number of warnings left prior to the shutdown. This value will
|
||||
* be 0 on the last warning, usually 1-2 minutes prior to the actual shutdown.
|
||||
* @param msLeft the approximate number of milliseconds left prior to the shutdown.
|
||||
*/
|
||||
void shutdownPlanned (int warningsLeft, long msLeft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes initialization of the manager.
|
||||
*/
|
||||
public void init ()
|
||||
{
|
||||
scheduleRegularReboot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules our next regularly scheduled reboot.
|
||||
*
|
||||
* @return true if a reboot was scheduled, false if regularly scheduled reboots are disabled.
|
||||
*/
|
||||
public boolean scheduleRegularReboot ()
|
||||
{
|
||||
// maybe schedule an automatic reboot based on our configuration
|
||||
int freq = getDayFrequency();
|
||||
if (freq == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Builder cal = Calendars.now().zeroTime().addHours(getRebootHour()).addDays(freq);
|
||||
|
||||
// maybe avoid weekends
|
||||
if (getSkipWeekends()) {
|
||||
int dow = cal.get(Calendar.DAY_OF_WEEK);
|
||||
switch (dow) {
|
||||
case Calendar.SATURDAY:
|
||||
if (freq > 1) {
|
||||
cal.addDays(-1);
|
||||
}
|
||||
break;
|
||||
|
||||
case Calendar.SUNDAY:
|
||||
if (freq > 2) {
|
||||
cal.addDays(-2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scheduleReboot(cal.toTime(), AUTOMATIC_INITIATOR);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the manager planning a shutdown in the near future?
|
||||
*/
|
||||
public boolean willShutdownSoon ()
|
||||
{
|
||||
return _rebootSoon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observer to the observer list.
|
||||
*/
|
||||
public void addObserver (PendingShutdownObserver observer)
|
||||
{
|
||||
_observers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a reboot for the specified time.
|
||||
*/
|
||||
public void scheduleReboot (long rebootTime, String initiator)
|
||||
{
|
||||
// if there's already a reboot scheduled, cancel it
|
||||
if (_interval != null) {
|
||||
_interval.cancel();
|
||||
_interval = null;
|
||||
}
|
||||
|
||||
// note our new reboot time and its initiator
|
||||
_nextReboot = rebootTime;
|
||||
_initiator = initiator;
|
||||
|
||||
// see if the reboot is happening within the time specified by the
|
||||
// longest warning; if so, issue the appropriate warning
|
||||
long now = System.currentTimeMillis();
|
||||
for (int ii = WARNINGS.length - 1; ii >= 0; ii--) {
|
||||
long warnTime = WARNINGS[ii] * 60 * 1000;
|
||||
if (now + warnTime >= _nextReboot) {
|
||||
doWarning(ii);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, it's further off; schedule an interval to wake up when we
|
||||
// should issue the first pre-reboot warning
|
||||
_rebootSoon = false;
|
||||
long firstWarnTime = (_nextReboot - (WARNINGS[0] * 60 * 1000)) - now;
|
||||
(_interval = _omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
doWarning(0);
|
||||
}
|
||||
})).schedule(firstWarnTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by an entity that would like to prevent a reboot.
|
||||
*/
|
||||
public int preventReboot (String whereFrom)
|
||||
{
|
||||
if (whereFrom == null) {
|
||||
throw new IllegalArgumentException("whereFrom must be descriptive.");
|
||||
}
|
||||
int lockId = _nextRebootLockId++;
|
||||
_rebootLocks.put(lockId, whereFrom);
|
||||
return lockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a reboot lock.
|
||||
*/
|
||||
public void allowReboot (int lockId)
|
||||
{
|
||||
if (null == _rebootLocks.remove(lockId)) {
|
||||
throw new IllegalArgumentException("no such lockId (" + lockId + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides us with our dependencies.
|
||||
*/
|
||||
protected RebootManager (PresentsServer server, RootDObjectManager omgr)
|
||||
{
|
||||
_server = server;
|
||||
_omgr = omgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a message to everyone on the server. The following messages will be broadcast:
|
||||
* <ul><li> m.rebooting_now
|
||||
* <li> m.reboot_warning (minutes) (message or m.reboot_msg_standard)
|
||||
* <li> m.reboot_delayed
|
||||
* </ul>
|
||||
*/
|
||||
protected abstract void broadcast (String message);
|
||||
|
||||
/**
|
||||
* Returns the frequency in days of our automatic reboots, or -1 to disable automatically
|
||||
* scheduled reboots.
|
||||
*/
|
||||
protected abstract int getDayFrequency ();
|
||||
|
||||
/**
|
||||
* Returns the desired hour at which to perform our reboot.
|
||||
*/
|
||||
protected abstract int getRebootHour ();
|
||||
|
||||
/**
|
||||
* Returns true if the reboot manager should avoid scheduling automated reboots on the
|
||||
* weekends.
|
||||
*/
|
||||
protected abstract boolean getSkipWeekends ();
|
||||
|
||||
/**
|
||||
* Returns a custom message to be used when broadcasting a pending reboot.
|
||||
*/
|
||||
protected abstract String getCustomRebootMessage ();
|
||||
|
||||
/**
|
||||
* Composes the given reboot message with the minutes and either the custom or standard details
|
||||
* about the pending reboot.
|
||||
*/
|
||||
protected String getRebootMessage (String key, int minutes)
|
||||
{
|
||||
String msg = getCustomRebootMessage();
|
||||
if (StringUtil.isBlank(msg)) {
|
||||
msg = "m.reboot_msg_standard";
|
||||
}
|
||||
|
||||
return MessageBundle.compose(key, MessageBundle.taint("" + minutes), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a warning, schedule the next.
|
||||
*/
|
||||
protected void doWarning (final int level)
|
||||
{
|
||||
_rebootSoon = true;
|
||||
if (level == WARNINGS.length) {
|
||||
if (checkLocks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// that's it! do the reboot
|
||||
log.info("Performing automatic server reboot/shutdown, as scheduled by: " + _initiator);
|
||||
broadcast("m.rebooting_now");
|
||||
|
||||
// wait 1 second, then do it
|
||||
new Interval() { // Note: This interval does not run on the dobj thread
|
||||
@Override public void expired () {
|
||||
_server.queueShutdown(); // this posts a LongRunnable
|
||||
}
|
||||
}.schedule(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// issue the warning
|
||||
int minutes = WARNINGS[level];
|
||||
broadcast(getRebootMessage("m.reboot_warning", minutes));
|
||||
if (level < WARNINGS.length - 1) {
|
||||
minutes -= WARNINGS[level + 1];
|
||||
}
|
||||
|
||||
// schedule the next warning
|
||||
(_interval = _omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
doWarning(level + 1);
|
||||
}
|
||||
})).schedule(minutes * 60 * 1000);
|
||||
notifyObservers(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if there are outstanding reboot locks that may delay the reboot, returning
|
||||
* false if there are none.
|
||||
*/
|
||||
protected boolean checkLocks ()
|
||||
{
|
||||
if (_rebootLocks.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("Reboot delayed due to outstanding locks", "locks", _rebootLocks.elements());
|
||||
broadcast("m.reboot_delayed");
|
||||
(_interval = _omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
doWarning(WARNINGS.length);
|
||||
}
|
||||
})).schedule(60 * 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all PendingShutdownObservers of the pending shutdown!
|
||||
*/
|
||||
protected void notifyObservers (int level)
|
||||
{
|
||||
final int warningsLeft = WARNINGS.length - level - 1;
|
||||
final long msLeft = 1000L * 60L * WARNINGS[level];
|
||||
_observers.apply(new ObserverList.ObserverOp<PendingShutdownObserver>() {
|
||||
public boolean apply (PendingShutdownObserver observer) {
|
||||
observer.shutdownPlanned(warningsLeft, msLeft);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The server that we're going to reboot. */
|
||||
protected PresentsServer _server;
|
||||
|
||||
/** Our distributed object manager. */
|
||||
protected RootDObjectManager _omgr;
|
||||
|
||||
/** The time at which our next reboot is scheduled or 0L. */
|
||||
protected long _nextReboot;
|
||||
|
||||
/** The entity that scheduled the reboot. */
|
||||
protected String _initiator;
|
||||
|
||||
/** True if the reboot is coming soon, within the earliest warning. */
|
||||
protected boolean _rebootSoon = false;
|
||||
|
||||
/** The interval scheduled to perform the next step the reboot process. */
|
||||
protected Interval _interval;
|
||||
|
||||
/** A list of PendingShutdownObservers. */
|
||||
protected ObserverList<PendingShutdownObserver> _observers = ObserverList.newFastUnsafe();
|
||||
|
||||
/** The next reboot lock id. */
|
||||
protected int _nextRebootLockId = 0;
|
||||
|
||||
/** Things that can delay the reboot. */
|
||||
protected HashIntMap<String> _rebootLocks = new HashIntMap<String>();
|
||||
|
||||
/** The minutes at which we give warnings. The last value is also the
|
||||
* minimum time at which we can possibly reboot after the value of the
|
||||
* nextReboot field is changed, to prevent accidentally causing instant
|
||||
* server reboots. */
|
||||
public static final int[] WARNINGS = { 30, 20, 15, 10, 5, 2 };
|
||||
|
||||
protected static final String AUTOMATIC_INITIATOR = "automatic";
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.threerings.util.MessageBundle;
|
||||
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* A simple server that does nothing more than spit out a canned error response to everyone who
|
||||
* logs in.
|
||||
*/
|
||||
public class Rejector extends PresentsServer
|
||||
{
|
||||
/** Configures dependencies needed by the Rejector. */
|
||||
public static class Module extends PresentsServer.Module
|
||||
{
|
||||
@Override protected void configure () {
|
||||
super.configure();
|
||||
bind(Authenticator.class).to(RejectingAuthenticator.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getListenPorts ()
|
||||
{
|
||||
return _ports;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: Rejector ports error_msg [args]");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
_ports = StringUtil.parseIntArray(args[0]);
|
||||
_errmsg = args[1];
|
||||
if (args.length > 2) {
|
||||
String[] eargs = new String[args.length-2];
|
||||
System.arraycopy(args, 2, eargs, 0, eargs.length);
|
||||
_errmsg = MessageBundle.tcompose(_errmsg, eargs);
|
||||
}
|
||||
|
||||
Injector injector = Guice.createInjector(new Module());
|
||||
Rejector server = injector.getInstance(Rejector.class);
|
||||
try {
|
||||
server.init(injector);
|
||||
server.run();
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to initialize server.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An authenticator implementation that refuses all authentication requests.
|
||||
*/
|
||||
protected class RejectingAuthenticator extends Authenticator
|
||||
{
|
||||
@Override
|
||||
protected void processAuthentication (AuthingConnection conn, AuthResponse rsp)
|
||||
throws Exception
|
||||
{
|
||||
log.info("Rejecting request: " + conn.getAuthRequest());
|
||||
throw new AuthException(_errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
protected static String _errmsg;
|
||||
protected static int[] _ports;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Handles the generation of server status reports.
|
||||
*/
|
||||
@Singleton
|
||||
public class ReportManager
|
||||
{
|
||||
/**
|
||||
* Used to generate "state of the server" reports.
|
||||
* See {@link ReportManager#registerReporter(Reporter)}.
|
||||
* */
|
||||
public static interface Reporter
|
||||
{
|
||||
/**
|
||||
* Requests that this reporter append its report to the supplied string buffer.
|
||||
*
|
||||
* @param buffer the string buffer to which the report text should be appended.
|
||||
* @param now the time at which the report generation began, in epoch millis.
|
||||
* @param sinceLast number of milliseconds since the last time we generated a report.
|
||||
* @param reset if true, all accumulating stats should be reset, if false they should be
|
||||
* allowed to continue to accumulate.
|
||||
*/
|
||||
void appendReport (StringBuilder buffer, long now, long sinceLast, boolean reset);
|
||||
}
|
||||
|
||||
/** A string constant representing the default report. */
|
||||
public static final String DEFAULT_TYPE = "";
|
||||
|
||||
/** A string constant representing a report with detailed profiling information. */
|
||||
public static final String PROFILE_TYPE = "profile";
|
||||
|
||||
/**
|
||||
* Starts up our periodic report generation task.
|
||||
*/
|
||||
public void activatePeriodicReport ()
|
||||
{
|
||||
// queue up an interval which will generate reports as long as the omgr is alive
|
||||
_omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
logReport(LOG_REPORT_HEADER +
|
||||
generateReport(DEFAULT_TYPE, System.currentTimeMillis(), true));
|
||||
}
|
||||
}).schedule(getReportInterval(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reporter for the default state of server report.
|
||||
*/
|
||||
public void registerReporter (Reporter reporter)
|
||||
{
|
||||
registerReporter(DEFAULT_TYPE, reporter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reporter for the report of the specified type.
|
||||
*/
|
||||
public void registerReporter (String type, Reporter reporter)
|
||||
{
|
||||
_reporters.put(type, reporter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a default state of server report.
|
||||
*/
|
||||
public String generateReport ()
|
||||
{
|
||||
return generateReport(DEFAULT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report for all system services registered as a {@link Reporter}.
|
||||
*/
|
||||
public String generateReport (String type)
|
||||
{
|
||||
return generateReport(type, System.currentTimeMillis(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and logs a "state of server" report.
|
||||
*/
|
||||
protected String generateReport (String type, long now, boolean reset)
|
||||
{
|
||||
long sinceLast = now - _lastReportStamp;
|
||||
long uptime = now - _serverStartTime;
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
// add standard bits to the default report
|
||||
if (DEFAULT_TYPE.equals(type)) {
|
||||
report.append("- Uptime: ");
|
||||
report.append(StringUtil.intervalToString(uptime)).append("\n");
|
||||
report.append("- Report period: ");
|
||||
report.append(StringUtil.intervalToString(sinceLast)).append("\n");
|
||||
|
||||
// report on the state of memory
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
long total = rt.totalMemory(), max = rt.maxMemory();
|
||||
long used = (total - rt.freeMemory());
|
||||
report.append("- Memory: ").append(used/1024).append("k used, ");
|
||||
report.append(total/1024).append("k total, ");
|
||||
report.append(max/1024).append("k max\n");
|
||||
}
|
||||
|
||||
for (Reporter rptr : _reporters.get(type)) {
|
||||
try {
|
||||
rptr.appendReport(report, now, sinceLast, reset);
|
||||
} catch (Throwable t) {
|
||||
log.warning("Reporter choked", "rptr", rptr, t);
|
||||
}
|
||||
}
|
||||
|
||||
/* The following Interval debug methods are no longer supported,
|
||||
* but they could be added back easily if needed.
|
||||
report.append("* samskivert.Interval:\n");
|
||||
report.append("- Registered intervals: ");
|
||||
report.append(Interval.registeredIntervalCount());
|
||||
report.append("\n- Fired since last report: ");
|
||||
report.append(Interval.getAndClearFiredIntervals());
|
||||
report.append("\n");
|
||||
*/
|
||||
|
||||
// strip off the final newline
|
||||
int blen = report.length();
|
||||
if (report.length() > 0 && report.charAt(blen-1) == '\n') {
|
||||
report.delete(blen-1, blen);
|
||||
}
|
||||
|
||||
// only reset the last report time if this is a periodic report
|
||||
if (reset) {
|
||||
_lastReportStamp = now;
|
||||
}
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the state of the server report via the default logging mechanism. Derived classes may
|
||||
* wish to log the state of the server report via a different means.
|
||||
*/
|
||||
protected void logReport (String report)
|
||||
{
|
||||
log.info(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the period on which to schedule our report generation.
|
||||
*/
|
||||
protected long getReportInterval ()
|
||||
{
|
||||
return REPORT_INTERVAL;
|
||||
}
|
||||
|
||||
/** The time at which the server was started. */
|
||||
protected long _serverStartTime = System.currentTimeMillis();
|
||||
|
||||
/** The last time at which {@link #generateReport(String,long,boolean)} was run. */
|
||||
protected long _lastReportStamp = _serverStartTime;
|
||||
|
||||
/** Used to generate "state of server" reports. */
|
||||
protected Multimap<String, Reporter> _reporters = ArrayListMultimap.create();
|
||||
|
||||
/** We use the root omgr to queue up our reporting interval. */
|
||||
@Inject protected RootDObjectManager _omgr;
|
||||
|
||||
/** The frequency with which we generate "state of server" reports. */
|
||||
protected static final long REPORT_INTERVAL = 15 * 60 * 1000L;
|
||||
|
||||
/** The header to prefix to our logged reports. */
|
||||
protected static final String LOG_REPORT_HEADER = "State of server report:\n";
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Extends invoker with a reporter implementation that shows current queue status, maximum
|
||||
* historical size and the results of unit profiling if enabled.
|
||||
*/
|
||||
public class ReportingInvoker extends Invoker
|
||||
{
|
||||
public static class Stats
|
||||
{
|
||||
public int unitsRun;
|
||||
public int maxQueueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new reporting invoker. The instance will be registered with the report manager
|
||||
* if profiling is enabled ({@link Invoker#PERF_TRACK}).
|
||||
*/
|
||||
public ReportingInvoker (String name, Executor receiver, ReportManager repmgr)
|
||||
{
|
||||
super(name, receiver);
|
||||
if (PERF_TRACK) {
|
||||
repmgr.registerReporter(ReportManager.DEFAULT_TYPE, _defrep);
|
||||
repmgr.registerReporter(ReportManager.PROFILE_TYPE, _profrep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a recent snapshot of runtime statistics tracked by the invoker.
|
||||
*
|
||||
* @param snapshot if true, the current stats will be snapshotted and reset and the new
|
||||
* snapshot will be returned. If false, the previous snapshot will be returned. If no snapshot
|
||||
* has ever been taken, the current stats that have been accumulting since the JVM start will
|
||||
* be returned.
|
||||
*/
|
||||
public Stats getStats (boolean snapshot)
|
||||
{
|
||||
synchronized (this) {
|
||||
if (snapshot) {
|
||||
_recent = _current;
|
||||
_current = new Stats();
|
||||
_current.maxQueueSize = _queue.size();
|
||||
}
|
||||
return _recent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Invoker
|
||||
protected void willInvokeUnit (Unit unit, long start)
|
||||
{
|
||||
super.willInvokeUnit(unit, start);
|
||||
|
||||
int queueSize = _queue.size();
|
||||
synchronized (this) {
|
||||
// keep track of the largest queue size we've seen
|
||||
if (queueSize > _current.maxQueueSize) {
|
||||
_current.maxQueueSize = queueSize;
|
||||
}
|
||||
_current.unitsRun++;
|
||||
_totalUnitsRun++;
|
||||
|
||||
// note the currently invoking unit
|
||||
_currentUnit = unit;
|
||||
_currentUnitStart = start;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Invoker
|
||||
protected void didInvokeUnit (Unit unit, long start)
|
||||
{
|
||||
super.didInvokeUnit(unit, start);
|
||||
|
||||
synchronized (this) {
|
||||
// clear out our currently invoking unit
|
||||
_currentUnit = null;
|
||||
_currentUnitStart = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a report on our runtime behavior. */
|
||||
protected ReportManager.Reporter _defrep = new ReportManager.Reporter() {
|
||||
public void appendReport (StringBuilder buf, long now, long sinceLast, boolean reset) {
|
||||
buf.append("* " + getName() + ":\n");
|
||||
int qsize = _queue.size();
|
||||
buf.append("- Queue size: ").append(qsize).append("\n");
|
||||
synchronized (this) {
|
||||
Stats stats = getStats(reset);
|
||||
buf.append("- Max queue size: ").append(stats.maxQueueSize).append("\n");
|
||||
buf.append("- Units executed: ").append(stats.unitsRun).append("\n");
|
||||
buf.append("- Total units executed: ").append(_totalUnitsRun);
|
||||
long runPerSec = stats.unitsRun/Math.max(1, sinceLast/1000);
|
||||
buf.append(" (").append(runPerSec).append("/s)\n");
|
||||
if (_currentUnit != null) {
|
||||
String uname = StringUtil.safeToString(_currentUnit);
|
||||
buf.append("- Current unit: ").append(uname).append(" ");
|
||||
buf.append(now-_currentUnitStart).append("ms\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Generates a report with our profiling data. */
|
||||
protected ReportManager.Reporter _profrep = new ReportManager.Reporter() {
|
||||
public void appendReport (StringBuilder buf, long now, long sinceLast, boolean reset) {
|
||||
buf.append("* " + getName() + ":\n");
|
||||
if (PresentsDObjectMgr.UNIT_PROF_ENABLED) {
|
||||
for (Object key : _tracker.keySet()) {
|
||||
UnitProfile profile = _tracker.get(key);
|
||||
if (key instanceof Class<?>) {
|
||||
key = StringUtil.shortClassName((Class<?>)key);
|
||||
}
|
||||
buf.append(" ").append(key).append(" ");
|
||||
buf.append(profile).append("\n");
|
||||
if (reset) {
|
||||
profile.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.append(" - Unit profiling disabled.\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Used to track runtime statistics. */
|
||||
protected Stats _recent = new Stats(), _current = _recent;
|
||||
|
||||
/** The total number of units run. */
|
||||
protected int _totalUnitsRun;
|
||||
|
||||
/** Records the currently invoking unit. */
|
||||
protected Object _currentUnit;
|
||||
|
||||
/** The time at which our current unit started. */
|
||||
protected long _currentUnitStart;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.ServiceCreds;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Works in conjunction with {@link ServiceCreds} to handle the authentication of service clients
|
||||
* (bureaus, peers, etc.).
|
||||
*/
|
||||
public abstract class ServiceAuthenticator<T extends ServiceCreds> extends ChainedAuthenticator
|
||||
{
|
||||
/**
|
||||
* Creates an authenticator that will handle requests using the supplied credentials class and
|
||||
* which will create instances of the supplied auth name class to identify those clients. Note
|
||||
* that the auth name class <em>must</em> have a public constructor that takes a single string.
|
||||
*/
|
||||
public ServiceAuthenticator (Class<T> credsClass, Class<? extends Name> authNameClass)
|
||||
{
|
||||
_credsClass = credsClass;
|
||||
try {
|
||||
_authNamer = authNameClass.getConstructor(String.class);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
throw new IllegalArgumentException("AuthName must have AuthName(String) constructor.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from abstract ChainedAuthenticator
|
||||
public boolean shouldHandleConnection (AuthingConnection conn)
|
||||
{
|
||||
return _credsClass.isInstance(conn.getAuthRequest().getCredentials());
|
||||
}
|
||||
|
||||
@Override // from abstract Authenticator
|
||||
protected void processAuthentication (AuthingConnection conn, AuthResponse rsp)
|
||||
throws Exception
|
||||
{
|
||||
T creds = _credsClass.cast(conn.getAuthRequest().getCredentials());
|
||||
if (!areValid(creds)) {
|
||||
log.warning("Received invalid service auth request?", "creds", creds);
|
||||
throw new AuthException(AuthCodes.SERVER_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
conn.setAuthName(_authNamer.newInstance(creds.clientId));
|
||||
rsp.getData().code = AuthResponseData.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to construct auth name", "namer", _authNamer, e);
|
||||
throw new AuthException(AuthCodes.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the creds in question are valid.
|
||||
*/
|
||||
protected abstract boolean areValid (T creds);
|
||||
|
||||
protected Class<T> _credsClass;
|
||||
protected Constructor<? extends Name> _authNamer;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.Credentials;
|
||||
|
||||
/**
|
||||
* Used to determine what type of {@link PresentsSession} to use to manage an authenticated client
|
||||
* as well the type of {@link ClientResolver} to use when resolving clients' runtime data.
|
||||
*/
|
||||
public abstract class SessionFactory
|
||||
{
|
||||
/** The default client factory. */
|
||||
public static SessionFactory DEFAULT = new SessionFactory() {
|
||||
@Override public Class<? extends PresentsSession> getSessionClass (AuthRequest areq) {
|
||||
return PresentsSession.class;
|
||||
}
|
||||
@Override public Class<? extends ClientResolver> getClientResolverClass (Name username) {
|
||||
return ClientResolver.class;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a session factory that handles clients with the supplied credentials and
|
||||
* authentication name.
|
||||
*/
|
||||
public static SessionFactory newSessionFactory (
|
||||
final Class<? extends Credentials> credsClass,
|
||||
final Class<? extends PresentsSession> sessionClass,
|
||||
final Class<? extends Name> nameClass,
|
||||
final Class<? extends ClientResolver> resolverClass)
|
||||
{
|
||||
return new SessionFactory() {
|
||||
@Override public Class<? extends PresentsSession> getSessionClass (AuthRequest areq) {
|
||||
return credsClass.isInstance(areq.getCredentials()) ? sessionClass : null;
|
||||
}
|
||||
@Override
|
||||
public Class <? extends ClientResolver> getClientResolverClass (Name username) {
|
||||
return nameClass.isInstance(username) ? resolverClass : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PresentsSession} derived class to use for the session that authenticated
|
||||
* with the supplied request or null if this factory does not handle sessions of the supplied
|
||||
* type.
|
||||
*/
|
||||
public abstract Class<? extends PresentsSession> getSessionClass (AuthRequest areq);
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientResolver} derived class to use to resolve a client with the
|
||||
* specified username or null if this factory does not handle clients of the supplied type.
|
||||
*/
|
||||
public abstract Class <? extends ClientResolver> getClientResolverClass (Name username);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
/**
|
||||
* Handles the orderly shutdown of all server services.
|
||||
*
|
||||
* @Deprecated use Lifecycle
|
||||
*/
|
||||
@Singleton
|
||||
public class ShutdownManager
|
||||
{
|
||||
/** Implementers of this interface will be notified when the server is shutting down. */
|
||||
public static interface Shutdowner extends Lifecycle.ShutdownComponent
|
||||
{
|
||||
}
|
||||
|
||||
/** Constraints for use with {@link ShutdownManager#addConstraint}. */
|
||||
public static enum Constraint { RUNS_BEFORE, RUNS_AFTER }
|
||||
|
||||
/**
|
||||
* Registers an entity that will be notified when the server is shutting down.
|
||||
*/
|
||||
public void registerShutdowner (Shutdowner downer)
|
||||
{
|
||||
_cycle.addComponent(downer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the shutdowner from hearing when the server is shutdown.
|
||||
*/
|
||||
public void unregisterShutdowner (Shutdowner downer)
|
||||
{
|
||||
_cycle.removeComponent(downer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a constraint that a certain shutdowner must be run before another.
|
||||
*/
|
||||
public void addConstraint (Shutdowner lhs, Constraint constraint, Shutdowner rhs)
|
||||
{
|
||||
switch (constraint) {
|
||||
case RUNS_BEFORE:
|
||||
_cycle.addShutdownConstraint(lhs, Lifecycle.Constraint.RUNS_BEFORE, rhs);
|
||||
break;
|
||||
case RUNS_AFTER:
|
||||
_cycle.addShutdownConstraint(lhs, Lifecycle.Constraint.RUNS_AFTER, rhs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a request to shutdown on the dobjmgr thread. This method may be safely called from
|
||||
* any thread.
|
||||
*/
|
||||
public void queueShutdown ()
|
||||
{
|
||||
_server.queueShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we're in the process of shutting down.
|
||||
*/
|
||||
public boolean isShuttingDown ()
|
||||
{
|
||||
return _cycle.isShuttingDown();
|
||||
}
|
||||
|
||||
@Inject protected Lifecycle _cycle;
|
||||
@Inject protected PresentsServer _server;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
/**
|
||||
* If injected into a presents server, listens for a USR2 Unix signal captured by
|
||||
* {@link AbstractSignalHandler}.
|
||||
*/
|
||||
public interface SignalReceiver
|
||||
{
|
||||
void received();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.SignalUtil;
|
||||
|
||||
/**
|
||||
* Handles signals using Sun's undocumented Signal class.
|
||||
*/
|
||||
public class SunSignalHandler extends AbstractSignalHandler
|
||||
{
|
||||
@Override
|
||||
protected boolean registerHandlers ()
|
||||
{
|
||||
SignalUtil.register(SignalUtil.Number.TERM, new SignalUtil.Handler() {
|
||||
public void signalReceived (SignalUtil.Number sig) {
|
||||
termReceived();
|
||||
}
|
||||
});
|
||||
SignalUtil.register(SignalUtil.Number.INT, new SignalUtil.Handler() {
|
||||
public void signalReceived (SignalUtil.Number sig) {
|
||||
intReceived();
|
||||
}
|
||||
});
|
||||
SignalUtil.register(SignalUtil.Number.USR2, new SignalUtil.Handler() {
|
||||
public void signalReceived (SignalUtil.Number sig) {
|
||||
usr2Received();
|
||||
}
|
||||
});
|
||||
if (!RunAnywhere.isWindows()) {
|
||||
SignalUtil.register(SignalUtil.Number.HUP, new SignalUtil.Handler() {
|
||||
public void signalReceived (SignalUtil.Number sig) {
|
||||
hupReceived();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.presents.client.TimeBaseService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.TimeBaseMarshaller;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link TimeBaseProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from TimeBaseService.java.")
|
||||
public class TimeBaseDispatcher extends InvocationDispatcher<TimeBaseMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public TimeBaseDispatcher (TimeBaseProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeBaseMarshaller createMarshaller ()
|
||||
{
|
||||
return new TimeBaseMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case TimeBaseMarshaller.GET_TIME_OID:
|
||||
((TimeBaseProvider)provider).getTimeOid(
|
||||
source, (String)args[0], (TimeBaseService.GotTimeBaseListener)args[1]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.threerings.presents.client.TimeBaseService.GotTimeBaseListener;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.TimeBaseCodes;
|
||||
import com.threerings.presents.data.TimeBaseObject;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
/**
|
||||
* Provides the server-side of the time base services. The time base services provide a means by
|
||||
* which delta times can be sent over the network which are expanded based on a shared base time
|
||||
* into full time stamps.
|
||||
*/
|
||||
public class TimeBaseProvider
|
||||
implements InvocationProvider, TimeBaseCodes
|
||||
{
|
||||
/**
|
||||
* Registers the time provider with the appropriate managers. Called by the presents server at
|
||||
* startup.
|
||||
*/
|
||||
public static void init (InvocationManager invmgr, RootDObjectManager omgr)
|
||||
{
|
||||
// we'll need these later
|
||||
_invmgr = invmgr;
|
||||
_omgr = omgr;
|
||||
|
||||
// register a provider instance
|
||||
invmgr.registerDispatcher(new TimeBaseDispatcher(new TimeBaseProvider()), GLOBAL_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a time base object which can subsequently be fetched by the client and used to send
|
||||
* delta times.
|
||||
*
|
||||
* @param timeBase the name of the time base to create.
|
||||
*
|
||||
* @return the created and registered time base object.
|
||||
*/
|
||||
public static TimeBaseObject createTimeBase (String timeBase)
|
||||
{
|
||||
TimeBaseObject object = _omgr.registerObject(new TimeBaseObject());
|
||||
_timeBases.put(timeBase, object);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the named timebase object, or null if no time base object has been created with that
|
||||
* name.
|
||||
*/
|
||||
public static TimeBaseObject getTimeBase (String timeBase)
|
||||
{
|
||||
return _timeBases.get(timeBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a request from a client to fetch the oid of the specified time object.
|
||||
*/
|
||||
public void getTimeOid (ClientObject source, String timeBase, GotTimeBaseListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// look up the time base object in question
|
||||
TimeBaseObject time = getTimeBase(timeBase);
|
||||
if (time == null) {
|
||||
throw new InvocationException(NO_SUCH_TIME_BASE);
|
||||
}
|
||||
// and send the response
|
||||
listener.gotTimeOid(time.getOid());
|
||||
}
|
||||
|
||||
/** Used to keep track of our time base objects. */
|
||||
protected static HashMap<String,TimeBaseObject> _timeBases = Maps.newHashMap();
|
||||
|
||||
/** The invocation manager with which we interoperate. */
|
||||
protected static InvocationManager _invmgr;
|
||||
|
||||
/** The distributed object manager with which we interoperate. */
|
||||
protected static RootDObjectManager _omgr;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.Message;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The authing connection manages the client connection until authentication has completed (for
|
||||
* better or for worse).
|
||||
*/
|
||||
public class AuthingConnection extends Connection
|
||||
{
|
||||
public AuthingConnection ()
|
||||
{
|
||||
setMessageHandler(new MessageHandler() {
|
||||
public void handleMessage (Message msg) {
|
||||
try {
|
||||
// keep a handle on our auth request
|
||||
_authreq = (AuthRequest)msg;
|
||||
// post ourselves for processing by the authmgr
|
||||
_cmgr.authenticateConnection(AuthingConnection.this);
|
||||
} catch (ClassCastException cce) {
|
||||
log.warning("Received non-authreq message during authentication process",
|
||||
"conn", AuthingConnection.this, "msg", msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the auth request currently being processed.
|
||||
*/
|
||||
public AuthRequest getAuthRequest ()
|
||||
{
|
||||
return _authreq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth response delivered to the client (only valid after the auth request has
|
||||
* been processed.
|
||||
*/
|
||||
public AuthResponse getAuthResponse ()
|
||||
{
|
||||
return _authrsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a reference to the auth response delivered to this connection. This is called by the
|
||||
* auth manager after delivering the auth response to the client.
|
||||
*/
|
||||
public void setAuthResponse (AuthResponse authrsp)
|
||||
{
|
||||
_authrsp = authrsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username that uniquely identifies this authenticated session. This will be used
|
||||
* to map Name -> PresentsSession in the ClientManager and used elsewhere.
|
||||
*/
|
||||
public Name getAuthName ()
|
||||
{
|
||||
return _authname;
|
||||
}
|
||||
|
||||
/**
|
||||
* During the authentication process, the authenticator must establish the client's
|
||||
* authentication username and configure it via this method.
|
||||
*/
|
||||
public void setAuthName (Name authname)
|
||||
{
|
||||
_authname = authname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "[mode=AUTHING, addr=" + getInetAddress() + "]";
|
||||
}
|
||||
|
||||
protected AuthRequest _authreq;
|
||||
protected AuthResponse _authrsp;
|
||||
protected Name _authname;
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.net.AddressUtil;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Binds tcp sockets and listens for datagrams in addition to ConnectionManager's normal duties.
|
||||
*/
|
||||
@Singleton
|
||||
public class BindingConnectionManager extends ConnectionManager
|
||||
{
|
||||
@Inject public BindingConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super(cycle, repmgr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the connection manager with the hostname and ports on which it will listen for
|
||||
* socket connections and datagram packets. This must be called before the connection manager
|
||||
* is started (via {@via #start}) as the sockets will be bound at that time.
|
||||
*
|
||||
* @param socketHostname the hostname to which we bind our sockets or null to bind to all
|
||||
* interfaces.
|
||||
* @param datagramHostname the hostname to which we bind our datagram socket or null to bind
|
||||
* to all interfaces.
|
||||
* @param socketPorts the ports on which to listen for TCP connection.
|
||||
* @param datagramPorts the ports on which to listen for datagram packets.
|
||||
*/
|
||||
public void init (String socketHostname, String datagramHostname,
|
||||
int[] socketPorts, int[] datagramPorts)
|
||||
throws IOException
|
||||
{
|
||||
Preconditions.checkNotNull(socketPorts, "Socket ports must be non-null.");
|
||||
Preconditions.checkNotNull(datagramPorts, "Datagram ports must be non-null. " +
|
||||
"Pass a zero-length array to bind no datagram ports.");
|
||||
|
||||
_bindHostname = socketHostname;
|
||||
_ports = socketPorts;
|
||||
_datagramHostname = datagramHostname;
|
||||
_datagramPorts = datagramPorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void willStart ()
|
||||
{
|
||||
super.willStart();
|
||||
|
||||
// open our TCP listening ports
|
||||
int successes = 0;
|
||||
for (int port : _ports) {
|
||||
try {
|
||||
acceptConnections(port);
|
||||
successes++;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure listening to socket", "hostname", _bindHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
if (successes == 0) {
|
||||
log.warning("ConnectionManager failed to bind to any ports. Shutting down.");
|
||||
_server.queueShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// open up the datagram ports as well
|
||||
for (int port : _datagramPorts) {
|
||||
try {
|
||||
acceptDatagrams(port);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure opening datagram channel", "hostname", _datagramHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void didShutdown ()
|
||||
{
|
||||
super.didShutdown();
|
||||
// TODO: consider closing the listen sockets earlier, like in the shutdown method
|
||||
|
||||
// unbind our listening sockets; note: because we wait for the objmgr to exit before we do,
|
||||
// we will still be accepting connections as long as there are events pending.
|
||||
for (ServerSocketChannel ssocket : _ssockets) {
|
||||
try {
|
||||
ssocket.socket().close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to close listening socket: " + ssocket, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// unbind datagram channels, if any
|
||||
for (DatagramChannel datagramChannel : _datagramChannels) {
|
||||
datagramChannel.socket().close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void acceptConnections (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a listening socket
|
||||
final ServerSocketChannel ssocket = ServerSocketChannel.open();
|
||||
ssocket.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_bindHostname, port);
|
||||
ssocket.socket().bind(isa);
|
||||
|
||||
// and add it to the select set
|
||||
SelectionKey sk = ssocket.register(_selector, SelectionKey.OP_ACCEPT);
|
||||
_handlers.put(sk, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
try {
|
||||
handleAcceptedSocket(ssocket.accept());
|
||||
} catch (IOException ioe) {
|
||||
log.info("Failure accepting connected socket: " +ioe);
|
||||
}
|
||||
// there's no easy way to measure bytes read when accepting a connection, so we
|
||||
// claim nothing
|
||||
return 0;
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false; // we're never idle
|
||||
}
|
||||
public void becameIdle () {
|
||||
// we're never idle
|
||||
}
|
||||
});
|
||||
_ssockets.add(ssocket);
|
||||
log.info("Server listening on " + isa + ".");
|
||||
}
|
||||
|
||||
protected void acceptDatagrams (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a channel
|
||||
final DatagramChannel channel = DatagramChannel.open();
|
||||
channel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
|
||||
channel.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_datagramHostname, port);
|
||||
channel.socket().bind(isa);
|
||||
|
||||
// and add it to the select set
|
||||
SelectionKey sk = channel.register(_selector, SelectionKey.OP_READ);
|
||||
_handlers.put(sk, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
return handleDatagram(channel, when);
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false;// Can't be idle
|
||||
}
|
||||
public void becameIdle () {}
|
||||
});
|
||||
_datagramChannels.add(channel);
|
||||
log.info("Server accepting datagrams on " + isa + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a datagram message is ready to be read off its channel.
|
||||
*/
|
||||
protected int handleDatagram (DatagramChannel listener, long when)
|
||||
{
|
||||
InetSocketAddress source;
|
||||
_databuf.clear();
|
||||
try {
|
||||
source = (InetSocketAddress)listener.receive(_databuf);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure receiving datagram.", ioe);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make sure we actually read a packet
|
||||
if (source == null) {
|
||||
log.info("Psych! Got READ_READY, but no datagram.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// flip the buffer and record the size (which must be at least 14 to contain the connection
|
||||
// id, authentication hash, and a class reference)
|
||||
int size = _databuf.flip().remaining();
|
||||
if (size < 14) {
|
||||
log.warning("Received undersized datagram", "source", source, "size", size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// the first four bytes are the connection id
|
||||
int connectionId = _databuf.getInt();
|
||||
Connection conn = _connections.get(connectionId);
|
||||
if (conn != null) {
|
||||
conn.handleDatagram(source, listener, _databuf, when);
|
||||
} else {
|
||||
log.debug("Received datagram for unknown connection", "id", connectionId,
|
||||
"source", source);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
protected int[] _ports, _datagramPorts;
|
||||
protected String _bindHostname, _datagramHostname;
|
||||
|
||||
protected List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
protected List<DatagramChannel> _datagramChannels = Lists.newArrayList();
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.threerings.io.FramedInputStream;
|
||||
import com.threerings.io.FramingOutputStream;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.util.DatagramSequencer;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The base connection class implements the net event handler interface and processes raw incoming
|
||||
* network data into a stream of parsed {@link Message} objects. It also provides the means to send
|
||||
* messages to the client and facilities for checking delinquency.
|
||||
*/
|
||||
public class Connection implements NetEventHandler
|
||||
{
|
||||
/** Used with {@link Connection#setMessageHandler}. */
|
||||
public static interface MessageHandler {
|
||||
/** Called when a complete message has been parsed from incoming network data. */
|
||||
void handleMessage (Message message);
|
||||
}
|
||||
|
||||
/** The key used by the NIO code to track this connection. */
|
||||
public SelectionKey selkey;
|
||||
|
||||
/**
|
||||
* Initializes a connection object with a socket and related info.
|
||||
*
|
||||
* @param cmgr The connection manager with which this connection is associated.
|
||||
* @param channel The socket channel from which we'll be reading messages.
|
||||
* @param createStamp The time at which this connection was created.
|
||||
*/
|
||||
public void init (ConnectionManager cmgr, SocketChannel channel, long createStamp)
|
||||
throws IOException
|
||||
{
|
||||
_cmgr = cmgr;
|
||||
_channel = channel;
|
||||
_lastEvent = createStamp;
|
||||
_connectionId = ++_lastConnectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the connection to pass parsed messages on to this handler for processing. This
|
||||
* should be done before the connection is turned loose to process messages.
|
||||
*/
|
||||
public void setMessageHandler (MessageHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this connection with a custom class loader.
|
||||
*/
|
||||
public void setClassLoader (ClassLoader loader)
|
||||
{
|
||||
_loader = loader;
|
||||
if (_oin != null) {
|
||||
_oin.setClassLoader(loader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection's unique identifier.
|
||||
*/
|
||||
public int getConnectionId ()
|
||||
{
|
||||
return _connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-blocking socket object used to construct this connection.
|
||||
*/
|
||||
public SocketChannel getChannel ()
|
||||
{
|
||||
return _channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address associated with this connection or null if it has no underlying socket
|
||||
* channel.
|
||||
*/
|
||||
public InetAddress getInetAddress ()
|
||||
{
|
||||
return (_channel == null) ? null : _channel.socket().getInetAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether we should transmit datagrams.
|
||||
*/
|
||||
public void setTransmitDatagrams (boolean transmit)
|
||||
{
|
||||
_transmitDatagrams = transmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we should transmit datagrams.
|
||||
*/
|
||||
public boolean getTransmitDatagrams ()
|
||||
{
|
||||
return _transmitDatagrams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address to which datagrams should be sent or null if no datagram address has
|
||||
* been established.
|
||||
*/
|
||||
public InetSocketAddress getDatagramAddress ()
|
||||
{
|
||||
return _datagramAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel through which datagrams should be sent or null if no datagram channel
|
||||
* has been established.
|
||||
*/
|
||||
public DatagramChannel getDatagramChannel ()
|
||||
{
|
||||
return _datagramChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secret string used to authenticate datagrams from the client.
|
||||
*/
|
||||
public void setDatagramSecret (String secret)
|
||||
{
|
||||
try {
|
||||
_datagramSecret = secret.getBytes("UTF-8");
|
||||
} catch (Exception e) {
|
||||
_datagramSecret = new byte[0]; // shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this connection is closed.
|
||||
*/
|
||||
public boolean isClosed ()
|
||||
{
|
||||
return (_channel == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this connection and unregisters it from the connection manager. This should only be
|
||||
* called from the conmgr thread.
|
||||
*/
|
||||
public void close ()
|
||||
{
|
||||
// we shouldn't be closed twice
|
||||
if (isClosed()) {
|
||||
log.warning("Attempted to re-close connection " + this + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// unregister from the select set
|
||||
_cmgr.connectionClosed(this);
|
||||
|
||||
// close our socket
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a request to have this connection closed by the connection manager once all
|
||||
* messages in its queue have been written to its target.
|
||||
*/
|
||||
public void asyncClose ()
|
||||
{
|
||||
_cmgr.postAsyncClose(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a 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 (Message msg)
|
||||
{
|
||||
// pass this along to the connection manager
|
||||
_cmgr.postMessage(this, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an outgoing socket experiences a connect failure. The connection manager will
|
||||
* have cleaned up the partial registration needed during the connect process, so we are only
|
||||
* responsible for closing our socket.
|
||||
*/
|
||||
public void connectFailure (IOException ioe)
|
||||
{
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is a failure reading or writing to this connection. We notify the
|
||||
* connection manager and close ourselves down.
|
||||
*/
|
||||
public void networkFailure (IOException ioe)
|
||||
{
|
||||
// if we're already closed, then something is seriously funny
|
||||
if (isClosed()) {
|
||||
log.warning("Failure reported on closed connection " + this + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// let the connection manager know we're hosed
|
||||
_cmgr.connectionFailed(this, ioe);
|
||||
|
||||
// and close our socket
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a datagram sent to this connection.
|
||||
*/
|
||||
public void handleDatagram (InetSocketAddress source, DatagramChannel channel,
|
||||
ByteBuffer buf, long when)
|
||||
{
|
||||
// lazily create our various bits and bobs
|
||||
if (_digest == null) {
|
||||
try {
|
||||
_digest = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
log.warning("Missing MD5 algorithm.");
|
||||
return;
|
||||
}
|
||||
_sequencer = _cmgr.createDatagramSequencer();
|
||||
}
|
||||
|
||||
// verify the hash
|
||||
buf.position(12);
|
||||
_digest.update(buf);
|
||||
byte[] hash = _digest.digest(_datagramSecret);
|
||||
buf.position(4);
|
||||
for (int ii = 0; ii < 8; ii++) {
|
||||
if (hash[ii] != buf.get()) {
|
||||
log.warning("Datagram failed hash check", "id", _connectionId, "source", source);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// update our target address
|
||||
_datagramAddress = source;
|
||||
_datagramChannel = channel;
|
||||
|
||||
// read the contents through the sequencer
|
||||
try {
|
||||
Message msg = _sequencer.readDatagram();
|
||||
if (msg == null) {
|
||||
return; // received out of order
|
||||
}
|
||||
msg.received = when;
|
||||
_handler.handleMessage(msg);
|
||||
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
log.warning("Error reading datagram", "error", cnfe);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error reading datagram", "error", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public int handleEvent (long when)
|
||||
{
|
||||
// make a note that we received an event as of this time
|
||||
_lastEvent = when;
|
||||
|
||||
int bytesIn = 0;
|
||||
try {
|
||||
// we're lazy about creating our input streams because we may be inheriting them from
|
||||
// our authing connection and we don't want to unnecessarily create them in that case
|
||||
if (_fin == null) {
|
||||
_fin = new FramedInputStream();
|
||||
_oin = new ObjectInputStream(_fin);
|
||||
if (_loader != null) {
|
||||
_oin.setClassLoader(_loader);
|
||||
}
|
||||
}
|
||||
|
||||
// there may be more than one frame in the buffer, so we keep reading them until we run
|
||||
// out of data
|
||||
while (_fin.readFrame(_channel)) {
|
||||
// make a note of how many bytes are in this frame (including the frame length
|
||||
// bytes which aren't reported in available())
|
||||
bytesIn = _fin.available() + 4;
|
||||
// parse the message and pass it on
|
||||
Message msg = (Message)_oin.readObject();
|
||||
msg.received = when;
|
||||
// Log.info("Read message " + msg + ".");
|
||||
_handler.handleMessage(msg);
|
||||
}
|
||||
|
||||
} catch (EOFException eofe) {
|
||||
// close down the socket gracefully
|
||||
close();
|
||||
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
log.warning("Error reading message from socket", "channel", _channel, "error", cnfe);
|
||||
// deal with the failure
|
||||
String errmsg = "Unable to decode incoming message.";
|
||||
networkFailure((IOException) new IOException(errmsg).initCause(cnfe));
|
||||
|
||||
} catch (IOException ioe) {
|
||||
// don't log a warning for the ever-popular "the client dropped the connection" failure
|
||||
String msg = ioe.getMessage();
|
||||
if (msg == null || msg.indexOf("reset by peer") == -1) {
|
||||
log.warning("Error reading message from socket", "channel", _channel, "error", ioe);
|
||||
}
|
||||
// deal with the failure
|
||||
networkFailure(ioe);
|
||||
}
|
||||
|
||||
return bytesIn;
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public boolean checkIdle (long now)
|
||||
{
|
||||
long idleMillis = now - _lastEvent;
|
||||
if (idleMillis < PingRequest.PING_INTERVAL + LATENCY_GRACE) {
|
||||
return false;
|
||||
}
|
||||
if (isClosed()) {
|
||||
return true;
|
||||
}
|
||||
log.info("Disconnecting non-communicative client", "conn", this, "idle", idleMillis + "ms");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public void becameIdle ()
|
||||
{
|
||||
_cmgr.closeConnection(this);
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public String toString ()
|
||||
{
|
||||
return "[id=" + (hashCode() % 1000) + ", addr=" + getInetAddress() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object input stream associated with this connection. This should only be used
|
||||
* by the connection manager.
|
||||
*/
|
||||
protected ObjectInputStream getObjectInputStream ()
|
||||
{
|
||||
return _oin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs this connection to inherit its streams from the supplied connection object. This
|
||||
* is called by the connection manager when the time comes to pass streams from the authing
|
||||
* connection to the running connection.
|
||||
*/
|
||||
protected void inheritStreams (Connection other)
|
||||
{
|
||||
_fin = other._fin;
|
||||
_oin = other._oin;
|
||||
_oout = other._oout;
|
||||
if (_loader != null) {
|
||||
_oin.setClassLoader(_loader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object output stream associated with this connection (creating it if
|
||||
* necessary). This should only be used by the connection manager.
|
||||
*/
|
||||
protected ObjectOutputStream getObjectOutputStream (FramingOutputStream fout)
|
||||
{
|
||||
// we're lazy about creating our output stream because we may be inheriting it from our
|
||||
// authing connection and we don't want to unnecessarily create it in that case
|
||||
if (_oout == null) {
|
||||
_oout = new ObjectOutputStream(fout);
|
||||
}
|
||||
return _oout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object output stream used by this connection. This should only be called by the
|
||||
* connection manager.
|
||||
*/
|
||||
protected void setObjectOutputStream (ObjectOutputStream oout)
|
||||
{
|
||||
_oout = oout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the connection's datagram sequencer. This should only be called by
|
||||
* the connection manager.
|
||||
*/
|
||||
protected DatagramSequencer getDatagramSequencer ()
|
||||
{
|
||||
return _sequencer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket associated with this connection. This happens when we receive EOF, are
|
||||
* requested to close down or when our connection fails.
|
||||
*/
|
||||
protected void closeSocket ()
|
||||
{
|
||||
if (_channel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Closing channel " + this + ".");
|
||||
try {
|
||||
_channel.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing connection", "conn", this, "error", ioe);
|
||||
}
|
||||
|
||||
// clear out our references to prevent repeat closings
|
||||
_channel = null;
|
||||
}
|
||||
|
||||
protected ConnectionManager _cmgr;
|
||||
protected SocketChannel _channel;
|
||||
|
||||
protected long _lastEvent;
|
||||
|
||||
protected int _connectionId;
|
||||
|
||||
protected FramedInputStream _fin;
|
||||
protected ObjectInputStream _oin;
|
||||
protected ObjectOutputStream _oout;
|
||||
|
||||
protected InetSocketAddress _datagramAddress;
|
||||
protected DatagramChannel _datagramChannel;
|
||||
protected byte[] _datagramSecret;
|
||||
protected boolean _transmitDatagrams;
|
||||
|
||||
protected MessageDigest _digest;
|
||||
protected DatagramSequencer _sequencer;
|
||||
|
||||
protected MessageHandler _handler;
|
||||
protected ClassLoader _loader;
|
||||
|
||||
/** The last connection id assigned. */
|
||||
protected static int _lastConnectionId;
|
||||
|
||||
/** The number of milliseconds beyond the ping interval that we allow a client's network
|
||||
* connection to be idle before we forcibly disconnect them. */
|
||||
protected static final long LATENCY_GRACE = 30 * 1000L;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
/**
|
||||
* When a network event occurs, the connection manager calls the net event
|
||||
* handler associated with that channel 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.
|
||||
*
|
||||
* <p> Utilising 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 this handler's source.
|
||||
*
|
||||
* @return the number of bytes read from the network as a result of
|
||||
* handling this event.
|
||||
*/
|
||||
int handleEvent (long when);
|
||||
|
||||
/**
|
||||
* Called to ensure that this channel has not been idle for longer
|
||||
* than is possible in happily operating circumstances.
|
||||
*
|
||||
* @return true if the handler is idle (in which case it will be
|
||||
* closed shortly), false if it is not.
|
||||
*/
|
||||
boolean checkIdle (long now);
|
||||
|
||||
/**
|
||||
* Called if the handler is deemed to be idle. Should shutdown any associated connection and
|
||||
* remove any registrations with the connection manager.
|
||||
*/
|
||||
void becameIdle ();
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.ClientObserver;
|
||||
import com.threerings.presents.client.Communicator;
|
||||
import com.threerings.presents.client.ObserverOps;
|
||||
import com.threerings.presents.client.SessionObserver;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.LogoffRequest;
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
import com.threerings.presents.net.UpstreamMessage;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Provides Client {@link Communicator} services using non-blocking I/O and the connection manager.
|
||||
*/
|
||||
public class ServerCommunicator extends Communicator
|
||||
{
|
||||
public ServerCommunicator (Client client, ConnectionManager conmgr, RootDObjectManager rootmgr)
|
||||
{
|
||||
super(client);
|
||||
_conmgr = conmgr;
|
||||
_rootmgr = rootmgr;
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
public void logon ()
|
||||
{
|
||||
// make sure things are copacetic
|
||||
if (_conn != null) {
|
||||
throw new RuntimeException("Communicator already started.");
|
||||
}
|
||||
|
||||
// we assume server entities have no firewall issues and can connect on the first port
|
||||
try {
|
||||
Connection conn = new Connection() {
|
||||
@Override public void postMessage (Message msg) {
|
||||
super.postMessage(msg);
|
||||
// outgoing traffic on this connection is used to prevent idleness
|
||||
// TODO: shouldn't PongResponse handle this?
|
||||
_lastEvent = System.currentTimeMillis();
|
||||
if (PING_DEBUG && msg instanceof PingRequest) { // TEMP
|
||||
log.info("Pinging on server comm " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void connectFailure (IOException ioe) {
|
||||
_logonError = ioe; // report this as a logon failure
|
||||
super.connectFailure(ioe);
|
||||
}
|
||||
|
||||
@Override public void networkFailure (final IOException ioe) {
|
||||
notifyClientObservers(new ObserverOps.Client(_client) {
|
||||
@Override protected void notify (ClientObserver obs) {
|
||||
obs.clientConnectionFailed(_client, ioe);
|
||||
}
|
||||
});
|
||||
super.networkFailure(ioe);
|
||||
}
|
||||
|
||||
@Override protected void closeSocket () {
|
||||
super.closeSocket();
|
||||
shutdown();
|
||||
}
|
||||
};
|
||||
conn.setMessageHandler(new Connection.MessageHandler() {
|
||||
public void handleMessage (Message message) {
|
||||
try {
|
||||
// our first message will always be an auth response
|
||||
gotAuthResponse((AuthResponse)message);
|
||||
} catch (Exception e) {
|
||||
_logonError = e;
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
_conmgr.openOutgoingConnection(conn, _client.getHostname(), _client.getPorts()[0]);
|
||||
_conn = conn;
|
||||
if (_loader != null) {
|
||||
_conn.setClassLoader(_loader);
|
||||
}
|
||||
|
||||
// now send our auth request
|
||||
postMessage(new AuthRequest(_client.getCredentials(), _client.getVersion(),
|
||||
_client.getBootGroups()));
|
||||
|
||||
} catch (IOException ioe) {
|
||||
_logonError = ioe;
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
public void logoff ()
|
||||
{
|
||||
if (_conn != null) {
|
||||
_conn.postMessage(new LogoffRequest());
|
||||
_conn.asyncClose();
|
||||
_conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
public void gotBootstrap ()
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
public void postMessage (final UpstreamMessage msg)
|
||||
{
|
||||
// if we're not on the main dobjmgr thread, we need to get there
|
||||
if (!_rootmgr.isDispatchThread()) {
|
||||
_rootmgr.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
postMessage(msg);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (_conn == null) {
|
||||
log.info("Dropping message for lack of connection.", "client", _client, "msg", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// pass this message along to our connection
|
||||
_conn.postMessage(msg);
|
||||
|
||||
// we cheat a bit and claim that we "wrote" when we post our messages so that we don't have
|
||||
// to modify the connection manager to call a method on Connection every time it writes a
|
||||
// message from the queue
|
||||
updateWriteStamp();
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
public void setClassLoader (ClassLoader loader)
|
||||
{
|
||||
_loader = loader;
|
||||
if (_conn != null) {
|
||||
_conn.setClassLoader(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Communicator
|
||||
protected synchronized void logonSucceeded (AuthResponseData data)
|
||||
{
|
||||
super.logonSucceeded(data);
|
||||
|
||||
// now we can route all messages to the ClientDObjectMgr
|
||||
_conn.setMessageHandler(new Connection.MessageHandler() {
|
||||
public void handleMessage (Message message) {
|
||||
if (PING_DEBUG && message instanceof PongResponse) {
|
||||
log.info("Got pong from server " + message);
|
||||
}
|
||||
processMessage(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void shutdown ()
|
||||
{
|
||||
if (_logonError == null) {
|
||||
// we were logged on successfully, so report didLogoff first
|
||||
notifyClientObservers(new ObserverOps.Session(_client) {
|
||||
@Override protected void notify (SessionObserver obs) {
|
||||
obs.clientDidLogoff(_client);
|
||||
}
|
||||
});
|
||||
}
|
||||
clientCleanup(_logonError);
|
||||
}
|
||||
|
||||
protected ConnectionManager _conmgr;
|
||||
protected RootDObjectManager _rootmgr;
|
||||
protected Connection _conn;
|
||||
protected ClassLoader _loader;
|
||||
protected Exception _logonError;
|
||||
|
||||
// TEMP
|
||||
protected static final boolean PING_DEBUG = Boolean.getBoolean("ping_debug");
|
||||
}
|
||||
Reference in New Issue
Block a user