A work item represents the act of firing a single WebHook with one or more notifications.
示例#1
0
        internal static HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem, ILogger logger)
        {
            WebHook hook = workItem.WebHook;

            // Create WebHook request
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri);

            // Fill in request body based on WebHook and work item data
            JObject body = CreateWebHookRequestBody(workItem);

            SignWebHookRequest(hook, request, body);

            // Add extra request or entity headers
            foreach (var kvp in hook.Headers)
            {
                if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                {
                    if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                    {
                        string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id);
                        logger.Error(msg);
                    }
                }
            }

            return(request);
        }
示例#2
0
 public WebHookWorkItemTests()
 {
     _notification = new NotificationDictionary("action", data: null);
     _notifications = new List<NotificationDictionary> { _notification };
     _webHook = new WebHook();
     _workItem = new WebHookWorkItem(_webHook, _notifications);
 }
示例#3
0
        /// <summary>
        /// Adds a SHA 256 signature to the <paramref name="body"/> and adds it to the <paramref name="request"/> as an
        /// HTTP header to the <see cref="HttpRequestMessage"/> along with the entity body.
        /// </summary>
        /// <param name="workItem">The current <see cref="WebHookWorkItem"/>.</param>
        /// <param name="request">The request to add the signature to.</param>
        /// <param name="body">The body to sign and add to the request.</param>
        protected virtual void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }
            if (workItem.WebHook == null)
            {
                string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Sender_BadWorkItem, this.GetType().Name, "WebHook");
                throw new ArgumentException(msg, "workItem");
            }
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }

            byte[] secret = Encoding.UTF8.GetBytes(workItem.WebHook.Secret);
            using (var hasher = new HMACSHA256(secret))
            {
                string serializedBody = body.ToString();
                request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json");

                byte[] data        = Encoding.UTF8.GetBytes(serializedBody);
                byte[] sha256      = hasher.ComputeHash(data);
                string headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, EncodingUtilities.ToHex(sha256));
                request.Headers.Add(SignatureHeaderName, headerValue);
            }
        }
示例#4
0
        /// <summary>
        /// Creates a <see cref="JObject"/> used as the <see cref="HttpRequestMessage"/> entity body for a <see cref="WebHook"/>.
        /// </summary>
        /// <param name="workItem">The <see cref="WebHookWorkItem"/> representing the data to be sent.</param>
        /// <returns>An initialized <see cref="JObject"/>.</returns>
        protected virtual JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            Dictionary <string, object> body = new Dictionary <string, object>();

            // Set properties from work item
            body[BodyIdKey]      = workItem.Id;
            body[BodyAttemptKey] = workItem.Offset + 1;

            // Set properties from WebHook
            IDictionary <string, object> properties = workItem.WebHook.Properties;

            if (properties != null)
            {
                body[BodyPropertiesKey] = new Dictionary <string, object>(properties);
            }

            // Set notifications
            body[BodyNotificationsKey] = workItem.Notifications;

            return(JObject.FromObject(body));
        }
        public async Task NotifyAsync_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func <HttpRequestMessage, int, Task <HttpResponseMessage> > handler, int expected)
        {
            // Arrange
            ManualResetEvent done = new ManualResetEvent(initialState: false);
            WebHookWorkItem  success = null, failure = null;

            _manager = new WebHookManager(_storeMock.Object, _loggerMock.Object, delays, _options, _httpClient, onWebHookSuccess: item =>
            {
                success = item;
                done.Set();
            }, onWebHookFailure: item =>
            {
                failure = item;
                done.Set();
            });
            _handlerMock.Handler = handler;
            NotificationDictionary notification = new NotificationDictionary("a1", data: null);

            // Act
            int actual = await _manager.NotifyAsync(TestUser, new[] { notification });

            done.WaitOne();

            // Assert
            _storeMock.Verify();
            if (expected >= 0)
            {
                Assert.Equal(expected, success.Offset);
            }
            else
            {
                Assert.Equal(Math.Abs(expected), failure.Offset);
            }
        }
示例#6
0
        protected virtual HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            WebHook hook = workItem.WebHook;

            // Create WebHook request
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri);

            // Fill in request body based on WebHook and work item data
            JObject body = CreateWebHookRequestBody(workItem);

            SignWebHookRequest(workItem, request, body);

            // Add extra request or entity headers
            foreach (var kvp in hook.Headers)
            {
                if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                {
                    if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                    {
                        string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id);
                        _logger.Error(msg);
                    }
                }
            }

            return(request);
        }
示例#7
0
        internal static IEnumerable <WebHookWorkItem> GetWorkItems(ICollection <WebHook> webHooks, ICollection <NotificationDictionary> notifications)
        {
            List <WebHookWorkItem> workItems = new List <WebHookWorkItem>();

            foreach (WebHook webHook in webHooks)
            {
                ICollection <NotificationDictionary> webHookNotifications;

                // Pick the notifications that apply for this particular WebHook. If we only got one notification
                // then we know that it applies to all WebHooks. Otherwise each notification may apply only to a subset.
                if (notifications.Count == 1)
                {
                    webHookNotifications = notifications;
                }
                else
                {
                    webHookNotifications = notifications.Where(n => webHook.MatchesAction(n.Action)).ToArray();
                    if (webHookNotifications.Count == 0)
                    {
                        continue;
                    }
                }

                WebHookWorkItem workItem = new WebHookWorkItem(webHook, webHookNotifications);
                workItems.Add(workItem);
            }
            return(workItems);
        }
        public void CreateWebHookRequest_CreatesExpectedRequest()
        {
            // Arrange
            WebHookWorkItem workItem = CreateWorkItem();

            workItem.WebHook.Headers.Add("Content-Language", "da");
            _sender = new WebHookSenderMock(_loggerMock.Object);

            // Act
            HttpRequestMessage actual = _sender.CreateWebHookRequest(workItem);

            // Assert
            Assert.Equal(HttpMethod.Post, actual.Method);
            Assert.Equal(workItem.WebHook.WebHookUri, actual.RequestUri);

            IEnumerable <string> headers;

            actual.Headers.TryGetValues("h1", out headers);
            Assert.Equal("hv1", headers.Single());

            actual.Headers.TryGetValues("ms-signature", out headers);
            Assert.Equal(WebHookSignature, headers.Single());

            Assert.Equal("da", actual.Content.Headers.ContentLanguage.Single());
            Assert.Equal("application/json; charset=utf-8", actual.Content.Headers.ContentType.ToString());
        }
 protected override Task OnWebHookRetry(WebHookWorkItem workItem)
 {
     if (_onRetry != null)
     {
         _onRetry(workItem);
     }
     return(Task.FromResult(true));
 }
 protected override Task OnWebHookSuccess(WebHookWorkItem workItem)
 {
     if (_onSuccess != null)
     {
         _onSuccess(workItem);
     }
     return(Task.FromResult(true));
 }
 protected override Task OnWebHookFailure(WebHookWorkItem workItem)
 {
     if (_onFailure != null)
     {
         _onFailure(workItem);
     }
     return(Task.FromResult(true));
 }
示例#12
0
 public WebHookWorkItemTests()
 {
     _notification  = new NotificationDictionary("action", data: null);
     _notifications = new List <NotificationDictionary> {
         _notification
     };
     _webHook  = new WebHook();
     _workItem = new WebHookWorkItem(_webHook, _notifications);
 }
            /// <inheritdoc />
            public override async Task SendWebHookWorkItemsAsync(IEnumerable <WebHookWorkItem> workItems)
            {
                if (workItems == null)
                {
                    throw new ArgumentNullException(nameof(workItems));
                }

                // Keep track of which queued messages should be deleted because processing has completed.
                List <CloudQueueMessage> deleteMessages = new List <CloudQueueMessage>();

                // Submit WebHook requests in parallel
                List <Task <HttpResponseMessage> > requestTasks = new List <Task <HttpResponseMessage> >();

                foreach (var workItem in workItems)
                {
                    HttpRequestMessage request = CreateWebHookRequest(workItem);
                    request.Properties[WorkItemKey] = workItem;

                    try
                    {
                        Task <HttpResponseMessage> requestTask = _parent._httpClient.SendAsync(request);
                        requestTasks.Add(requestTask);
                    }
                    catch (Exception ex)
                    {
                        string msg = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_SendFailure, request.RequestUri, ex.Message);
                        Logger.Info(msg);

                        CloudQueueMessage message = GetMessage(workItem);
                        if (DiscardMessage(workItem, message))
                        {
                            deleteMessages.Add(message);
                        }
                    }
                }

                // Wait for all responses and see which messages should be deleted from the queue based on the response statuses.
                HttpResponseMessage[] responses = await Task.WhenAll(requestTasks);

                foreach (HttpResponseMessage response in responses)
                {
                    WebHookWorkItem workItem = response.RequestMessage.Properties.GetValueOrDefault <WebHookWorkItem>(WorkItemKey);
                    string          msg      = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_WebHookStatus, workItem.WebHook.Id, response.StatusCode, workItem.Offset);
                    Logger.Info(msg);

                    // If success or 'gone' HTTP status code then we remove the message from the Azure queue.
                    // If error then we leave it in the queue to be consumed once it becomes visible again or we give up
                    CloudQueueMessage message = GetMessage(workItem);
                    if (response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.Gone || DiscardMessage(workItem, message))
                    {
                        deleteMessages.Add(message);
                    }
                }

                // Delete successfully delivered messages and messages that have been attempted delivered too many times.
                await _parent._storageManager.DeleteMessagesAsync(_parent._queue, deleteMessages);
            }
        /// <summary>
        /// Launch a <see cref="WebHook"/>.
        /// </summary>
        /// <remarks>We don't let exceptions propagate out from this method as it is used by the launchers
        /// and if they see an exception they shut down.</remarks>
        private async Task LaunchWebHook(WebHookWorkItem workItem)
        {
            try
            {
                // Setting up and send WebHook request
                var request  = CreateWebHookRequest(workItem);
                var response = await _httpClient.SendAsync(request);

                var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_Result, workItem.WebHook.Id, response.StatusCode, workItem.Offset);
                Logger.Info(message);

                if (response.IsSuccessStatusCode)
                {
                    // If we get a successful response then we are done.
                    await OnWebHookSuccess(workItem);

                    return;
                }
                else if (response.StatusCode == HttpStatusCode.Gone)
                {
                    // If we get a 410 Gone then we are also done.
                    await OnWebHookGone(workItem);

                    return;
                }
            }
            catch (Exception ex)
            {
                var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_WebHookFailure, workItem.Offset, workItem.WebHook.Id, ex.Message);
                Logger.Error(message, ex);
            }

            try
            {
                // See if we should retry the request with delay or give up
                workItem.Offset++;
                if (workItem.Offset < _launchers.Length)
                {
                    // If we are to retry then we submit the request again after a delay.
                    await OnWebHookRetry(workItem);

                    _launchers[workItem.Offset].Post(workItem);
                }
                else
                {
                    var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_GivingUp, workItem.WebHook.Id, workItem.Offset);
                    Logger.Error(message);
                    await OnWebHookFailure(workItem);
                }
            }
            catch (Exception ex)
            {
                var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_WebHookFailure, workItem.Offset, workItem.WebHook.Id, ex.Message);
                Logger.Error(message, ex);
            }
        }
        /// <summary>
        /// Dequeues available WebHooks and sends them out to each WebHook recipient.
        /// </summary>
        protected virtual async Task DequeueAndSendWebHooks(CancellationToken cancellationToken)
        {
            bool isEmpty = false;

            while (true)
            {
                try
                {
                    do
                    {
                        if (cancellationToken.IsCancellationRequested)
                        {
                            return;
                        }

                        // Dequeue messages from Azure queue
                        IEnumerable <CloudQueueMessage> messages = await _storageManager.GetMessagesAsync(_queue, MaxDequeuedMessages, _messageTimeout);

                        // Extract the work items
                        ICollection <WebHookWorkItem> workItems = messages.Select(m =>
                        {
                            WebHookWorkItem workItem             = JsonConvert.DeserializeObject <WebHookWorkItem>(m.AsString, _serializerSettings);
                            workItem.Properties[QueueMessageKey] = m;
                            return(workItem);
                        }).ToArray();

                        if (cancellationToken.IsCancellationRequested)
                        {
                            return;
                        }

                        // Submit work items to be sent to WebHook receivers
                        if (workItems.Count > 0)
                        {
                            await _sender.SendWebHookWorkItemsAsync(workItems);
                        }
                        isEmpty = workItems.Count == 0;
                    }while (!isEmpty);
                }
                catch (Exception ex)
                {
                    string msg = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_ErrorDequeueing, _queue.Name, ex.Message);
                    _logger.Error(msg, ex);
                }

                try
                {
                    await Task.Delay(_pollingFrequency, cancellationToken);
                }
                catch (OperationCanceledException oex)
                {
                    _logger.Error(oex.Message, oex);
                    return;
                }
            }
        }
 private bool DiscardMessage(WebHookWorkItem workItem, CloudQueueMessage message)
 {
     if (message.DequeueCount >= _parent._maxAttempts)
     {
         string error = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_GivingUp, workItem.WebHook.Id, message.DequeueCount);
         Logger.Error(error);
         return(true);
     }
     return(false);
 }
        public void CreateWebHookRequestBody_CreatesExpectedBody()
        {
            // Arrange
            WebHookWorkItem workItem = CreateWorkItem();

            // Act
            JObject actual = WebHookManager.CreateWebHookRequestBody(workItem);

            // Assert
            Assert.Equal(SerializedWebHook, actual.ToString());
        }
            private QueueMessage GetMessage(WebHookWorkItem workItem)
            {
                var queueMessage = workItem?.Properties.GetValueOrDefault <QueueMessage>(QueueMessageKey);

                if (queueMessage == null)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_NoProperty, QueueMessageKey, workItem?.Id);
                    Logger.Error(message);
                    throw new InvalidOperationException(message);
                }
                return(queueMessage);
            }
            private CloudQueueMessage GetMessage(WebHookWorkItem workItem)
            {
                CloudQueueMessage message = workItem != null?workItem.Properties.GetValueOrDefault <CloudQueueMessage>(QueueMessageKey) : null;

                if (message == null)
                {
                    string msg = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_NoProperty, QueueMessageKey, workItem.Id);
                    Logger.Error(msg);
                    throw new InvalidOperationException(msg);
                }
                return(message);
            }
        public void CreateWebHookRequestBody_CreatesExpectedBody()
        {
            // Arrange
            WebHookWorkItem workItem = CreateWorkItem();

            _sender = new WebHookSenderMock(_loggerMock.Object);

            // Act
            JObject actual = _sender.CreateWebHookRequestBody(workItem);

            // Assert
            Assert.Equal(SerializedWebHook, actual.ToString());
        }
        public async Task SendWebHook_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func <HttpRequestMessage, int, Task <HttpResponseMessage> > handler, int expectedOffset, SendResult expectedResult)
        {
            // Arrange
            var             actualResult  = SendResult.None;
            var             done          = new ManualResetEvent(initialState: false);
            WebHookWorkItem final         = null;
            var             actualRetries = 0;

            _sender = new TestDataflowWebHookSender(_loggerMock.Object, delays, _options, _httpClient,
                                                    onWebHookRetry: item =>
            {
                actualRetries++;
            },
                                                    onWebHookSuccess: item =>
            {
                final        = item;
                actualResult = SendResult.Success;
                done.Set();
            },
                                                    onWebHookGone: item =>
            {
                final        = item;
                actualResult = SendResult.Gone;
                done.Set();
            },
                                                    onWebHookFailure: item =>
            {
                final        = item;
                actualResult = SendResult.Failure;
                done.Set();
            });
            _handlerMock.Handler = handler;
            var notification = new NotificationDictionary("a1", data: null);
            var webHook      = WebHookManagerTests.CreateWebHook();
            var workItem     = new WebHookWorkItem(webHook, new[] { notification })
            {
                Id = "1234567890",
            };

            // Act
            await _sender.SendWebHookWorkItemsAsync(new[] { workItem });

            done.WaitOne();

            // Assert
            var expectedRetries = expectedResult == SendResult.Failure ? Math.Max(0, expectedOffset - 1) : expectedOffset;

            Assert.Equal(expectedRetries, actualRetries);
            Assert.Equal(expectedResult, actualResult);
            Assert.Equal(expectedOffset, final.Offset);
        }
        private static WebHookWorkItem CreateWorkItem()
        {
            WebHook webHook = WebHookManagerTests.CreateWebHook();
            NotificationDictionary notification1 = new NotificationDictionary("a1", new { d1 = "dv1" });
            NotificationDictionary notification2 = new NotificationDictionary("a1", new Dictionary <string, object> {
                { "d2", new Uri("http://localhost") }
            });
            WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification1, notification2 })
            {
                Id = "1234567890",
            };

            return(workItem);
        }
        public async Task SendWebHookWorkItems_ThrowsException_IfInvalidWorkItem()
        {
            // Arrange
            WebHook webHook = new WebHook();
            NotificationDictionary notification = new NotificationDictionary();
            notification.Add("k", new SerializationFailure());
            WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification });
            IEnumerable<WebHookWorkItem> workItems = new[] { workItem };

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _sender.SendWebHookWorkItemsAsync(workItems));

            // Assert
            Assert.Equal("Could not serialize message: Error getting value from 'Fail' on 'Microsoft.AspNet.WebHooks.AzureWebHookSenderTests+SerializationFailure'.", ex.Message);
        }
示例#24
0
        public async Task SendWebHookWorkItems_ThrowsException_IfInvalidWorkItem()
        {
            // Arrange
            WebHook webHook = new WebHook();
            NotificationDictionary notification = new NotificationDictionary();

            notification.Add("k", new SerializationFailure());
            WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification });
            IEnumerable <WebHookWorkItem> workItems = new[] { workItem };

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _sender.SendWebHookWorkItemsAsync(workItems));

            // Assert
            Assert.Equal("Could not serialize message: Error getting value from 'Fail' on 'Microsoft.AspNet.WebHooks.AzureWebHookSenderTests+SerializationFailure'.", ex.Message);
        }
        public void SignWebHookRequest_HandlesNullWebHook()
        {
            WebHookWorkItem    workItem = CreateWorkItem();
            HttpRequestMessage request  = new HttpRequestMessage();

            _sender = new WebHookSenderMock(_loggerMock.Object);
            JObject body = _sender.CreateWebHookRequestBody(workItem);

            workItem.WebHook = null;

            // Act
            ArgumentException ex = Assert.Throws <ArgumentException>(() => _sender.SignWebHookRequest(workItem, request, body));

            // Assert
            Assert.StartsWith("Invalid 'WebHookSenderMock' instance: 'WebHook' cannot be null.", ex.Message);
        }
示例#26
0
        internal static JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
        {
            Dictionary <string, object> body = new Dictionary <string, object>();

            // Set properties from work item
            body[BodyIdKey]      = workItem.Id;
            body[BodyAttemptKey] = workItem.Offset + 1;

            // Set properties from WebHook
            IDictionary <string, object> properties = workItem.WebHook.Properties;

            if (properties != null)
            {
                body[BodyPropertiesKey] = new Dictionary <string, object>(properties);
            }

            // Set notifications
            body[BodyNotificationsKey] = workItem.Notifications;

            return(JObject.FromObject(body));
        }
        public async Task SignWebHookRequest_SignsBodyCorrectly()
        {
            // Arrange
            WebHookWorkItem    workItem = CreateWorkItem();
            HttpRequestMessage request  = new HttpRequestMessage();
            JObject            body     = WebHookManager.CreateWebHookRequestBody(workItem);

            // Act
            WebHookManager.SignWebHookRequest(workItem.WebHook, request, body);

            // Assert
            IEnumerable <string> signature;

            request.Headers.TryGetValues("ms-signature", out signature);
            Assert.Equal(WebHookSignature, signature.Single());

            string requestBody = await request.Content.ReadAsStringAsync();

            Assert.Equal(SerializedWebHook, requestBody);

            Assert.Equal("application/json; charset=utf-8", request.Content.Headers.ContentType.ToString());
        }
        public async Task SendWebHook_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func <HttpRequestMessage, int, Task <HttpResponseMessage> > handler, int expected)
        {
            // Arrange
            ManualResetEvent done = new ManualResetEvent(initialState: false);
            WebHookWorkItem  success = null, failure = null;

            _sender = new DataflowWebHookSender(_loggerMock.Object, delays, _options, _httpClient, onWebHookSuccess: item =>
            {
                success = item;
                done.Set();
            }, onWebHookFailure: item =>
            {
                failure = item;
                done.Set();
            });
            _handlerMock.Handler = handler;
            NotificationDictionary notification = new NotificationDictionary("a1", data: null);
            WebHook         webHook             = WebHookManagerTests.CreateWebHook();
            WebHookWorkItem workItem            = new WebHookWorkItem(webHook, new[] { notification })
            {
                Id = "1234567890",
            };

            // Act
            await _sender.SendWebHookWorkItemsAsync(new[] { workItem });

            done.WaitOne();

            // Assert
            if (expected >= 0)
            {
                Assert.Equal(expected, success.Offset);
            }
            else
            {
                Assert.Equal(Math.Abs(expected), failure.Offset);
            }
        }
示例#29
0
 /// <summary>
 /// If delivery of a WebHook results in a 410 Gone HTTP status code, then <see cref="OnWebHookGone"/>
 /// is called enabling additional post-processing.
 /// </summary>
 /// <param name="workItem">The current <see cref="WebHookWorkItem"/>.</param>
 protected virtual Task OnWebHookGone(WebHookWorkItem workItem)
 {
     return(Task.FromResult(true));
 }
示例#30
0
 private static WebHookWorkItem CreateWorkItem()
 {
     WebHook webHook = WebHookManagerTests.CreateWebHook();
     NotificationDictionary notification1 = new NotificationDictionary("a1", new { d1 = "dv1" });
     NotificationDictionary notification2 = new NotificationDictionary("a1", new Dictionary<string, object> { { "d2", new Uri("http://localhost") } });
     WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification1, notification2 })
     {
         Id = "1234567890",
     };
     return workItem;
 }
示例#31
0
 public new void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body)
 {
     base.SignWebHookRequest(workItem, request, body);
 }
 protected override Task OnWebHookFailure(WebHookWorkItem workItem)
 {
     _onFailure?.Invoke(workItem);
     return(Task.FromResult(true));
 }
示例#33
0
        protected virtual HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            WebHook hook = workItem.WebHook;

            // Create WebHook request
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri);

            // Fill in request body based on WebHook and work item data
            JObject body = CreateWebHookRequestBody(workItem);
            SignWebHookRequest(workItem, request, body);

            // Add extra request or entity headers
            foreach (var kvp in hook.Headers)
            {
                if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                {
                    if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                    {
                        string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id);
                        _logger.Error(msg);
                    }
                }
            }

            return request;
        }
 protected override Task OnWebHookSuccess(WebHookWorkItem workItem)
 {
     if (_onSuccess != null)
     {
         _onSuccess(workItem);
     }
     return Task.FromResult(true);
 }
示例#35
0
 public new JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
 {
     return base.CreateWebHookRequestBody(workItem);
 }
示例#36
0
 /// <summary>
 /// By overriding this method you can control just the WebHook header containing a signature of the 
 /// body.
 /// </summary>
 protected override void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body)
 {
     base.SignWebHookRequest(workItem, request, body);
 }
示例#37
0
        internal static JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
        {
            Dictionary<string, object> body = new Dictionary<string, object>();

            // Set properties from work item
            body[BodyIdKey] = workItem.Id;
            body[BodyAttemptKey] = workItem.Offset + 1;

            // Set properties from WebHook
            IDictionary<string, object> properties = workItem.WebHook.Properties;
            if (properties != null)
            {
                body[BodyPropertiesKey] = new Dictionary<string, object>(properties);
            }

            // Set notifications
            body[BodyNotificationsKey] = workItem.Notifications;

            return JObject.FromObject(body);
        }
        public async Task SendWebHook_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func<HttpRequestMessage, int, Task<HttpResponseMessage>> handler, int expected)
        {
            // Arrange
            ManualResetEvent done = new ManualResetEvent(initialState: false);
            WebHookWorkItem success = null, failure = null;
            _sender = new DataflowWebHookSender(_loggerMock.Object, delays, _options, _httpClient, onWebHookSuccess: item =>
            {
                success = item;
                done.Set();
            }, onWebHookFailure: item =>
            {
                failure = item;
                done.Set();
            });
            _handlerMock.Handler = handler;
            NotificationDictionary notification = new NotificationDictionary("a1", data: null);
            WebHook webHook = WebHookManagerTests.CreateWebHook();
            WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification })
            {
                Id = "1234567890",
            };

            // Act
            await _sender.SendWebHookWorkItemsAsync(new[] { workItem });
            done.WaitOne();

            // Assert
            if (expected >= 0)
            {
                Assert.Equal(expected, success.Offset);
            }
            else
            {
                Assert.Equal(Math.Abs(expected), failure.Offset);
            }
        }
示例#39
0
        /// <summary>
        /// Creates a <see cref="JObject"/> used as the <see cref="HttpRequestMessage"/> entity body for a <see cref="WebHook"/>.
        /// </summary>
        /// <param name="workItem">The <see cref="WebHookWorkItem"/> representing the data to be sent.</param>
        /// <returns>An initialized <see cref="JObject"/>.</returns>
        protected virtual JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            Dictionary<string, object> body = new Dictionary<string, object>();

            // Set properties from work item
            body[BodyIdKey] = workItem.Id;
            body[BodyAttemptKey] = workItem.Offset + 1;

            // Set properties from WebHook
            IDictionary<string, object> properties = workItem.WebHook.Properties;
            if (properties != null)
            {
                body[BodyPropertiesKey] = new Dictionary<string, object>(properties);
            }

            // Set notifications
            body[BodyNotificationsKey] = workItem.Notifications;

            return JObject.FromObject(body);
        }
示例#40
0
        private async Task DelayedLaunchWebHook(WebHookWorkItem item, TimeSpan delay)
        {
            await Task.Delay(delay);

            await LaunchWebHook(item);
        }
示例#41
0
 /// <summary>
 /// If delivery of a WebHook results in a 410 Gone HTTP status code, then <see cref="OnWebHookGone"/> 
 /// is called enabling additional post-processing. 
 /// </summary>
 /// <param name="workItem">The current <see cref="WebHookWorkItem"/>.</param>
 protected virtual Task OnWebHookGone(WebHookWorkItem workItem)
 {
     return Task.FromResult(true);
 }
 protected override Task OnWebHookFailure(WebHookWorkItem workItem)
 {
     if (_onFailure != null)
     {
         _onFailure(workItem);
     }
     return Task.FromResult(true);
 }
 private CloudQueueMessage GetMessage(WebHookWorkItem workItem)
 {
     CloudQueueMessage message = workItem != null ? workItem.Properties.GetValueOrDefault<CloudQueueMessage>(QueueMessageKey) : null;
     if (message == null)
     {
         string msg = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_NoProperty, QueueMessageKey, workItem.Id);
         Logger.Error(msg);
         throw new InvalidOperationException(msg);
     }
     return message;
 }
示例#44
0
 public new HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem)
 {
     return base.CreateWebHookRequest(workItem);
 }
示例#45
0
 private async Task DelayedLaunchWebHook(WebHookWorkItem item, TimeSpan delay)
 {
     await Task.Delay(delay);
     await LaunchWebHook(item);
 }
        public async Task SendWebHook_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func<HttpRequestMessage, int, Task<HttpResponseMessage>> handler, int expectedOffset, SendResult expectedResult)
        {
            // Arrange
            SendResult actualResult = SendResult.None;
            ManualResetEvent done = new ManualResetEvent(initialState: false);
            WebHookWorkItem final = null;
            int actualRetries = 0;
            _sender = new TestDataflowWebHookSender(_loggerMock.Object, delays, _options, _httpClient,
            onWebHookRetry: item =>
            {
                actualRetries++;
            },
            onWebHookSuccess: item =>
            {
                final = item;
                actualResult = SendResult.Success;
                done.Set();
            },
            onWebHookGone: item =>
            {
                final = item;
                actualResult = SendResult.Gone;
                done.Set();
            },
            onWebHookFailure: item =>
            {
                final = item;
                actualResult = SendResult.Failure;
                done.Set();
            });
            _handlerMock.Handler = handler;
            NotificationDictionary notification = new NotificationDictionary("a1", data: null);
            WebHook webHook = WebHookManagerTests.CreateWebHook();
            WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification })
            {
                Id = "1234567890",
            };

            // Act
            await _sender.SendWebHookWorkItemsAsync(new[] { workItem });
            done.WaitOne();

            // Assert
            int expectedRetries = expectedResult == SendResult.Failure ? Math.Max(0, expectedOffset - 1) : expectedOffset;
            Assert.Equal(expectedRetries, actualRetries);
            Assert.Equal(expectedResult, actualResult);
            Assert.Equal(expectedOffset, final.Offset);
        }
 protected override Task OnWebHookSuccess(WebHookWorkItem workItem)
 {
     _onSuccess?.Invoke(workItem);
     return(Task.FromResult(true));
 }
 private bool DiscardMessage(WebHookWorkItem workItem, CloudQueueMessage message)
 {
     if (message.DequeueCount >= _parent._maxAttempts)
     {
         string error = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.DequeueManager_GivingUp, workItem.WebHook.Id, message.DequeueCount);
         Logger.Error(error);
         return true;
     }
     return false;
 }
示例#49
0
        /// <summary>
        /// Adds a SHA 256 signature to the <paramref name="body"/> and adds it to the <paramref name="request"/> as an 
        /// HTTP header to the <see cref="HttpRequestMessage"/> along with the entity body.
        /// </summary>
        /// <param name="workItem">The current <see cref="WebHookWorkItem"/>.</param>
        /// <param name="request">The request to add the signature to.</param>
        /// <param name="body">The body to sign and add to the request.</param>
        protected virtual void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }
            if (workItem.WebHook == null)
            {
                string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Sender_BadWorkItem, this.GetType().Name, "WebHook");
                throw new ArgumentException(msg, "workItem");
            }
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }

            byte[] secret = Encoding.UTF8.GetBytes(workItem.WebHook.Secret);
            using (var hasher = new HMACSHA256(secret))
            {
                string serializedBody = body.ToString();
                request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json");

                byte[] data = Encoding.UTF8.GetBytes(serializedBody);
                byte[] sha256 = hasher.ComputeHash(data);
                string headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, EncodingUtilities.ToHex(sha256));
                request.Headers.Add(SignatureHeaderName, headerValue);
            }
        }
示例#50
0
        internal static IEnumerable<WebHookWorkItem> GetWorkItems(ICollection<WebHook> webHooks, ICollection<NotificationDictionary> notifications)
        {
            List<WebHookWorkItem> workItems = new List<WebHookWorkItem>();
            foreach (WebHook webHook in webHooks)
            {
                ICollection<NotificationDictionary> webHookNotifications;

                // Pick the notifications that apply for this particular WebHook. If we only got one notification
                // then we know that it applies to all WebHooks. Otherwise each notification may apply only to a subset.
                if (notifications.Count == 1)
                {
                    webHookNotifications = notifications;
                }
                else
                {
                    webHookNotifications = notifications.Where(n => webHook.MatchesAction(n.Action)).ToArray();
                    if (webHookNotifications.Count == 0)
                    {
                        continue;
                    }
                }

                WebHookWorkItem workItem = new WebHookWorkItem(webHook, webHookNotifications);
                workItems.Add(workItem);
            }
            return workItems;
        }
示例#51
0
 /// <summary>
 /// By overriding this method you can shape the WebHook request URI exactly as you want, both 
 /// in terms of HTTP headers and HTTP body.
 /// </summary>
 protected override HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem)
 {
     return base.CreateWebHookRequest(workItem);
 }
示例#52
0
        internal static HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem, ILogger logger)
        {
            WebHook hook = workItem.WebHook;

            // Create WebHook request
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri);

            // Fill in request body based on WebHook and work item data
            JObject body = CreateWebHookRequestBody(workItem);
            SignWebHookRequest(hook, request, body);

            // Add extra request or entity headers
            foreach (var kvp in hook.Headers)
            {
                if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                {
                    if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value))
                    {
                        string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id);
                        logger.Error(msg);
                    }
                }
            }

            return request;
        }
示例#53
0
 /// <summary>
 /// By overriding this method you can control just the WebHook request body. The rest of the 
 /// WebHook request follows the default model including the HTTP header containing a signature of the 
 /// body.
 /// </summary>
 protected override JObject CreateWebHookRequestBody(WebHookWorkItem workItem)
 {
     return base.CreateWebHookRequestBody(workItem);
 }
示例#54
0
        /// <summary>
        /// Launch a <see cref="WebHook"/>.
        /// </summary>
        /// <remarks>We don't let exceptions propagate out from this method as it is used by the launchers
        /// and if they see an exception they shut down.</remarks>
        private async Task LaunchWebHook(WebHookWorkItem workItem)
        {
            try
            {
                // Setting up and send WebHook request 
                HttpRequestMessage request = CreateWebHookRequest(workItem, _logger);
                HttpResponseMessage response = await _httpClient.SendAsync(request);

                string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_Result, workItem.WebHook.Id, response.StatusCode, workItem.Offset);
                _logger.Info(msg);

                if (response.IsSuccessStatusCode)
                {
                    // If we get a successful response then we are done.
                    if (_onWebHookSuccess != null)
                    {
                        _onWebHookSuccess(workItem);
                    }
                    return;
                }
                else if (response.StatusCode == HttpStatusCode.Gone)
                {
                    // If we get a 410 Gone then we are also done.
                    if (_onWebHookFailure != null)
                    {
                        _onWebHookFailure(workItem);
                    }
                    return;
                }
            }
            catch (Exception ex)
            {
                string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_WebHookFailure, workItem.Offset, workItem.WebHook.Id, ex.Message);
                _logger.Error(msg, ex);
            }

            try
            {
                // See if we should retry the request with delay or give up
                workItem.Offset++;
                if (workItem.Offset < _launchers.Length)
                {
                    // Submit work item
                    _launchers[workItem.Offset].Post(workItem);
                }
                else
                {
                    string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_GivingUp, workItem.WebHook.Id, workItem.Offset);
                    _logger.Error(msg);
                    if (_onWebHookFailure != null)
                    {
                        _onWebHookFailure(workItem);
                    }
                }
            }
            catch (Exception ex)
            {
                string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_WebHookFailure, workItem.Offset, workItem.WebHook.Id, ex.Message);
                _logger.Error(msg, ex);
            }
        }
 protected override Task OnWebHookRetry(WebHookWorkItem workItem)
 {
     if (_onRetry != null)
     {
         _onRetry(workItem);
     }
     return Task.FromResult(true);
 }