public void CanMeasureTimeSpentInSteps() { var stats = new PipelineStepProfilerStats(); var pipeline = new DefaultPipeline() .OnReceive(new Step300()) .OnReceive(new Step100()) .OnReceive(new Step200()); var profiler = new PipelineStepProfiler(pipeline, stats); var receivePipeline = profiler.ReceivePipeline(); var invoker = new DefaultPipelineInvoker(); var transportMessage = new TransportMessage(new Dictionary<string, string>(), new byte[0]); using (new DefaultTransactionContextScope()) { var stepContext = new IncomingStepContext(transportMessage, AmbientTransactionContext.Current); invoker.Invoke(stepContext, receivePipeline).Wait(); var stepStats = stats.GetStats(); Console.WriteLine(string.Join(Environment.NewLine, stepStats)); } }
public async Task Process(IncomingStepContext context, Func<Task> next) { var message = context.Load<Message>(); string correlationId; var hasCorrelationId = message.Headers.TryGetValue(Headers.CorrelationId, out correlationId); if (hasCorrelationId) { var isRequestReplyCorrelationId = correlationId.StartsWith(SpecialCorrelationIdPrefix); if (isRequestReplyCorrelationId) { string dummy; var isRequest = message.Headers.TryGetValue(SpecialRequestTag, out dummy); if (!isRequest) { // it's the reply! _messages[correlationId] = message; return; } } } await next(); }
void PossiblyDecompressTransportMessage(IncomingStepContext context) { var transportMessage = context.Load<TransportMessage>(); string contentEncoding; if (!transportMessage.Headers.TryGetValue(Headers.ContentEncoding, out contentEncoding)) return; if (contentEncoding != ZipMessagesOutgoingStep.GzipEncodingHeader) { var message = $"The message {transportMessage.GetMessageLabel()} has a '{Headers.ContentEncoding}' with the" + $" value '{contentEncoding}', but this middleware only knows how to decompress" + $" '{ZipMessagesOutgoingStep.GzipEncodingHeader}'"; throw new ArgumentException(message); } var headers = transportMessage.Headers.Clone(); var compressedBody = transportMessage.Body; headers.Remove(Headers.ContentEncoding); var body = _zipper.Unzip(compressedBody); context.Save(new TransportMessage(headers, body)); }
public async Task Process(IncomingStepContext context, Func<Task> next) { await next(); var message = context.Load<Message>(); var handlerInvokers = context.Load<HandlerInvokers>(); var createdAndUpdatedSagaData = handlerInvokers .Where(i => i.HasSaga) .Select(i => new { Handler = i.Handler, SagaData = i.GetSagaData() }) .ToList(); var saveTasks = createdAndUpdatedSagaData .Select(sagaData => { var metadata = GetMetadata(sagaData.SagaData, sagaData.Handler, message); return _sagaSnapshotStorage.Save(sagaData.SagaData, metadata); }); await Task.WhenAll(saveTasks); }
/// <summary> /// Invokes the routing function and performs some action depending on the returned <see cref="ForwardAction"/> result /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); var routingResult = (await _routingFunction(transportMessage)) ?? ForwardAction.None; var actionType = routingResult.ActionType; switch (actionType) { case ActionType.Forward: var destinationAddresses = routingResult.DestinationAddresses; var transactionContext = context.Load<ITransactionContext>(); _log.Debug("Forwarding {0} to {1}", transportMessage.GetMessageLabel(), string.Join(", ", destinationAddresses)); await Task.WhenAll( destinationAddresses .Select(address => _transport.Send(address, transportMessage, transactionContext)) ); break; case ActionType.None: await next(); break; default: await next(); break; } }
public async Task Process(IncomingStepContext context, Func<Task> next) { var message = context.Load<Message>(); var headers = message.Headers; if (headers.ContainsKey(MapLegacyHeadersIncomingStep.LegacyMessageHeader)) { var body = message.Body; var array = body as object[]; if (array == null) { throw new FormatException( $"Incoming message has the '{MapLegacyHeadersIncomingStep.LegacyMessageHeader}' header, but the message body {body} is not an object[] as expected"); } foreach (var bodyToDispatch in array) { var messageBodyToDispatch = PossiblyConvertBody(bodyToDispatch, headers); context.Save(new Message(headers, messageBodyToDispatch)); await next(); } return; } await next(); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var handlerInvokersForSagas = context.Load<HandlerInvokers>() .Where(l => l.HasSaga) .ToList(); var message = context.Load<Message>(); var messageId = message.GetMessageId(); var transactionContext = context.Load<ITransactionContext>(); foreach (var handlerInvoker in handlerInvokersForSagas) { var sagaData = handlerInvoker.GetSagaData() as IIdempotentSagaData; if (sagaData == null) continue; var idempotencyData = sagaData.IdempotencyData ?? (sagaData.IdempotencyData = new IdempotencyData()); if (idempotencyData.HasAlreadyHandled(messageId)) { _log.Info("Message with ID {0} has already been handled by saga with ID {1}", messageId, sagaData.Id); var outgoingMessages = idempotencyData .GetOutgoingMessages(messageId) .ToList(); if (outgoingMessages.Any()) { _log.Info("Found {0} outgoing messages to be (re-)sent... will do that now", outgoingMessages.Count); foreach (var messageToResend in outgoingMessages) { foreach (var destinationAddress in messageToResend.DestinationAddresses) { await _transport.Send(destinationAddress, messageToResend.TransportMessage, transactionContext); } } } else { _log.Info("Found no outgoing messages to be (re-)sent..."); } handlerInvoker.SkipInvocation(); } else { idempotencyData.MarkMessageAsHandled(messageId); } } await next(); }
public async Task Process(IncomingStepContext context, Func<Task> next) { using (var scope = new TransactionScope(ScopeOption, _transactionOptions, AsyncFlowOption)) { await next(); scope.Complete(); } }
public async Task Process(IncomingStepContext context, Func<Task> next) { GetActionList(context).Add(string.Format("enter {0}", _name)); await next(); GetActionList(context).Add(string.Format("leave {0}", _name)); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var statsContext = context.Load<StatsContext>(); using (statsContext.Measure(_nextStep)) { await next(); } }
void MutateLegacyTransportMessage(IncomingStepContext context, Dictionary<string, string> headers, TransportMessage transportMessage) { var newHeaders = MapTrivialHeaders(headers); MapSpecialHeaders(newHeaders); newHeaders[LegacyMessageHeader] = ""; context.Save(new TransportMessage(newHeaders, transportMessage.Body)); }
/// <summary> /// Reorders the handler invokers if necessary /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var handlerInvokers = context.Load<HandlerInvokers>(); var orderedHandlerInvokers = handlerInvokers.OrderBy(i => _configuration.GetIndex(i.Handler)); var newHandlerInvokers = new HandlerInvokers(handlerInvokers.Message, orderedHandlerInvokers); context.Save(newHandlerInvokers); await next(); }
/// <summary> /// Carries out whichever logic it takes to do something good for the incoming message :) /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var message = context.Load<Message>(); var newPrincipal = GetClaimsPrincipalOrNull(message); using (new CurrentPrincipalRewriter(newPrincipal)) { await next(); } }
public async Task Process(IncomingStepContext context, Func<Task> next) { var statsContext = new StatsContext(); // save stats context for all the ProfilerSteps to find context.Save(statsContext); await next(); _profilerStats.Register(statsContext); }
/// <summary> /// Executes the entire message processing pipeline in an exception handler, tracking the number of failed delivery attempts. /// Forwards the message to the error queue when the max number of delivery attempts has been exceeded. /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); var transactionContext = context.Load<ITransactionContext>(); var messageId = transportMessage.Headers.GetValueOrNull(Headers.MessageId); if (string.IsNullOrWhiteSpace(messageId)) { await MoveMessageToErrorQueue("<no message ID>", transportMessage, transactionContext, string.Format("Received message with empty or absent '{0}' header! All messages must be" + " supplied with an ID . If no ID is present, the message cannot be tracked" + " between delivery attempts, and other stuff would also be much harder to" + " do - therefore, it is a requirement that messages be supplied with an ID.", Headers.MessageId), shortErrorDescription: string.Format("Received message with empty or absent '{0}' header", Headers.MessageId)); return; } if (_errorTracker.HasFailedTooManyTimes(messageId)) { // if we don't have 2nd level retries, just get the message out of the way if (!_simpleRetryStrategySettings.SecondLevelRetriesEnabled) { await MoveMessageToErrorQueue(messageId, transportMessage, transactionContext, GetErrorDescriptionFor(messageId), GetErrorDescriptionFor(messageId, brief: true)); _errorTracker.CleanUp(messageId); return; } // change the identifier to track by to perform this 2nd level of delivery attempts var secondLevelMessageId = messageId + "-2nd-level"; if (_errorTracker.HasFailedTooManyTimes(secondLevelMessageId)) { await MoveMessageToErrorQueue(messageId, transportMessage, transactionContext, GetErrorDescriptionFor(messageId), GetErrorDescriptionFor(messageId, brief: true)); _errorTracker.CleanUp(messageId); _errorTracker.CleanUp(secondLevelMessageId); return; } context.Save(DispatchAsFailedMessageKey, true); await DispatchWithTrackerIdentifier(next, secondLevelMessageId, transactionContext); return; } await DispatchWithTrackerIdentifier(next, messageId, transactionContext); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var transactionContext = context.Load<ITransactionContext>(); var transportMessage = context.Load<TransportMessage>(); var clone = transportMessage.Clone(); clone.Headers[Headers.AuditTime] = RebusTime.Now.ToString("O"); await _transport.Send(_auditQueue, clone, transactionContext); await next(); }
public async Task CanRestoreIdentity() { var step = new RestorePrincipalFromIncomingMessage(new DummySerializer()); var instance = new Message(new Dictionary<string, string>(), new object()); var context = new IncomingStepContext(new TransportMessage(new Dictionary<string, string>(), new byte[0] ), new DefaultTransactionContext() ); instance.Headers[CapturePrincipalInOutgoingMessage.PrincipalCaptureKey] = "Larry"; context.Save(instance); await step.Process(context, async () => { Assert.AreEqual(ClaimsPrincipal.Current.Identity.Name, "Larry"); }); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); var headers = transportMessage.Headers; if (headers.ContainsKey("rebus-msg-id")) { MutateLegacyTransportMessage(context, headers, transportMessage); } await next(); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var resetEvents = DequeueResetEvents(); context.Load<ITransactionContext>() .OnDisposed(() => { resetEvents.ForEach(resetEvent => resetEvent.Set()); }); await next(); }
/// <summary> /// For each <see cref="HandlerInvoker"/> found in the current <see cref="IncomingStepContext"/>'s <see cref="HandlerInvokers"/>, /// this step will see if the invoker's handler is actually a <see cref="Saga"/>. If that is the case, the saga's correlation properties /// are used to see if a piece of existing saga data can be retrieved and mounted on the <see cref="Saga{TSagaData}.Data"/> property. /// If no existing instance was found, but the saga implements <see cref="IAmInitiatedBy{TMessage}"/> for the current message, /// a new saga data instance will be created (and mounted). Otherwise, the message is ignored. /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { // first we get the relevant handler invokers var handlerInvokersForSagas = context.Load<HandlerInvokers>() .Where(l => l.HasSaga) .ToList(); // maybe short-circuit? this makes it slightly faster if (!handlerInvokersForSagas.Any()) { await next(); return; } var message = context.Load<Message>(); var label = message.GetMessageLabel(); var body = message.Body; // keep track of saga data instances in these two lists var loadedSagaData = new List<RelevantSagaInfo>(); var newlyCreatedSagaData = new List<RelevantSagaInfo>(); // and then we process them foreach (var sagaInvoker in handlerInvokersForSagas) { await TryMountSagaDataOnInvoker(sagaInvoker, body, label, loadedSagaData, newlyCreatedSagaData); } // invoke the rest of the pipeline (most likely also dispatching the incoming message to the now-ready saga handlers) await next(); // everything went well - let's divide saga data instances into those to insert, update, and delete var newlyCreatedSagaDataToSave = newlyCreatedSagaData.Where(s => !s.Saga.WasMarkedAsComplete && !s.Saga.WasMarkedAsUnchanged); var loadedSagaDataToUpdate = loadedSagaData.Where(s => !s.Saga.WasMarkedAsComplete && !s.Saga.WasMarkedAsUnchanged); var loadedSagaDataToDelete = loadedSagaData.Where(s => s.Saga.WasMarkedAsComplete); foreach (var sagaDataToInsert in newlyCreatedSagaDataToSave) { await SaveSagaData(sagaDataToInsert, insert: true); } foreach (var sagaDataToUpdate in loadedSagaDataToUpdate) { await SaveSagaData(sagaDataToUpdate, insert: false); } foreach (var sagaDataToUpdate in loadedSagaDataToDelete) { await _sagaStorage.Delete(sagaDataToUpdate.SagaData); } }
/// <summary> /// Invokes the pipeline of <see cref="IIncomingStep"/> steps, passing the given <see cref="IncomingStepContext"/> to each step as it is invoked /// </summary> public Task Invoke(IncomingStepContext context, IEnumerable <IIncomingStep> pipeline) { var receivePipeline = GetPipeline(pipeline); var step = TerminationStep; for (var index = receivePipeline.Length - 1; index >= 0; index--) { var nextStep = step; var stepToInvoke = receivePipeline[index]; step = () => stepToInvoke.Process(context, nextStep); } return(step()); }
/// <summary> /// Invokes the pipeline of <see cref="IIncomingStep"/> steps, passing the given <see cref="IncomingStepContext"/> to each step as it is invoked /// </summary> public async Task Invoke(IncomingStepContext context, IEnumerable <IIncomingStep> pipeline) { var receivePipeline = pipeline.ToArray(); var step = TerminationStep; for (var index = receivePipeline.Length - 1; index >= 0; index--) { var nextStep = step; var stepToInvoke = receivePipeline[index]; step = () => stepToInvoke.Process(context, nextStep); } await step(); }
/// <summary> /// Invokes the pipeline of <see cref="IIncomingStep"/> steps, passing the given <see cref="IncomingStepContext"/> to each step as it is invoked /// </summary> public async Task Invoke(IncomingStepContext context, IEnumerable<IIncomingStep> pipeline) { var receivePipeline = pipeline.ToArray(); var step = TerminationStep; for (var index = receivePipeline.Length - 1; index >= 0; index--) { var nextStep = step; var stepToInvoke = receivePipeline[index]; step = () => stepToInvoke.Process(context, nextStep); } await step(); }
public async Task InvokesInOrder() { var invoker = new DefaultPipelineInvoker(); var stepContext = new IncomingStepContext(new TransportMessage(new Dictionary<string, string>(), new byte[0]), null); await invoker.Invoke(stepContext, new IIncomingStep[] { new NamedStep("first"), new NamedStep("second"), new NamedStep("third"), }); Console.WriteLine(string.Join(Environment.NewLine, stepContext.Load<List<string>>())); }
public async Task Process(IncomingStepContext context, Func<Task> next) { var begin = RebusTime.Now; await next(); var transactionContext = context.Load<ITransactionContext>(); var transportMessage = context.Load<TransportMessage>(); var clone = transportMessage.Clone(); _auditingHelper.SetCommonHeaders(clone); clone.Headers[AuditHeaders.HandleTime] = begin.ToString("O"); await _transport.Send(_auditingHelper.AuditQueue, clone, transactionContext); }
/// <summary> /// Descrypts the incoming <see cref="TransportMessage"/> if it has the <see cref="EncryptionHeaders.ContentEncryption"/> header /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); if (transportMessage.Headers.ContainsKey(EncryptionHeaders.ContentEncryption)) { var headers = transportMessage.Headers.Clone(); var encryptedBodyBytes = transportMessage.Body; var iv = GetIv(headers); var bodyBytes = _encryptor.Decrypt(encryptedBodyBytes, iv); context.Save(new TransportMessage(headers, bodyBytes)); } await next(); }
/// <summary> /// Executes the entire message processing pipeline in an exception handler, tracking the number of failed delivery attempts. /// Forwards the message to the error queue when the max number of delivery attempts has been exceeded. /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); var transactionContext = context.Load<ITransactionContext>(); var messageId = transportMessage.Headers.GetValueOrNull(Headers.MessageId); if (string.IsNullOrWhiteSpace(messageId)) { await MoveMessageToErrorQueue("<no message ID>", transportMessage, transactionContext, string.Format("Received message with empty or absent '{0}' header! All messages must be" + " supplied with an ID . If no ID is present, the message cannot be tracked" + " between delivery attempts, and other stuff would also be much harder to" + " do - therefore, it is a requirement that messages be supplied with an ID.", Headers.MessageId), shortErrorDescription: "Received message with empty or absent 'rbs2-msg-id' header"); return; } if (HasFailedTooManyTimes(messageId)) { await MoveMessageToErrorQueue(messageId, transportMessage, transactionContext, GetErrorDescriptionFor(messageId), GetErrorDescriptionFor(messageId, brief: true)); RemoveErrorTracking(messageId); return; } try { await next(); } catch (Exception exception) { var errorTracking = _trackedErrors.AddOrUpdate(messageId, id => new ErrorTracking(exception), (id, tracking) => tracking.AddError(exception)); _log.Warn("Unhandled exception {0} while handling message with ID {1}: {2}", errorTracking.Errors.Count(), messageId, exception); transactionContext.Abort(); } }
/// <summary> /// Descrypts the incoming <see cref="TransportMessage"/> if it has the <see cref="EncryptionHeaders.ContentEncryption"/> header /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); string contentEncryptionValue; if (transportMessage.Headers.TryGetValue(EncryptionHeaders.ContentEncryption, out contentEncryptionValue) && contentEncryptionValue == _encryptor.ContentEncryptionValue) { var headers = transportMessage.Headers.Clone(); var encryptedBodyBytes = transportMessage.Body; var iv = GetIv(headers); var bodyBytes = _encryptor.Decrypt(new EncryptedData(encryptedBodyBytes, iv)); context.Save(new TransportMessage(headers, bodyBytes)); } await next(); }
public void CheckTiming() { var invoker = new DefaultPipelineInvoker(); var stopwatch = Stopwatch.StartNew(); 1000000.Times(() => { var stepContext = new IncomingStepContext(new TransportMessage(new Dictionary<string, string>(), new byte[0]), GetFakeTransactionContext()); var pipeline = Enumerable.Range(0, 15) .Select(stepNumber => new NamedStep($"step {stepNumber}")) .ToArray(); invoker.Invoke(stepContext, pipeline).Wait(); }); Console.WriteLine("Execution took {0:0.0} s", stopwatch.Elapsed.TotalSeconds); }
/// <summary> /// Executes the entire message processing pipeline in an exception handler, tracking the number of failed delivery attempts. /// Forwards the message to the error queue when the max number of delivery attempts has been exceeded. /// </summary> public async Task Process(IncomingStepContext context, Func<Task> next) { var transportMessage = context.Load<TransportMessage>(); var transactionContext = context.Load<ITransactionContext>(); var messageId = transportMessage.Headers.GetValueOrNull(Headers.MessageId); if (string.IsNullOrWhiteSpace(messageId)) { await MoveMessageToErrorQueue("<no message ID>", transportMessage, transactionContext, string.Format("Received message with empty or absent '{0}' header! All messages must be" + " supplied with an ID . If no ID is present, the message cannot be tracked" + " between delivery attempts, and other stuff would also be much harder to" + " do - therefore, it is a requirement that messages be supplied with an ID.", Headers.MessageId), shortErrorDescription: "Received message with empty or absent 'rbs2-msg-id' header"); return; } if (_errorTracker.HasFailedTooManyTimes(messageId)) { await MoveMessageToErrorQueue(messageId, transportMessage, transactionContext, GetErrorDescriptionFor(messageId), GetErrorDescriptionFor(messageId, brief: true)); _errorTracker.CleanUp(messageId); return; } try { await next(); } catch (Exception exception) { _errorTracker.RegisterError(messageId, exception); transactionContext.Abort(); } }
public async Task Process(IncomingStepContext context, Func<Task> next) { GetActionList(context).Add($"enter {_name}"); await next(); GetActionList(context).Add($"leave {_name}"); }
public async Task Process(IncomingStepContext context, Func<Task> next) { }