| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 org.apache.royale.events.Event; |
| |
| COMPILE::SWF |
| { |
| import flash.events.AsyncErrorEvent; |
| import flash.events.ErrorEvent; |
| import flash.events.IOErrorEvent; |
| import flash.events.NetStatusEvent; |
| import flash.events.SecurityErrorEvent; |
| import flash.net.NetConnection; |
| import flash.net.ObjectEncoding; |
| } |
| COMPILE::JS |
| { |
| import mx.events.AsyncErrorEvent; |
| import mx.events.ErrorEvent; |
| import mx.events.IOErrorEvent; |
| import mx.events.NetStatusEvent; |
| import mx.events.SecurityErrorEvent; |
| import mx.net.NetConnection; |
| import mx.net.ObjectEncoding; |
| } |
| |
| import mx.core.mx_internal; |
| import mx.logging.Log; |
| import mx.messaging.config.LoaderConfig; |
| import mx.messaging.MessageAgent; |
| import mx.messaging.MessageResponder; |
| import mx.messaging.events.ChannelEvent; |
| import mx.messaging.events.ChannelFaultEvent; |
| import mx.messaging.events.MessageEvent; |
| import mx.messaging.messages.AcknowledgeMessage; |
| import mx.messaging.messages.AsyncMessage; |
| import mx.messaging.messages.CommandMessage; |
| import mx.messaging.messages.ErrorMessage; |
| import mx.messaging.messages.IMessage; |
| import mx.messaging.messages.ISmallMessage; |
| import mx.messaging.messages.MessagePerformanceInfo; |
| import mx.messaging.messages.MessagePerformanceUtils; |
| import mx.netmon.NetworkMonitor; |
| import mx.utils.StringUtil; |
| |
| use namespace mx_internal; |
| |
| /** |
| * This NetConnectionChannel provides the basic NetConnection support for messaging. |
| * The AMFChannel and RTMPChannel both extend this class. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion BlazeDS 4 |
| * @productversion LCDS 3 |
| */ |
| public class NetConnectionChannel extends PollingChannel |
| { |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Creates a new NetConnectionChannel instance. |
| * <p> |
| * The underlying NetConnection's <code>objectEncoding</code> |
| * is set to <code>ObjectEncoding.AMF3</code> by default. It can be |
| * changed manually by accessing the channel's <code>netConnection</code> |
| * property. The global <code>NetConnection.defaultObjectEncoding</code> |
| * setting is not honored by this channel. |
| * </p> |
| * |
| * @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 NetConnectionChannel(id:String = null, uri:String = null) |
| { |
| super(id, uri); |
| |
| _nc = new NetConnection(); |
| _nc.objectEncoding = ObjectEncoding.AMF3; |
| _nc.client = this; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _appendToURL:String; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // netConnection |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| protected var _nc:NetConnection; |
| |
| /** |
| * Provides access to the associated NetConnection for this Channel. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion BlazeDS 4 |
| * @productversion LCDS 3 |
| */ |
| public function get netConnection():NetConnection |
| { |
| return _nc; |
| } |
| |
| //---------------------------------- |
| // useSmallmessages |
| //---------------------------------- |
| |
| /** |
| * @private |
| * If the ObjectEncoding is set to AMF0 we can't support small messages. |
| */ |
| override public function get useSmallMessages():Boolean |
| { |
| return (super.useSmallMessages && _nc != null && _nc.objectEncoding >= ObjectEncoding.AMF3); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Protected Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function connectTimeoutHandler(event:Event):void |
| { |
| shutdownNetConnection(); |
| super.connectTimeoutHandler(event); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getDefaultMessageResponder(agent:MessageAgent, msg:IMessage):MessageResponder |
| { |
| return new NetConnectionMessageResponder(agent, msg, this); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function internalDisconnect(rejected:Boolean = false):void |
| { |
| super.internalDisconnect(rejected); |
| shutdownNetConnection(); |
| disconnectSuccess(rejected); // make sure to notify everyone that we have disconnected. |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function internalConnect():void |
| { |
| super.internalConnect(); |
| var url:String = endpoint; |
| if (_appendToURL != null) |
| { |
| // WSRP support - append any extra stuff on the wsrp-url, not the actual url. |
| |
| // Do we have a wsrp-url? |
| var i:int = url.indexOf("wsrp-url="); |
| if (i != -1) |
| { |
| // Extract the wsrp-url in to a string which will get the |
| // extra info appended to it |
| var temp:String = url.substr(i + 9, url.length); |
| var j:int = temp.indexOf("&"); |
| if (j != -1) |
| { |
| temp = temp.substr(0, j); |
| } |
| |
| // Replace the wsrp-url with a version that has the extra stuff |
| url = url.replace(temp, temp + _appendToURL); |
| } |
| else |
| { |
| // If we didn't find a wsrp-url, just append the info |
| url += _appendToURL; |
| } |
| } |
| |
| // If the NetConnection has a non-null uri the Player will close() it automatically |
| // as part of its connect() processing below. Pre-emptively close the connection while suppressing |
| // NetStatus event handling to avoid spurious events. |
| if (_nc.uri != null && _nc.uri.length > 0 && _nc.connected) |
| { |
| _nc.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); |
| _nc.close(); |
| } |
| |
| // Propagate our requestTimeout for those platforms |
| // supporting the httpIdleTimeout property on NetConnection. |
| if ("httpIdleTimeout" in _nc && requestTimeout > 0) |
| _nc["httpIdleTimeout"] = requestTimeout * 1000; |
| |
| _nc.addEventListener(NetStatusEvent.NET_STATUS, statusHandler); |
| _nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); |
| _nc.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); |
| _nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); |
| |
| try |
| { |
| if (NetworkMonitor.isMonitoring()) |
| { |
| var redirectedUrl:String = NetworkMonitor.adjustNetConnectionURL(LoaderConfig.url, url); |
| if(redirectedUrl != null){ |
| url = redirectedUrl; |
| } |
| } |
| _nc.connect(url); |
| } |
| catch(e:Error) |
| { |
| // In some cases, this error does not have the URL in it (if it is a malformed |
| // URL for example) and in others it does (sandbox violation). Usually this is |
| // a URL problem, so add it all of the time even though this means we'll see it |
| // twice for the sandbox violation. |
| e.message += " url: '" + url + "'"; |
| throw e; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function internalSend(msgResp:MessageResponder):void |
| { |
| // Set the global RoyaleClient Id. |
| setRoyaleClientIdOnMessage(msgResp.message); |
| |
| // If MPI is enabled initialize MPI object and stamp it with client send time |
| if (mpiEnabled) |
| { |
| var mpii:MessagePerformanceInfo = new MessagePerformanceInfo(); |
| if (recordMessageTimes) |
| mpii.sendTime = new Date().getTime(); |
| msgResp.message.headers[MessagePerformanceUtils.MPI_HEADER_IN] = mpii; |
| } |
| |
| var message:IMessage = msgResp.message; |
| |
| // Finally, if "Small Messages" are enabled, send this form instead of |
| // the normal message where possible. |
| if (useSmallMessages && message is ISmallMessage) |
| { |
| var smallMessage:IMessage = ISmallMessage(message).getSmallMessage(); |
| if (smallMessage != null) |
| message = smallMessage; |
| } |
| |
| _nc.call(null, msgResp, message); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Special handler for legacy AMF packet level header "AppendToGatewayUrl". |
| * When we receive this header we assume the server detected that a session was |
| * created but it believed the client could not accept its session cookie, so we |
| * need to decorate the channel endpoint with the session id. |
| * |
| * We do not modify the underlying endpoint property, however, as this session |
| * is transient and should not apply if the channel is disconnected and re-connected |
| * at some point in the future. |
| */ |
| public function AppendToGatewayUrl(value:String):void |
| { |
| if (value != null && value != "" && value != _appendToURL) |
| { |
| if (Log.isDebug()) |
| _log.debug("'{0}' channel will disconnect and reconnect with with its session identifier '{1}' appended to its endpoint url \n", id, value); |
| _appendToURL = value; |
| } |
| } |
| |
| /** |
| * @private |
| * Called by the player when the server pushes a message. |
| * Dispatches a MessageEvent to any MessageAgents that are listening. |
| * Any ...rest args passed via RTMP are ignored. |
| * |
| * @param msg The message pushed from the server. |
| */ |
| public function receive(msg:IMessage, ...rest:Array):void |
| { |
| if (Log.isDebug()) |
| { |
| _log.debug("'{0}' channel got message\n{1}\n", id, msg.toString()); |
| |
| // If MPI is enabled write a performance summary to log |
| if (this.mpiEnabled) |
| { |
| try |
| { |
| var mpiutil:MessagePerformanceUtils = new MessagePerformanceUtils(msg); |
| _log.debug(mpiutil.prettyPrint()); |
| } |
| catch (e:Error) |
| { |
| _log.debug("Could not get message performance information for: " + msg.toString()); |
| } |
| } |
| } |
| |
| dispatchEvent(MessageEvent.createEvent(MessageEvent.MESSAGE, msg)); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Protected Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Utility method to close a NetConnection; includes retry to deal with instances |
| * that have generated a NetStatus error on the same frame and can't be closed immediately. |
| * |
| protected function closeNetConnection(nc:NetConnection):void |
| { |
| var closeHelper:CloseHelper = new CloseHelper(_nc); |
| closeHelper.close(); |
| } |
| */ |
| |
| /** |
| * @private |
| * Shuts down the underlying NetConnection for the channel. |
| */ |
| protected function shutdownNetConnection():void |
| { |
| _nc.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); |
| _nc.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); |
| _nc.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); |
| _nc.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); |
| _nc.close(); |
| } |
| |
| /** |
| * @private |
| * Called when a status event occurs on the NetConnection. |
| * Descendants must override this method. |
| */ |
| protected function statusHandler(event:NetStatusEvent):void |
| { |
| } |
| |
| /** |
| * @private |
| * If the player rejects a NetConnection request for security reasons, |
| * such as a security sandbox violation, the NetConnection raises a |
| * securityError event which we dispatch as a channel fault. |
| */ |
| protected function securityErrorHandler(event:SecurityErrorEvent):void |
| { |
| defaultErrorHandler("Channel.Security.Error", event); |
| } |
| |
| /** |
| * @private |
| * If there is a network problem, the NetConnection raises an |
| * ioError event which we dispatch as a channel fault. |
| */ |
| protected function ioErrorHandler(event:IOErrorEvent):void |
| { |
| defaultErrorHandler("Channel.IO.Error", event); |
| } |
| |
| /** |
| * @private |
| * If a problem arises in the native player code asynchronously, this |
| * error event will be dispatched as a channel fault. |
| */ |
| protected function asyncErrorHandler(event:AsyncErrorEvent):void |
| { |
| defaultErrorHandler("Channel.Async.Error", event); |
| } |
| |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Private Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Utility function to dispatch a ChannelFaultEvent at an "error" level |
| * based upon the passed code and ErrorEvent. |
| */ |
| private function defaultErrorHandler(code:String, event:ErrorEvent):void |
| { |
| var faultEvent:ChannelFaultEvent = ChannelFaultEvent.createEvent |
| (this, false, code, "error", event.text + " url: '" + endpoint + "'"); |
| faultEvent.rootCause = event; |
| |
| if (_connecting) |
| connectFailed(faultEvent); |
| else |
| dispatchEvent(faultEvent); |
| } |
| |
| } |
| |
| } |
| |
| //------------------------------------------------------------------------------ |
| // |
| // Private Classes |
| // |
| //------------------------------------------------------------------------------ |
| |
| import org.apache.royale.utils.Timer; |
| import org.apache.royale.events.Event; |
| COMPILE::SWF |
| { |
| import flash.net.NetConnection; |
| } |
| COMPILE::JS |
| { |
| import mx.net.NetConnection; |
| } |
| |
| import mx.core.mx_internal; |
| import mx.messaging.channels.NetConnectionChannel; |
| import mx.messaging.events.ChannelEvent; |
| import mx.messaging.events.ChannelFaultEvent; |
| import mx.messaging.MessageAgent; |
| import mx.messaging.MessageResponder; |
| import mx.messaging.messages.IMessage; |
| import mx.messaging.messages.AcknowledgeMessage; |
| import mx.messaging.messages.AsyncMessage; |
| import mx.messaging.messages.CommandMessage; |
| import mx.messaging.messages.ErrorMessage; |
| import mx.resources.IResourceManager; |
| import mx.resources.ResourceManager; |
| import mx.utils.StringUtil; |
| |
| use namespace mx_internal; |
| |
| //[ResourceBundle("messaging")] |
| |
| /** |
| * @private |
| * This class provides the responder level interface for dispatching message |
| * results from a remote destination. |
| * The NetConnectionChannel creates this handler to manage |
| * the results of a pending operation started when a message is sent. |
| * The message handler is always associated with a MessageAgent |
| * (the object that sent the message) and calls its <code>fault()</code>, |
| * <code>acknowledge()</code>, or <code>message()</code> method as appopriate. |
| */ |
| class NetConnectionMessageResponder extends MessageResponder |
| { |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Initializes this instance of the message responder with the specified |
| * agent. |
| * |
| * @param agent MessageAgent that this responder should call back when a |
| * message is received. |
| * |
| * @param msg The outbound message. |
| * |
| * @param channel The channel this responder is using. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion BlazeDS 4 |
| * @productversion LCDS 3 |
| */ |
| public function NetConnectionMessageResponder(agent:MessageAgent, |
| msg:IMessage, channel:NetConnectionChannel) |
| { |
| super(agent, msg, channel); |
| channel.addEventListener(ChannelEvent.DISCONNECT, channelDisconnectHandler); |
| channel.addEventListener(ChannelFaultEvent.FAULT, channelFaultHandler); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var handled:Boolean; |
| |
| /** |
| * @private |
| */ |
| private var resourceManager:IResourceManager = ResourceManager.getInstance(); |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Called when the result of sending a message is received. |
| * |
| * @param msg NetConnectionChannel-specific message data. |
| */ |
| override protected function resultHandler(msg:IMessage):void |
| { |
| if (handled) |
| return; |
| |
| var errorMsg:ErrorMessage; |
| |
| disconnect(); |
| if (msg is AsyncMessage) |
| { |
| if (AsyncMessage(msg).correlationId == message.messageId) |
| { |
| agent.acknowledge(msg as AcknowledgeMessage, message); |
| } |
| else |
| { |
| errorMsg = new ErrorMessage(); |
| errorMsg.faultCode = "Server.Acknowledge.Failed"; |
| errorMsg.faultString = resourceManager.getString( |
| "messaging", "ackFailed"); |
| errorMsg.faultDetail = resourceManager.getString( |
| "messaging", "ackFailed.details", |
| [ message.messageId, AsyncMessage(msg).correlationId ]); |
| errorMsg.correlationId = message.messageId; |
| agent.fault(errorMsg, message); |
| //@TODO: need to add constants here |
| } |
| } |
| else |
| { |
| errorMsg = new ErrorMessage(); |
| errorMsg.faultCode = "Server.Acknowledge.Failed"; |
| errorMsg.faultString = resourceManager.getString( |
| "messaging", "noAckMessage"); |
| errorMsg.faultDetail = resourceManager.getString( |
| "messaging", "noAckMessage.details", |
| [ msg ? msg.toString() : "null" ]); |
| errorMsg.correlationId = message.messageId; |
| agent.fault(errorMsg, message); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the current invocation fails. |
| * Passes the fault information on to the associated agent that made |
| * the request. |
| * |
| * @param msg NetConnectionMessageResponder status information. |
| */ |
| override protected function statusHandler(msg:IMessage):void |
| { |
| if (handled) |
| return; |
| |
| var errorMsg:ErrorMessage; |
| |
| disconnect(); |
| |
| // even a fault is still an acknowledgement of a message sent so pass it on... |
| if (msg is AsyncMessage) |
| { |
| if (AsyncMessage(msg).correlationId == message.messageId) |
| { |
| // pass the ack on... |
| var ack:AcknowledgeMessage = new AcknowledgeMessage(); |
| ack.correlationId = AsyncMessage(msg).correlationId; |
| ack.headers[AcknowledgeMessage.ERROR_HINT_HEADER] = true; // add a hint this is for an error |
| agent.acknowledge(ack, message); |
| // send the fault on... |
| agent.fault(msg as ErrorMessage, message); |
| } |
| else if (msg is ErrorMessage) |
| { |
| // we can't find a correlation id but do have some sort of error message so just forward |
| agent.fault(msg as ErrorMessage, message); |
| } |
| else |
| { |
| errorMsg = new ErrorMessage(); |
| errorMsg.faultCode = "Server.Acknowledge.Failed"; |
| errorMsg.faultString = resourceManager.getString( |
| "messaging", "noErrorForMessage"); |
| errorMsg.faultDetail = resourceManager.getString( |
| "messaging", "noErrorForMessage.details", |
| [ message.messageId, AsyncMessage(msg).correlationId ]); |
| errorMsg.correlationId = message.messageId; |
| agent.fault(errorMsg, message); |
| } |
| } |
| else |
| { |
| errorMsg = new ErrorMessage(); |
| errorMsg.faultCode = "Server.Acknowledge.Failed"; |
| errorMsg.faultString = resourceManager.getString( |
| "messaging", "noAckMessage"); |
| errorMsg.faultDetail = resourceManager.getString( |
| "messaging", "noAckMessage.details", |
| [ msg ? msg.toString(): "null" ]); |
| errorMsg.correlationId = message.messageId; |
| agent.fault(errorMsg, message); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Protected Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Handle a request timeout by removing ourselves as a listener on the |
| * NetConnection and faulting the message to the agent. |
| */ |
| override protected function requestTimedOut():void |
| { |
| statusHandler(createRequestTimeoutErrorMessage()); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Protected Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Handles a disconnect of the underlying Channel before a response is |
| * returned to the responder. |
| * The sent message is faulted and flagged with the ErrorMessage.MESSAGE_DELIVERY_IN_DOUBT |
| * code. |
| * |
| * @param event The DISCONNECT event. |
| */ |
| protected function channelDisconnectHandler(event:ChannelEvent):void |
| { |
| if (handled) |
| return; |
| |
| disconnect(); |
| var errorMsg:ErrorMessage = new ErrorMessage(); |
| errorMsg.correlationId = message.messageId; |
| errorMsg.faultString = resourceManager.getString( |
| "messaging", "deliveryInDoubt"); |
| errorMsg.faultDetail = resourceManager.getString |
| ("messaging", "deliveryInDoubt.details"); |
| errorMsg.faultCode = ErrorMessage.MESSAGE_DELIVERY_IN_DOUBT; |
| errorMsg.rootCause = event; |
| agent.fault(errorMsg, message); |
| } |
| |
| /** |
| * @private |
| * Handles a fault of the underlying Channel before a response is |
| * returned to the responder. |
| * The sent message is faulted and flagged with the ErrorMessage.MESSAGE_DELIVERY_IN_DOUBT |
| * code. |
| * |
| * @param event The ChannelFaultEvent. |
| */ |
| protected function channelFaultHandler(event:ChannelFaultEvent):void |
| { |
| if (handled) |
| return; |
| |
| disconnect(); |
| var errorMsg:ErrorMessage = event.createErrorMessage(); |
| errorMsg.correlationId = message.messageId; |
| // if the channel is no longer connected then we don't really |
| // know if the message made it to the server. |
| if (!event.channel.connected) |
| { |
| errorMsg.faultCode = ErrorMessage.MESSAGE_DELIVERY_IN_DOUBT; |
| } |
| errorMsg.rootCause = event; |
| agent.fault(errorMsg, message); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Private Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Disconnects the responder from the underlying Channel. |
| */ |
| private function disconnect():void |
| { |
| handled = true; |
| channel.removeEventListener(ChannelEvent.DISCONNECT, channelDisconnectHandler); |
| channel.removeEventListener(ChannelFaultEvent.FAULT, channelFaultHandler); |
| } |
| } |