如何安全地延迟ASP.net中的Web响应?

时间:2019-01-17 19:24:57

标签: c# rest asp.net-mvc-5

我有一个问题,当我们从第三方(Twilio)启动REST资源时,服务响应如此之快,我们没有时间将SID写入数据库。我们不能告诉服务等待,因为它仅在服务启动时才返回SID。应用程序本身无法保持状态,因为无法保证RESTful回调将到达我们应用程序的同一实例。

我们通过将SID写入数据库中的缓冲表来缓解此问题,并且我们尝试了一些策略来强制Web响应等待,但是使用Thread.Sleep似乎阻止了其他不相关的Web响应,通常会在高峰负载时降低服务器速度。

在检查数据库时,如何优雅地要求Web响应暂停一分钟?最好不要用阻塞的线程来破坏整个服务器。

这是启动服务的代码:

currenText()

这是响应的代码:

 private static void SendSMS(Shift_Offer so, Callout co,testdb2Entities5 db)
    {

        co.status = CalloutStatus.inprogress;
        db.SaveChanges();
        try
        {
            CallQueue cq = new CallQueue();
            cq.offer_id = so.shift_offer_id;
            cq.offer_finished = false;
            string ShMessage = getNewShiftMessage(so, co, db);
            so.offer_timestamp = DateTime.Now;
            string ServiceSID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

            var message = MessageResource.Create
                        (
                            body: ShMessage,
                            messagingServiceSid: ServiceSID,
                            to: new Twilio.Types.PhoneNumber(RCHStringHelpers.formatPhoneNumber(so.employee_phone_number)),
                            statusCallback: new Uri(TwilioCallBotController.SMSCallBackURL)
                        );
            cq.twilio_sid = message.Sid;
            db.CallQueues.Add(cq);
            db.SaveChanges();
            so.offer_status = ShiftOfferStatus.OfferInProgress;
            so.status = message.Status.ToString();
            so.twillio_sid = message.Sid;
            db.SaveChanges();

        }
        catch (SqlException e) //if we run into any problems here, release the lock to prevent stalling; 
                               //note to self - this should all be wrapped in a transaction and rolled back on error
        {
            Debug.WriteLine("Failure in CalloutManager.cs at method SendSMS: /n" +
                            "Callout Id: " + co.callout_id_pk + "/n"
                            + "Shift Offer Id: " + so.shift_offer_id + "/n"
                            + e.StackTrace);
            ResetCalloutStatus(co, db);
            ReleaseLock(co, db);
        }
        catch (Twilio.Exceptions.ApiException e) 
        {
            ReleaseLock(co, db);
            ResetCalloutStatus(co, db);
            Debug.WriteLine(e.Message + "/n" + e.StackTrace);
        }

    }

这是延迟的代码:

        public ActionResult TwilioSMSCallback()
        {
            //invalid operation exception occurring here
            string sid = Request.Form["SmsSid"];
            string status = Request.Form["SmsStatus"];
            Shift_Offer shoffer;
            CallQueue cq = null;

            List<Shift_Offer> sho = db.Shift_Offers.Where(s => s.twillio_sid == sid).ToList();
            List<CallQueue> cqi = getCallQueueItems(sid, db);
            if (sho.Count > 0)
            {
                shoffer = sho.First();
                if (cqi.Count > 0)
                {
                    cq = cqi.First();
                }
            }
            else
            {
                if (cqi.Count > 0)
                {
                    cq = cqi.First();
                    shoffer = db.Shift_Offers.Where(x => x.shift_offer_id == cq.offer_id).ToList().First();
                }
                else
                {
                    return new Twilio.AspNet.Mvc.HttpStatusCodeResult(HttpStatusCode.NoContent);
                }
            }

            Callout co = db.Callouts.Where(s => s.callout_id_pk == shoffer.callout_id_fk).ToList().First();
            shoffer.status = status;
            if (status.Contains("accepted"))
            {
                shoffer.offer_timestamp = DateTime.Now;
                shoffer.offer_status = ShiftOfferStatus.SMSAccepted + " " + DateTime.Now;
            }
            else if (status.Contains("queued") || status.Contains("sending"))
            {
                shoffer.offer_timestamp = DateTime.Now;
                shoffer.offer_status = ShiftOfferStatus.SMSSent + " " + DateTime.Now;
            }
            else if (status.Contains("delivered") || status.Contains("sent"))
            {
                shoffer.offer_timestamp = DateTime.Now;
                shoffer.offer_status = ShiftOfferStatus.SMSDelivered + " " + DateTime.Now;
                setStatus(co);
                if (cq != null){
                    cq.offer_finished = true;
                }
                CalloutManager.ReleaseLock(co, db);
            }
            else if (status.Contains("undelivered"))
            {
                shoffer.offer_status = ShiftOfferStatus.Failed + " " + DateTime.Now;
                setStatus(co);
                if (cq != null){
                    cq.offer_finished = true;
                }
                CalloutManager.ReleaseLock(co, db);
            }
            else if (status.Contains("failed"))
            {
                shoffer.offer_status = ShiftOfferStatus.Failed + " " + DateTime.Now;
                setStatus(co);
                if (cq != null){
                    cq.offer_finished = true;
                }
                cq.offer_finished = true;
                CalloutManager.ReleaseLock(co, db);
            }
            db.SaveChanges();
            return new Twilio.AspNet.Mvc.HttpStatusCodeResult(HttpStatusCode.OK);
        }

3 个答案:

答案 0 :(得分:3)

Good APIs™使消费者可以指定他们希望消息与之关联的ID。我自己从未使用过Twilio,但是现在我已经阅读了Creating a Message Resource的API参考,可悲的是,似乎他们没有为此提供参数。但是仍然有希望!

潜在的解决方案(首选)

即使没有明确的参数,也许您可​​以为创建的每条消息指定略有不同的回调URL?假设您的CallQueue实体具有唯一的Id属性,则可以让每条消息的回调URL包含指定此ID的查询字符串参数。然后,您可以在不知道消息Sid的情况下处理回调。

要实现此目的,您将在SendSMS方法中对事物进行重新排序,以便在调用Twilio API之前保存CallQueue实体:

db.CallQueues.Add(cq);
db.SaveChanges();

string queryStringParameter = "?cq_id=" + cq.id;
string callbackUrl = TwilioCallBotController.SMSCallBackURL + queryStringParameter;

var message = MessageResource.Create
(
    [...]
    statusCallback: new Uri(callbackUrl)
);

您还将修改回调处理程序TwilioSMSCallback,以便它通过其ID从CallQueue查询字符串参数中检索其cq_id实体。

几乎可以保证有效的解决方案(但需要更多工作)

某些云服务仅允许与预配置列表中的条目之一完全匹配的回调URL。对于此类服务,使用不同的回调URL的方法将不起作用。如果Twilio是这种情况,那么您应该可以使用以下思路解决您的问题。

与另一种方法相比,此方法需要对代码进行较大的更改,因此,我仅作简要说明,让您确定详细信息。

想法是即使TwilioSMSCallback实体在数据库中尚不存在,也要使CallQueue方法有效:

  • 如果数据库中没有匹配的CallQueue实体,TwilioSMSCallback应该只将接收到的消息状态更新存储在新的实体类型MessageStatusUpdate中,以便可以待会儿处理。

  • “稍后”位于SendSMS的最后:您将在此处添加代码以获取和处理任何具有匹配MessageStatusUpdate的未处理twilio_sid实体。

  • 实际处理消息状态更新(更新关联的Shift_Offer等)的代码应从TwilioSMSCallback移开,并放在单独的方法中,该方法也可以称为摘自SendSMS末尾的新代码。

使用这种方法,您还必须引入某种锁定机制,以避免试图处理同一twilio_sid的更新的多个线程/进程之间的竞争状况。

答案 1 :(得分:2)

您真的不应该延迟RESTful通话。将其分为两步,一个用于启动它,一个用于获取状态。您可以多次调用后者,直到操作安全完成为止,它是轻量级的,并且如果需要,还可以向呼叫者提供进度指示器或状态反馈。

答案 2 :(得分:-1)

异步/等待可以帮助您避免阻塞线程。

您可以尝试用int[] my_array = {1, 2, 5, 5, 6, 6, 7, 2}; for (int i = 0; i < my_array.length; i++) { boolean dup = false; for (int j = 0; j < my_array.length; j++) { if (i != j && my_array[i] == my_array[j]) { dup = true; break; } } if (! dup) { System.out.println(my_array[i]); } } 代替1 7

更新

我看到您在await Task.Delay(...).ConfigureAwait(false)中有一些长期运行的逻辑,并且我相信此回调应尽可能快地执行,因为它来自Twilio服务(可能会受到惩罚)。

我建议您将短信状态处理逻辑移至Thread.Sleep()方法的末尾,并使用TwilioSMSCallback在那里轮询数据库,直到获得短信状态为止。但是,这将使SendSMS请求在调用方保持活动状态,因此最好有单独的服务,该服务将轮询数据库并在发生更改时调用您的api。