/// <summary> /// Sends a SetTransferRequest message to a specified ServiceSocket /// </summary> /// <param name="connectedMsg">AsyncMessageTracker holding connected socket</param> /// <param name="Service">The state server instance</param> /// <param name="Resource">The URI associated with the message</param> /// <param name="SessionInfo">The Session information used to populate fields in the message</param> /// <param name="Data">The message data</param> /// <param name="SuccessAction">The Action to call if the message was accepted</param> /// <param name="FailAction">The Action to call if the message transmission failed or was refused</param> /// <param name="AlreadyExistsAction">The Action to call if the recipient peer already has the URI</param> /// <param name="PeerShuttingDownAction">The Action to call if the recipient peer is shutting down</param> /// <param name="TimeoutAction">The Action to call if the transfer timed out. This Action's processing time should be very short because a long list of Timeout actions can be daisy-chained and called one after the other</param> /// <param name="Timeout">The timeout time span</param> public static void Send(AsyncMessageTracker connectedMsg, StateServer Service, string Resource, ISessionResponseInfo SessionInfo, byte[] Data, Action<AsyncMessageTracker> SuccessAction, Action<AsyncMessageTracker> FailAction, Action<AsyncMessageTracker> AlreadyExistsAction, Action<AsyncMessageTracker> PeerShuttingDownAction, System.Threading.WaitCallback TimeoutAction, TimeSpan Timeout) { StringBuilder headers = new StringBuilder(); headers.AppendFormat("PUT {0} HTTP/1.1\r\nHost: {1}\r\nX-ID: {2}\r\n", Resource, Service.ServerIP ?? connectedMsg.Socket.LocalIP, connectedMsg.ID.ToString("N")); if (SessionInfo.LockDateInTicks != DateTime.MinValue.Ticks) { headers.AppendFormat("LockDate: {0}\r\nLockAge: {1}\r\nLockCookie: {2}\r\n", SessionInfo.LockDateInTicks, SessionInfo.LockAgeInSeconds, SessionInfo.LockCookie); } headers.AppendFormat("X-If-None-Exists: true\r\nExclusive: transfer\r\nTimeout: {0}\r\nExtraFlags: {1}\r\nLast-Modified: {2}\r\n", SessionInfo.Timeout, SessionInfo.ActionFlags, SessionInfo.UpdateDateInTicks); ResponseData rdata = new ResponseData( MergeResponseData(headers, Data, true, Service.Settings.EncryptPeerData, Service.Authenticator, connectedMsg.Socket.SessionKey), typeof(SetTransferRequest) ); //Create new AsyncResultActions object to hold delegates for actions based on the outcome of the call AsyncResultActions<AsyncMessageTracker> asyncResults = new AsyncResultActions<AsyncMessageTracker>(connectedMsg); asyncResults.Result1Action = SuccessAction; asyncResults.Result2Action = FailAction; asyncResults.Result3Action = AlreadyExistsAction; asyncResults.Result4Action = PeerShuttingDownAction; asyncResults.TimeoutAction = TimeoutAction; Service.AsyncMessageRequests.Add(DateTime.UtcNow + Timeout, connectedMsg.ID, asyncResults); connectedMsg.Socket.Send(rdata); Diags.LogSend(connectedMsg.Socket, rdata); }
/// <summary> /// Ends a failed Session Transfer /// </summary> /// <param name="transferredMessage">The AsyncMessageTracker representing the transfer.</param> private void TransferFailure(AsyncMessageTracker transferredMessage) { List<AsyncResultActions<string>> calls = service.RemoveActiveExport(Resource); service.SessionTable.EndExport(Resource, false); if (transferredMessage.Socket.IsConnected) { Diags.LogDisconnectingPeer(transferredMessage.Socket.RemoteIP); transferredMessage.Socket.Close(); } if (calls != null) CallExportEndedActions(calls); }
/// <summary> /// Ends a successful session transfer /// </summary> /// <param name="transferredMessage">The AsyncMessageTracker representing the transfer.</param> private void TransferSuccess(AsyncMessageTracker transferredMessage) { List<AsyncResultActions<string>> calls = service.RemoveActiveExport(Resource); service.SessionTable.EndExport(Resource, true); if (calls != null) CallExportEndedActions(calls); }
/// <summary> /// Called by the SessionDictionary.BeginExport method to complete processing the request, /// if the requested session was found and read /// </summary> /// <param name="Session">The read session</param> /// <param name="StateObject">The state object passed from the SessionDictionary.BeginExport method</param> private void CompleteTransferRequest(ISessionObject Session, object StateObject) { ISessionResponseInfo response = Session.CreateResponseInfo(); //Get the endpoint for the host to connect to string remoteHost; int? remotePort; ServerSettings.HostEndPoint.Parse(Host,out remoteHost,out remotePort); if(!remotePort.HasValue) remotePort = service.Settings.PeerPort; ServerSettings.HostEndPoint endPoint = new ServerSettings.HostEndPoint(remoteHost.Trim(),remotePort.Value); const int sentTransferExpiryTime = 2; // 2 seconds is sufficient for a broadcast to traverse half a reasonable network #region Callback Delegate declarations //SuccessAction Action<AsyncMessageTracker> successAction = delegate(AsyncMessageTracker asyncMsg) { //Add this transfer to list of recently transferred sessions and have it expire in 15 seconds service.SentTransfers.Add(DateTime.UtcNow + new TimeSpan(0, 0, sentTransferExpiryTime), Resource, null); TransferSuccess(asyncMsg); Diags.LogTransferSuccess(Resource); }; //FailedAction Action<AsyncMessageTracker> failureAction = delegate(AsyncMessageTracker asyncMsg) { TransferFailure(asyncMsg); Diags.LogTransferFailed(Resource, string.Empty); }; //AlreadyExistsAction Action<AsyncMessageTracker> alreadyExistsAction = delegate(AsyncMessageTracker asyncMsg) { //Add this transfer to list of recently transferred sessions and have it expire in 15 seconds service.SentTransfers.Add(DateTime.UtcNow + new TimeSpan(0, 0, sentTransferExpiryTime), Resource, null); TransferSuccess(asyncMsg); Diags.LogTransferFailed(Resource, "Resource already exists in remote peer -- deleted local copy"); }; //PeerShuttingDownAction Action<AsyncMessageTracker> peerShuttingDownAction = delegate(AsyncMessageTracker asyncMsg) { TransferFailure(asyncMsg); Diags.LogTransferFailed(Resource, "Peer is shutting down"); }; //TimeoutAction System.Threading.WaitCallback timeoutAction = delegate(object asyncMsg) { //This anonymous method can be called directly from a background thread so make sure it's exception-safe try { TransferFailure((AsyncMessageTracker)asyncMsg); Diags.LogTransferFailed(Resource, "Timed out"); } catch (Exception ex) { Diags.LogApplicationError( "TimeoutAction delegate error in CompleteTransferRequest", ex); } }; //FailedActionForExistingLink Action<AsyncMessageTracker> failureActionForExistingLink = delegate(AsyncMessageTracker asyncMsg) { Diags.LogTransferFailed(Resource, "Existing link, trying again on new link"); //Try again by reconnecting service.TransferSession(endPoint, Resource, response, Session.Data, successAction, failureAction, alreadyExistsAction, peerShuttingDownAction, timeoutAction); }; #endregion service.NewActiveExport(Resource); //create an entry in the exports list for this export //Look for standing link ServiceSocket linkSocket; if(service.TryGetLinkTo(endPoint, out linkSocket)) { //There is a standing link to this peer so send transfer through it AsyncMessageTracker msg = new AsyncMessageTracker(AsyncMessageOperation.SetTransferOperation, Guid.NewGuid(), linkSocket); service.TransferSession(msg, Resource, response, Session.Data, successAction, failureActionForExistingLink, alreadyExistsAction, peerShuttingDownAction, timeoutAction); } else { //Initiate a brand new connection service.TransferSession(endPoint, Resource, response, Session.Data, successAction,failureAction,alreadyExistsAction,peerShuttingDownAction,timeoutAction); } }