blob: 6d574f0fa744b84b8374758d28059cb3404aa9ab [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.messaging.channels
{
import flash.events.Event;
import flash.events.HTTPStatusEvent;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.events.StatusEvent;
import flash.utils.ByteArray;
import mx.core.mx_internal;
import mx.logging.Log;
import mx.messaging.FlexClient;
import mx.messaging.config.ConfigMap;
import mx.messaging.config.ServerConfig;
import mx.messaging.events.ChannelFaultEvent;
import mx.messaging.events.MessageEvent;
import mx.messaging.messages.AbstractMessage;
import mx.messaging.messages.AsyncMessage;
import mx.messaging.messages.ErrorMessage;
import mx.messaging.messages.IMessage;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
use namespace mx_internal;
[ResourceBundle("messaging")]
/**
* The StreamingHTTPChannel class provides support for messaging and
* offers a different push model than the base HTTPChannel. Rather than
* polling for data from the server, the streaming channel opens an internal
* HTTP connection to the server that is held open to allow the server to
* stream data down to the client with no poll overhead.
*
* <p>
* Messages sent by this channel to the server are sent using a URLLoader
* which uses an HTTP connection internally for the duration of the operation.
* Once the message is sent and an acknowledgement or fault is returned the HTTP connection
* used by URLLoader is released by the channel. These client-to-server messages are
* not sent over the streaming HTTP connection that the channel holds open to receive
* server pushed data.
* </p>
*
* <p>
* Although this class extends the base HTTPChannel to inherit the regular HTTP
* handling, it does not support polling.
* </p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public class StreamingHTTPChannel extends HTTPChannel
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param id The id of this Channel.
*
* @param uri The uri for this Channel.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public function StreamingHTTPChannel(id:String = null, uri:String = null)
{
super(id, uri);
// Disable polling.
internalPollingEnabled = false;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Helper class used by the channel to establish a streaming HTTP connection
* with the server.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private var streamingConnectionHandler:StreamingConnectionHandler;
/**
* @private
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// pollingEnabled
//----------------------------------
/**
* @private
*/
override public function set pollingEnabled(value:Boolean):void
{
var message:String = resourceManager.getString(
"messaging", "pollingNotSupportedHTTP");
throw new Error(message);
}
//----------------------------------
// pollingInterval
//----------------------------------
/**
* @private
*/
override public function set pollingInterval(value:Number):void
{
var message:String = resourceManager.getString(
"messaging", "pollingNotSupportedHTTP");
throw new Error(message);
}
//----------------------------------
// realtime
//----------------------------------
/**
* @private
* Returns true since streaming channels are considered realtime.
*/
override mx_internal function get realtime():Boolean
{
return true;
}
//--------------------------------------------------------------------------
//
// Overridden Public Methods
//
//--------------------------------------------------------------------------
/**
* Polling is not supported by this channel.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
override public function poll():void
{
var message:String = resourceManager.getString(
"messaging", "pollingNotSupportedHTTP");
throw new Error(message);
}
//--------------------------------------------------------------------------
//
// Overriden Protected Methods
//
//--------------------------------------------------------------------------
/**
* @private
* Closes the streaming connection before redispatching the fault event.
*
* @param event The ChannelFaultEvent.
*/
override protected function connectFailed(event:ChannelFaultEvent):void
{
if (streamingConnectionHandler != null)
streamingConnectionHandler.closeStreamingConnection();
super.connectFailed(event);
}
/**
* @private
* Closes the streaming connection before disconnecting.
*/
override protected function internalDisconnect(rejected:Boolean = false):void
{
if (streamingConnectionHandler != null)
streamingConnectionHandler.closeStreamingConnection();
super.internalDisconnect(rejected);
}
/**
* @private
* This method will be called if the ping message sent to test connectivity
* to the server during the connection attempt succeeds.
* Before triggering connect success handling the streaming channel must set
* up its streaming connection with the server.
*/
override protected function internalPingComplete(msg:AsyncMessage):void
{
if (msg != null)
{
ServerConfig.updateServerConfigData(msg.body as ConfigMap, endpoint);
// Set the server assigned FlexClient Id.
if (FlexClient.getInstance().id == null && msg.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER] != null)
FlexClient.getInstance().id = msg.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER];
}
if (credentials != null && !(msg is ErrorMessage))
setAuthenticated(true);
if (streamingConnectionHandler == null)
{
streamingConnectionHandler = new StreamingHTTPConnectionHandler(this, _log);
streamingConnectionHandler.addEventListener(Event.OPEN, streamOpenHandler);
streamingConnectionHandler.addEventListener(Event.COMPLETE, streamCompleteHandler);
streamingConnectionHandler.addEventListener(HTTPStatusEvent.HTTP_STATUS, streamHttpStatusHandler);
streamingConnectionHandler.addEventListener(IOErrorEvent.IO_ERROR, streamIoErrorHandler);
streamingConnectionHandler.addEventListener(SecurityErrorEvent.SECURITY_ERROR, streamSecurityErrorHandler);
streamingConnectionHandler.addEventListener(StatusEvent.STATUS, streamStatusHandler);
}
streamingConnectionHandler.openStreamingConnection(appendToURL);
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
/**
* If the streaming connection receives an open event the channel is setup
* and gets ready for messaging.
*
* @param event The OPEN Event.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamOpenHandler(event:Event):void
{
connectSuccess();
}
/**
* A complete event indicates that the streaming connection has been closed by the server.
* This is a no-op if the channel is disconnected on the client, otherwise notifies the client
* channel that it has disconnected.
*
* @param event The COMPLETE Event.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamCompleteHandler(event:Event):void
{
if (connected) // The server has closed the connection but the client currently believes it should be connected.
{
// Dispatch a channel fault event.
var faultEvent:ChannelFaultEvent = ChannelFaultEvent.createEvent(this, false, "Channel.Stream.Failed", "error", "Remote endpoint has closed the streaming connection.");
faultEvent.rootCause = event;
dispatchEvent(faultEvent);
// And disconnect the channel.
internalDisconnect();
}
}
/**
* Handles HTTP status events dispatched by the streaming connection.
*
* @param event The HTTPStatusEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamHttpStatusHandler(event:HTTPStatusEvent):void
{
// No-op because most of the times, HTTP status is zero.
// See ioErrorHandler.
}
/**
* Handles IO error events dispatched by the streaming connection.
*
* @param event The IOErrorEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamIoErrorHandler(event:IOErrorEvent):void
{
var faultEvent:ChannelFaultEvent;
if (connected)
{
// Dispatch a channel fault event.
faultEvent = ChannelFaultEvent.createEvent(this, false, "Channel.Stream.Failed", "error", " url: '" + endpoint + "'");
faultEvent.rootCause = event;
dispatchEvent(faultEvent);
// And disconnect the channel.
internalDisconnect();
}
else
{
// Fault the current connect attempt.
faultEvent = ChannelFaultEvent.createEvent(this, false, "Channel.Stream.Failed", "error", " url: '" + endpoint + "'");
faultEvent.rootCause = event;
connectFailed(faultEvent);
}
}
/**
* Handles security error events dispatched by the streaming connection.
*
* @param event The SecurityErrorEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamSecurityErrorHandler(event:SecurityErrorEvent):void
{
// Just log as we'll never reach this handler because the prior ping will trigger
// a security error before we try to establish the streaming connection.
if (Log.isDebug())
_log.debug("'{0}' channel encountered a security error: {1}", id, event.text);
}
/**
* Handle status events dispatched by the streaming connection.
*
* @param event The StatusEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private function streamStatusHandler(event:StatusEvent):void
{
// Only a disconnect status event is currently handled.
if (event.code == StreamingConnectionHandler.DISCONNECT_CODE)
{
streamingConnectionHandler.closeStreamingConnection();
disconnectSuccess(true /* rejected */);
credentials = null;
}
}
}
}
//------------------------------------------------------------------------------
//
// Private Classes
//
//------------------------------------------------------------------------------
import flash.net.ObjectEncoding;
import flash.utils.ByteArray;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.messaging.Channel;
import mx.messaging.channels.StreamingConnectionHandler;
import mx.messaging.channels.amfx.AMFXDecoder;
import mx.messaging.channels.amfx.AMFXContext;
import mx.messaging.messages.IMessage;
/**
* A helper class that is used by the streaming channels to open an internal
* HTTP connection to the server that is held open to allow the server to
* stream data down to the client with no poll overhead.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
class StreamingHTTPConnectionHandler extends StreamingConnectionHandler
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param channel The Channel that uses this class.
* @param log Reference to the logger for the associated Channel.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public function StreamingHTTPConnectionHandler(channel:Channel, log:ILogger)
{
super(channel, log);
}
//--------------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------------
/**
* Used by the streamProgressHandler to read an AMFX encoded message.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
override protected function readMessage():IMessage
{
var message:IMessage;
chunkBuffer.position = dataOffset - 1;
var messageBytes:ByteArray = new ByteArray();
messageBytes.objectEncoding = ObjectEncoding.AMF3;
try
{
chunkBuffer.readBytes(messageBytes, 0, dataBytesToRead);
var messageString:String = messageBytes.readUTFBytes(dataBytesToRead);
var messageObject:Object = AMFXDecoder.decodeValue(XML(messageString), new AMFXContext());
message = IMessage(messageObject);
}
catch(error:Error)
{
if (Log.isError())
_log.error("'{0}' channel encountered an error while reading a message from the streaming connection: {1}", channel.id, error.message);
}
return message;
}
}