org.directwebremoting.dwrp.BasePollHandler Maven / Gradle / Ivy
package org.directwebremoting.dwrp;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.Container;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.extend.Alarm;
import org.directwebremoting.extend.ContainerAbstraction;
import org.directwebremoting.extend.ConverterManager;
import org.directwebremoting.extend.EnginePrivate;
import org.directwebremoting.extend.PageNormalizer;
import org.directwebremoting.extend.ProtocolConstants;
import org.directwebremoting.extend.RealScriptSession;
import org.directwebremoting.extend.RealWebContext;
import org.directwebremoting.extend.ScriptSessionManager;
import org.directwebremoting.extend.ServerLoadMonitor;
import org.directwebremoting.extend.Sleeper;
import org.directwebremoting.impl.ShutdownAlarm;
import org.directwebremoting.impl.TimedAlarm;
import org.directwebremoting.util.BrowserDetect;
import org.directwebremoting.util.MimeConstants;
/**
* A Marshaller that output plain Javascript.
* This marshaller can be tweaked to output Javascript in an HTML context.
* This class works in concert with CallScriptConduit, they should be
* considered closely related and it is important to understand what one does
* while editing the other.
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class BasePollHandler extends BaseDwrpHandler
{
/**
* @param plain Are we using plain javascript or html wrapped javascript
*/
public BasePollHandler(boolean plain)
{
this.plain = plain;
}
/* (non-Javadoc)
* @see org.directwebremoting.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
public void handle(final HttpServletRequest request, final HttpServletResponse response) throws IOException
{
// If you're new to understanding this file, you may wish to skip this
// step and come back to it later ;-)
// So Jetty does something a bit weird with Ajax Continuations. You
// suspend a request while keeping hold
// of a continuation object. There are methods on this continuation
// object to resume the request. Also you can write to the output at
// any time the request is suspended. When the continuation is
// resumed, rather than resume the thread from where is was
// suspended, it starts it from the beginning again and will arrive
// here. Below we detect this and bail out after doing the work
// associated with the resumed request.
if (containerAbstraction.handleResumedRequest(request))
{
return;
}
// A PollBatch is the information that we expect from the request.
// if the parse fails we can do little more than tell the browser that
// something went wrong.
final PollBatch batch;
try
{
batch = new PollBatch(request);
}
catch (Exception ex)
{
log.debug("Failed to parse request", ex);
// Send a HTTP 400 to signal Bad Request
// (we have no possibility to reply with a DWR handle* script call as we don't know
// the instanceId, and this is required for script tag remoting
response.sendError(400, "Failed to parse request");
return;
}
// Security checks first, once we've parsed the input
checkGetAllowed(batch);
checkNotCsrfAttack(request, batch);
// Initialize WebContext stuff
String normalizedPage = pageNormalizer.normalizePage(batch.getPage());
final RealScriptSession scriptSession = scriptSessionManager.getOrCreateScriptSession(batch.getScriptSessionId(), normalizedPage, request.getSession(false));
RealWebContext webContext = (RealWebContext) WebContextFactory.get();
webContext.initialize(normalizedPage, scriptSession);
// We might need to complain that reverse ajax is not enabled.
if (!activeReverseAjaxEnabled)
{
log.error("Polling and Comet are disabled. To enable them set the init-param activeReverseAjaxEnabled to true.");
String script = EnginePrivate.getRemotePollCometDisabledScript(batch.getBatchId());
sendErrorScript(response, batch, script);
return;
}
// A script conduit is some route from a ScriptSession back to the page
// that belongs to the session. There may be zero or many of these
// conduits (although if there are more than 2, something is strange)
// All scripts destined for a page go to a ScriptSession and then out
// via a ScriptConduit.
scriptSession.confirmScripts(batch.getNextReverseAjaxIndex() - 1);
// Create a conduit depending on the type of request (from the URL)
final BaseScriptConduit conduit = createScriptConduit(response.getWriter(), batch);
// So we're going to go to sleep. How do we wake up?
final Sleeper sleeper = containerAbstraction.createSleeper(request, response, scriptSession, conduit);
container.initializeBean(sleeper);
// There are various reasons why we want to wake up and carry on ...
final List alarms = new ArrayList();
// Use of comet depends on the type of browser and the number of current
// connections from this browser (detected by cookies)
boolean clientSupportsLongRequests = BrowserDetect.supportsComet(request);
boolean clientSupportsStreamingUpdates = (batch.getPartialResponse() != PartialResponse.NO);
boolean configurationSaysFullStreaming = streamingEnabled || (maxWaitAfterWrite == -1);
boolean canWeHaveFullStreaming = clientSupportsLongRequests && clientSupportsStreamingUpdates && configurationSaysFullStreaming;
// For early closing mode add an output listener to the script session that calls the
// "wake me" method on whatever is putting us to sleep - if the client
// does not support streaming or streaming has not been configured.
final Sleeper proxiedSleeper;
if (!canWeHaveFullStreaming) {
final int earlyCloseTimeout = (maxWaitAfterWrite == -1) ? ProtocolConstants.FALLBACK_MAX_WAIT_AFTER_WRITE : maxWaitAfterWrite;
proxiedSleeper = new Sleeper()
{
public void enterSleep(String batchId, Runnable onClose, int disconnectedTime) throws IOException
{
sleeper.enterSleep(batchId, onClose, disconnectedTime);
}
public void wakeUpForData()
{
executor.schedule(new Runnable()
{
public void run()
{
sleeper.wakeUpToClose();
}
}, earlyCloseTimeout, TimeUnit.MILLISECONDS);
sleeper.wakeUpForData();
}
public int wakeUpToClose()
{
return sleeper.wakeUpToClose();
}
};
} else {
proxiedSleeper = sleeper;
}
// Set the system up to resume anyway after maxConnectedTime
alarms.add(new TimedAlarm(proxiedSleeper, serverLoadMonitor.getConnectedTime(), executor));
// We also need to wake-up if the server is being shut down
// WARNING: This code has a non-obvious side effect - The server load
// monitor (which hands out shutdown messages) also monitors usage by
// looking at the number of connected alarms.
alarms.add(new ShutdownAlarm(proxiedSleeper, serverLoadMonitor));
// Register the sleeper with a script session so messages can get out.
scriptSession.setSleeper(proxiedSleeper);
// We need to do some stuff when time has come to close sleeper...
final int disconnectedTime = serverLoadMonitor.getDisconnectedTime();
Runnable onClose = new Runnable()
{
public void run()
{
// Cancel all the alarms
for (Alarm alarm : alarms)
{
alarm.cancel();
}
// We can't be used as a sleeper for this session any longer
scriptSession.clearSleeper(proxiedSleeper);
updateCsrfState(request, batch);
}
};
// Flush any queued scripts
if (scriptSession.getScript(0) != null) {
proxiedSleeper.wakeUpForData();
}
// Actually go to sleep. This *must* be the last thing in this method to
// cope with all the methods of affecting Threads.
proxiedSleeper.enterSleep(batch.getBatchId(), onClose, disconnectedTime);
}
/**
* Create the correct type of ScriptConduit depending on the request.
* @param batch The parsed request
* @return A correctly configured conduit
* @throws IOException If the response can't be interrogated
*/
private BaseScriptConduit createScriptConduit(PrintWriter out, PollBatch batch) throws IOException
{
BaseScriptConduit conduit;
if (plain)
{
conduit = new PlainScriptConduit(out, batch.getInstanceId(), null);
}
else
{
conduit = new HtmlScriptConduit(out, batch.getInstanceId(), batch.getBatchId(), batch.getDocumentDomain());
}
return conduit;
}
/**
* Send a script to the browser and wrap it in the required prefixes etc.
* @param response The http response to write to
* @param batch ...
* @param script The script to write
* @throws IOException if writing fails.
*/
protected void sendErrorScript(HttpServletResponse response, Batch batch, String script) throws IOException
{
PrintWriter out = response.getWriter();
if (plain)
{
response.setContentType(MimeConstants.MIME_PLAIN);
}
else
{
response.setContentType(MimeConstants.MIME_HTML);
}
out.println(ProtocolConstants.SCRIPT_START_MARKER);
out.println(EnginePrivate.remoteBeginWrapper(batch.getInstanceId(), !plain, null));
out.println(script);
out.println(EnginePrivate.remoteEndWrapper(batch.getInstanceId(), !plain));
out.println(ProtocolConstants.SCRIPT_END_MARKER);
}
/**
* @param container DI container
*/
public void setContainer(Container container)
{
this.container = container;
}
/**
* DI container
*/
protected Container container;
/**
* @return Are we outputting in JSON mode?
*/
public boolean isJsonOutput()
{
return jsonOutput;
}
/**
* @param jsonOutput Are we outputting in JSON mode?
*/
public void setJsonOutput(boolean jsonOutput)
{
this.jsonOutput = jsonOutput;
}
/**
* Are we outputting in JSON mode?
*/
protected boolean jsonOutput = false;
/**
* Use {@link #setActiveReverseAjaxEnabled(boolean)}
* @param pollAndCometEnabled Are we doing full reverse ajax
* @deprecated Use {@link #setActiveReverseAjaxEnabled(boolean)}
*/
@Deprecated
public void setPollAndCometEnabled(boolean pollAndCometEnabled)
{
this.activeReverseAjaxEnabled = pollAndCometEnabled;
}
/**
* Are we doing full reverse ajax
* @param activeReverseAjaxEnabled Are we doing full reverse ajax
*/
public void setActiveReverseAjaxEnabled(boolean activeReverseAjaxEnabled)
{
this.activeReverseAjaxEnabled = activeReverseAjaxEnabled;
}
/**
* Are we doing full reverse ajax
*/
protected boolean activeReverseAjaxEnabled = false;
/**
* Sometimes with proxies, you need to close the stream all the time to
* make the flush work. A value of -1 indicates that we do not do early
* closing after writes IF the client supports streaming. If the client
* does not support streaming FALLBACK_MAX_WAIT_AFTER_WRITE is used.
* @param maxWaitAfterWrite the maxWaitAfterWrite to set
*/
public void setMaxWaitAfterWrite(int maxWaitAfterWrite)
{
this.maxWaitAfterWrite = maxWaitAfterWrite;
}
/**
* Sometimes with proxies, you need to close the stream all the time to
* make the flush work. A value of -1 indicates that we do not do early
* closing after writes IF the client supports streaming. If the client
* does not support streaming FALLBACK_MAX_WAIT_AFTER_WRITE is used.
*/
protected int maxWaitAfterWrite = -1;
/**
* Do we support streaming for clients that allow it?
*
* @param streamingEnabled ...
*/
public void setStreamingEnabled(boolean streamingEnabled)
{
this.streamingEnabled = streamingEnabled;
}
/**
* Do we support streaming for clients that allow it?
*/
private boolean streamingEnabled;
/**
* Accessor for the PageNormalizer.
* @param pageNormalizer The new PageNormalizer
*/
public void setPageNormalizer(PageNormalizer pageNormalizer)
{
this.pageNormalizer = pageNormalizer;
}
/**
* How we turn pages into the canonical form.
*/
protected PageNormalizer pageNormalizer;
/**
* Accessor for the server load monitor
* @param serverLoadMonitor the new server load monitor
*/
public void setServerLoadMonitor(ServerLoadMonitor serverLoadMonitor)
{
this.serverLoadMonitor = serverLoadMonitor;
}
/**
* We need to tell the system that we are waiting so it can load adjust
*/
protected ServerLoadMonitor serverLoadMonitor = null;
/**
* Accessor for the DefaultConverterManager that we configure
* @param converterManager The new DefaultConverterManager
*/
public void setConverterManager(ConverterManager converterManager)
{
this.converterManager = converterManager;
}
/**
* How we convert parameters
*/
protected ConverterManager converterManager = null;
/**
* @param scriptSessionManager the scriptSessionManager to set
*/
public void setScriptSessionManager(ScriptSessionManager scriptSessionManager)
{
this.scriptSessionManager = scriptSessionManager;
}
/**
* The owner of script sessions
*/
protected ScriptSessionManager scriptSessionManager = null;
/**
* @param containerAbstraction the containerAbstraction to set
*/
public void setContainerAbstraction(ContainerAbstraction containerAbstraction)
{
this.containerAbstraction = containerAbstraction;
}
/**
* How we abstract away container specific logic
*/
protected ContainerAbstraction containerAbstraction = null;
/**
* How often do we check for script sessions that need timing out
* @param executor ...
*/
public void setScheduledThreadPoolExecutor(ScheduledThreadPoolExecutor executor)
{
this.executor = executor;
}
/**
* @see #setScheduledThreadPoolExecutor(ScheduledThreadPoolExecutor)
*/
protected ScheduledThreadPoolExecutor executor;
/**
* Are we using plain javascript or html wrapped javascript.
* This is set by the constructor
*/
protected boolean plain;
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(BasePollHandler.class);
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy