//////////////////////////////////////////////////////////////////////////////// // // 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. * *

* 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. *

* *

* Although this class extends the base HTTPChannel to inherit the regular HTTP * handling, it does not support polling. *

* * @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; } }