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); }
public WebHookWorkItemTests() { _notification = new NotificationDictionary("action", data: null); _notifications = new List<NotificationDictionary> { _notification }; _webHook = new WebHook(); _workItem = new WebHookWorkItem(_webHook, _notifications); }
/// <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); } }
/// <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); } }
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); }
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)); }
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); }
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); }
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); } }
/// <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)); }
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 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)); }
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); }
public new JObject CreateWebHookRequestBody(WebHookWorkItem workItem) { return base.CreateWebHookRequestBody(workItem); }
/// <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); }
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); } }
/// <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); }
private async Task DelayedLaunchWebHook(WebHookWorkItem item, TimeSpan delay) { await Task.Delay(delay); await LaunchWebHook(item); }
/// <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; }
public new HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem) { return base.CreateWebHookRequest(workItem); }
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; }
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; }
/// <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); }
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; }
/// <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); }
/// <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); }