return;
}
Thread currentThread = Thread.currentThread();
String threadName = currentThread.getName();
EndpointPushNotifier notifier = null;
boolean suppressIOExceptionLogging = false; // Used to suppress logging for IO exception.
try
{
currentThread.setName(threadName + STREAMING_THREAD_NAME_EXTENSION);
// Open and commit response headers and get output stream.
if (addNoCacheHeaders)
addNoCacheHeaders(req, res);
res.setContentType(getResponseContentType());
res.setHeader("Connection", "close");
res.setHeader("Transfer-Encoding", "chunked");
ServletOutputStream os = res.getOutputStream();
res.flushBuffer();
// If kickstart-bytes are specified, stream them.
if (kickStartBytesToStream != null)
{
if (Log.isDebug())
log.debug("Endpoint with id '" + getId() + "' is streaming " + kickStartBytesToStream.length
+ " bytes (not counting chunk encoding overhead) to kick-start the streaming connection for FlexClient with id '"
+ flexClient.getId() + "'.");
streamChunk(kickStartBytesToStream, os, res);
}
// Setup serialization and type marshalling contexts
setThreadLocals();
// Activate streaming helper for this connection.
// Watch out for duplicate stream issues.
try
{
notifier = new EndpointPushNotifier(this, flexClient);
}
catch (MessageException me)
{
if (me.getNumber() == 10033) // It's a duplicate stream request from the same FlexClient. Leave the current stream in place and fault this.
{
if (Log.isWarn())
log.warn("Endpoint with id '" + getId() + "' received a duplicate streaming connection request from, FlexClient with id '"
+ flexClient.getId() + "'. Faulting request.");
// Rollback counters and send an error response.
synchronized (lock)
{
--streamingClientsCount;
canStream = (streamingClientsCount < maxStreamingClients);
synchronized (session)
{
--session.streamingConnectionsCount;
session.canStream = (session.streamingConnectionsCount < session.maxConnectionsPerSession);
}
}
try
{
res.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
catch (IOException ignore)
{
// NOWARN
}
return; // Exit early.
}
}
notifier.setIdleTimeoutMinutes(idleTimeoutMinutes);
notifier.setLogCategory(getLogCategory());
monitorTimeout(notifier);
currentStreamingRequests.put(notifier.getNotifierId(), notifier);
// Push down an acknowledgement for the 'connect' request containing the unique id for this specific stream.
AcknowledgeMessage connectAck = new AcknowledgeMessage();
connectAck.setBody(notifier.getNotifierId());
connectAck.setCorrelationId(BaseStreamingHTTPEndpoint.OPEN_COMMAND);
ArrayList toPush = new ArrayList(1);
toPush.add(connectAck);
streamMessages(toPush, os, res);
// Output session level streaming count.
if (Log.isDebug())
Log.getLogger(FlexSession.FLEX_SESSION_LOG_CATEGORY).info("Number of streaming clients for FlexSession with id '"+ session.getId() +"' is " + session.streamingConnectionsCount + ".");
// Output endpoint level streaming count.
if (Log.isDebug())
log.debug("Number of streaming clients for endpoint with id '"+ getId() +"' is " + streamingClientsCount + ".");
// And cycle in a wait-notify loop with the aid of the helper until it
// is closed, we're interrupted or the act of streaming data to the client fails.
while (!notifier.isClosed())
{
// Synchronize on pushNeeded which is our condition variable.
synchronized (notifier.pushNeeded)
{
try
{
// Drain any messages that might have been accumulated
// while the previous drain was being processed.
streamMessages(notifier.drainMessages(), os, res);
notifier.pushNeeded.wait(serverToClientHeartbeatMillis);
List messages = notifier.drainMessages();
// If there are no messages to send to the client, send an null
// byte as a heartbeat to make sure the client is still valid.
if (messages == null && serverToClientHeartbeatMillis > 0)
{
try
{
os.write(NULL_BYTE);
res.flushBuffer();
}
catch (IOException e)
{
if (Log.isWarn())
log.warn("Endpoint with id '" + getId() + "' is closing the streaming connection to FlexClient with id '"
+ flexClient.getId() + "' because endpoint encountered a socket write error" +
", possibly due to an unresponsive FlexClient.");
break; // Exit the wait loop.
}
}
// Otherwise stream the messages to the client.
else
{
// Update the last time notifier was used to drain messages.
// Important for idle timeout detection.
notifier.updateLastUse();
streamMessages(messages, os, res);
}
}
catch (InterruptedException e)
{
if (Log.isWarn())
log.warn("Streaming thread '" + threadName + "' for endpoint with id '" + getId() + "' has been interrupted and the streaming connection will be closed.");
os.close();
break; // Exit the wait loop.
}
}
// Update the FlexClient last use time to prevent FlexClient from
// timing out when the client is still subscribed. It is important
// to do this outside synchronized(notifier.pushNeeded) to avoid
// thread deadlock!
flexClient.updateLastUse();
}
if (Log.isDebug())
log.debug("Streaming thread '" + threadName + "' for endpoint with id '" + getId() + "' is releasing connection and returning to the request handler pool.");
suppressIOExceptionLogging = true;
// Terminate the response.
streamChunk(null, os, res);
}
catch (IOException e)
{
if (Log.isWarn() && !suppressIOExceptionLogging)
log.warn("Streaming thread '" + threadName + "' for endpoint with id '" + getId() + "' is closing connection due to an IO error.", e);
}
finally
{
currentThread.setName(threadName);
// We're done so decrement the counts for streaming threads,
// and update the canStream flag if necessary.
synchronized (lock)
{
--streamingClientsCount;
canStream = (streamingClientsCount < maxStreamingClients);
synchronized (session)
{
--session.streamingConnectionsCount;
session.canStream = (session.streamingConnectionsCount < session.maxConnectionsPerSession);
}
}
if (notifier != null && currentStreamingRequests != null)
{
currentStreamingRequests.remove(notifier.getNotifierId());
notifier.close();
}
// Output session level streaming count.
if (Log.isDebug())
Log.getLogger(FlexSession.FLEX_SESSION_LOG_CATEGORY).info("Number of streaming clients for FlexSession with id '"+ session.getId() +"' is " + session.streamingConnectionsCount + ".");