<commit_before>﻿using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Abp.Domain.Services;
using Abp.Domain.Uow;
using Abp.Json;
using Abp.Threading;

namespace Abp.Webhooks
{
    public class DefaultWebhookSender : DomainService, IWebhookSender
    {
        public IWebhookSendAttemptStore WebhookSendAttemptStore { get; set; }

        protected const string SignatureHeaderKey = "sha256";
        protected const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}";
        protected const string SignatureHeaderName = "abp-webhook-signature";

        private readonly IWebhooksConfiguration _webhooksConfiguration;

        public DefaultWebhookSender(IWebhooksConfiguration webhooksConfiguration)
        {
            _webhooksConfiguration = webhooksConfiguration;

            WebhookSendAttemptStore = NullWebhookSendAttemptStore.Instance;
        }

        public async Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs)
        {
            if (webhookSenderArgs.WebhookId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookId));
            }

            if (webhookSenderArgs.WebhookSubscriptionId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
            }

            var webhookSendAttemptId = await InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs);

            var request = CreateWebhookRequestMessage(webhookSenderArgs);

            var payload = await GetWebhookPayloadAsync(webhookSenderArgs);

            var serializedBody = _webhooksConfiguration.JsonSerializerSettings != null
                ? payload.ToJsonString(_webhooksConfiguration.JsonSerializerSettings)
                : payload.ToJsonString();

            SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);

            AddAdditionalHeaders(request, webhookSenderArgs);

            bool isSucceed = false;
            HttpStatusCode statusCode;
            string content;
            try
            {
                using (var client = new HttpClient()
                {
                    Timeout = _webhooksConfiguration.TimeoutDuration
                })
                {
                    var response = await client.SendAsync(request);

                    isSucceed = response.IsSuccessStatusCode;
                    statusCode = response.StatusCode;
                    content = await response.Content.ReadAsStringAsync();
                }
            }
            catch (TaskCanceledException)
            {
                statusCode = HttpStatusCode.RequestTimeout;
                content = "Request Timeout";
                isSucceed = false;
            }

            await StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);

            if (!isSucceed)
            {
                throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
            }
        }

        public void SendWebhook(WebhookSenderArgs webhookSenderArgs)
        {
            if (webhookSenderArgs.WebhookId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookId));
            }

            if (webhookSenderArgs.WebhookSubscriptionId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
            }

            var webhookSendAttemptId = InsertAndGetIdWebhookSendAttempt(webhookSenderArgs);

            var request = CreateWebhookRequestMessage(webhookSenderArgs);

            var payload = GetWebhookPayload(webhookSenderArgs);

            var serializedBody = _webhooksConfiguration.JsonSerializerSettings != null
                ? payload.ToJsonString(_webhooksConfiguration.JsonSerializerSettings)
                : payload.ToJsonString();

            SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);

            AddAdditionalHeaders(request, webhookSenderArgs);

            bool isSucceed = false;
            HttpStatusCode statusCode;
            string content;
            try
            {
                using (var client = new HttpClient()
                {
                    Timeout = _webhooksConfiguration.TimeoutDuration
                })
                {
                    var response = AsyncHelper.RunSync(() => client.SendAsync(request));

                    isSucceed = response.IsSuccessStatusCode;
                    statusCode = response.StatusCode;
                    content = AsyncHelper.RunSync(() => response.Content.ReadAsStringAsync());
                }
            }
            catch (TaskCanceledException)
            {
                statusCode = HttpStatusCode.RequestTimeout;
                content = "Request Timeout";
                isSucceed = false;
            }

            StoreResponseOnWebhookSendAttempt(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);

            if (!isSucceed)
            {
                throw new Exception($"Webhook send attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
            }
        }

        [UnitOfWork]
        protected virtual async Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs)
        {
            var workItem = new WebhookSendAttempt()
            {
                WebhookId = webhookSenderArgs.WebhookId,
                WebhookSubscriptionId = webhookSenderArgs.WebhookSubscriptionId,
                TenantId = webhookSenderArgs.TenantId
            };

            await WebhookSendAttemptStore.InsertAsync(workItem);
            await CurrentUnitOfWork.SaveChangesAsync();

            return workItem.Id;
        }

        [UnitOfWork]
        protected virtual Guid InsertAndGetIdWebhookSendAttempt(WebhookSenderArgs webhookSenderArgs)
        {
            var workItem = new WebhookSendAttempt()
            {
                WebhookId = webhookSenderArgs.WebhookId,
                WebhookSubscriptionId = webhookSenderArgs.WebhookSubscriptionId,
                TenantId = webhookSenderArgs.TenantId
            };

            WebhookSendAttemptStore.Insert(workItem);
            CurrentUnitOfWork.SaveChanges();

            return workItem.Id;
        }

        [UnitOfWork]
        protected virtual async Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, int? tenantId, HttpStatusCode statusCode, string content)
        {
            var webhookSendAttempt = await WebhookSendAttemptStore.GetAsync(tenantId, webhookSendAttemptId);

            webhookSendAttempt.ResponseStatusCode = statusCode;
            webhookSendAttempt.Response = content;

            await WebhookSendAttemptStore.UpdateAsync(webhookSendAttempt);
        }

        [UnitOfWork]
        protected virtual void StoreResponseOnWebhookSendAttempt(Guid webhookSendAttemptId, int? tenantId, HttpStatusCode statusCode, string content)
        {
            var webhookSendAttempt = WebhookSendAttemptStore.Get(tenantId, webhookSendAttemptId);

            webhookSendAttempt.ResponseStatusCode = statusCode;
            webhookSendAttempt.Response = content;

            WebhookSendAttemptStore.Update(webhookSendAttempt);
        }

        /// <summary>
        /// You can override this to change request message
        /// </summary>
        /// <returns></returns>
        protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs)<commit_msg>also save HttpRequestException's to webhooksendattempt<commit_after>﻿using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Abp.Domain.Services;
using Abp.Domain.Uow;
using Abp.Json;
using Abp.Threading;

namespace Abp.Webhooks
{
    public class DefaultWebhookSender : DomainService, IWebhookSender
    {
        public IWebhookSendAttemptStore WebhookSendAttemptStore { get; set; }

        protected const string SignatureHeaderKey = "sha256";
        protected const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}";
        protected const string SignatureHeaderName = "abp-webhook-signature";

        private readonly IWebhooksConfiguration _webhooksConfiguration;

        public DefaultWebhookSender(IWebhooksConfiguration webhooksConfiguration)
        {
            _webhooksConfiguration = webhooksConfiguration;

            WebhookSendAttemptStore = NullWebhookSendAttemptStore.Instance;
        }

        public async Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs)
        {
            if (webhookSenderArgs.WebhookId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookId));
            }

            if (webhookSenderArgs.WebhookSubscriptionId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
            }

            var webhookSendAttemptId = await InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs);

            var request = CreateWebhookRequestMessage(webhookSenderArgs);

            var payload = await GetWebhookPayloadAsync(webhookSenderArgs);

            var serializedBody = _webhooksConfiguration.JsonSerializerSettings != null
                ? payload.ToJsonString(_webhooksConfiguration.JsonSerializerSettings)
                : payload.ToJsonString();

            SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);

            AddAdditionalHeaders(request, webhookSenderArgs);

            bool isSucceed = false;
            HttpStatusCode? statusCode;
            string content;
            try
            {
                using (var client = new HttpClient()
                {
                    Timeout = _webhooksConfiguration.TimeoutDuration
                })
                {
                    var response = await client.SendAsync(request);

                    isSucceed = response.IsSuccessStatusCode;
                    statusCode = response.StatusCode;
                    content = await response.Content.ReadAsStringAsync();
                }
            }
            catch (TaskCanceledException)
            {
                statusCode = HttpStatusCode.RequestTimeout;
                content = "Request Timeout";
                isSucceed = false;
            }
            catch (HttpRequestException e)
            {
                content = e.Message;
                statusCode = null;
                isSucceed = false;
            }

            await StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);

            if (!isSucceed)
            {
                throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
            }
        }

        public void SendWebhook(WebhookSenderArgs webhookSenderArgs)
        {
            if (webhookSenderArgs.WebhookId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookId));
            }

            if (webhookSenderArgs.WebhookSubscriptionId == default)
            {
                throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
            }

            var webhookSendAttemptId = InsertAndGetIdWebhookSendAttempt(webhookSenderArgs);

            var request = CreateWebhookRequestMessage(webhookSenderArgs);

            var payload = GetWebhookPayload(webhookSenderArgs);

            var serializedBody = _webhooksConfiguration.JsonSerializerSettings != null
                ? payload.ToJsonString(_webhooksConfiguration.JsonSerializerSettings)
                : payload.ToJsonString();

            SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);

            AddAdditionalHeaders(request, webhookSenderArgs);

            bool isSucceed = false;
            HttpStatusCode? statusCode;
            string content;
            try
            {
                using (var client = new HttpClient()
                {
                    Timeout = _webhooksConfiguration.TimeoutDuration
                })
                {
                    var response = AsyncHelper.RunSync(() => client.SendAsync(request));

                    isSucceed = response.IsSuccessStatusCode;
                    statusCode = response.StatusCode;
                    content = AsyncHelper.RunSync(() => response.Content.ReadAsStringAsync());
                }
            }
            catch (TaskCanceledException)
            {
                statusCode = HttpStatusCode.RequestTimeout;
                content = "Request Timeout";
                isSucceed = false;
            }
            catch (HttpRequestException e)
            {
                content = e.Message;
                statusCode = null;
                isSucceed = false;
            }

            StoreResponseOnWebhookSendAttempt(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);

            if (!isSucceed)
            {
                throw new Exception($"Webhook send attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
            }
        }

        [UnitOfWork]
        protected virtual async Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs)
        {
            var workItem = new WebhookSendAttempt()
            {
                WebhookId = webhookSenderArgs.WebhookId,
                WebhookSubscriptionId = webhookSenderArgs.WebhookSubscriptionId,
                TenantId = webhookSenderArgs.TenantId
            };

            await WebhookSendAttemptStore.InsertAsync(workItem);
            await CurrentUnitOfWork.SaveChangesAsync();

            return workItem.Id;
        }

        [UnitOfWork]
        protected virtual Guid InsertAndGetIdWebhookSendAttempt(WebhookSenderArgs webhookSenderArgs)
        {
            var workItem = new WebhookSendAttempt()
            {
                WebhookId = webhookSenderArgs.WebhookId,
                WebhookSubscriptionId = webhookSenderArgs.WebhookSubscriptionId,
                TenantId = webhookSenderArgs.TenantId
            };

            WebhookSendAttemptStore.Insert(workItem);
            CurrentUnitOfWork.SaveChanges();

            return workItem.Id;
        }

        [UnitOfWork]
        protected virtual async Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, int? tenantId, HttpStatusCode? statusCode, string content)
        {
            var webhookSendAttempt = await WebhookSendAttemptStore.GetAsync(tenantId, webhookSendAttemptId);

            webhookSendAttempt.ResponseStatusCode = statusCode;
            webhookSendAttempt.Response = content;

            await WebhookSendAttemptStore.UpdateAsync(webhookSendAttempt);
        }

        [UnitOfWork]
        protected virtual void StoreResponseOnWebhookSendAttempt(Guid webhookSendAttemptId, int? tenantId, HttpStatusCode? statusCode, string content)
        {
            var webhookSendAttempt = WebhookSendAttemptStore.Get(tenantId, webhookSendAttemptId);

            webhookSendAttempt.ResponseStatusCode = statusCode;
            webhookSendAttempt.Response = content;

            WebhookSendAttemptStore.Update(webhookSendAttempt);
        }

        /// <summary>
        /// You can override this to change request message
        /// </summary>
        /// <returns></returns>
        protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs)
{'fraction_non_alphanumeric': 0.06308455213106522, 'fraction_numerical': 0.0005021655891030067, 'mean_word_length': 2.119639710201684, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5312144', 'n_tokens_mistral': 3875, 'n_tokens_neox': 3679, 'n_words': 703}