3

我使用 Microsoft 的 Bot Framework 创建了一个名为 picturesaver 的机器人,我添加了一个 GroupMe 频道,并将它托管在 Azure 中。该机器人完美运行,将图片保存到 Google Drive。

但是,机器人给出了一个错误,说“服务错误:POST 到图片保护程序在 15 秒后超时”是否可以延长超时时间?或者甚至阻止机器人发布任何内容。这可能是 Azure 问题还是 GroupMe 问题?

4

3 回答 3

2

Bot 连接器服务有 15 秒的超时,因此您需要确保在该时间范围内处理任何异步 API 调用,或者确保您的机器人在等待其他操作完成时以某种消息进行响应。目前无法修改 15s 超时。

于 2017-12-20T21:34:08.020 回答
2

如果您的机器人执行处理消息所需时间超过 15 秒的操作,您可以在另一个线程上处理该消息,并立即确认调用。就像是:

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        if ([determine if this will take > 15s]) 
        {
            // process the message asyncronously
            Task.Factory.StartNew(async () => await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()));
        }
        else
        {
            //process the message normally
            await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
        }
    }

    return Request.CreateResponse(HttpStatusCode.OK); //ack the call
}

这将避免连接器和机器人之间的 15 秒超时。


编辑:以上内容不会扩展,只是使用 Task.Factory。有关处理来自机器人的长操作的推荐指南,请参阅https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-long-operations-guidance

于 2017-12-21T00:41:43.407 回答
2

在另一个线程上处理消息并立即确认调用的解决方案仅适用于 App Service 上的机器人

但是对于Functions Bot,如果我立即从此方法返回,这样做将完成 Azure Function。

我尝试过这个。Azure Function 停止运行,聊天的真正响应永远不会到来。所以这根本不是功能机器人的解决方案。

我最终得到了 Functions Bot 的这段代码,它解决了这个问题。

使用 Azure 队列

public static class Functions
{
    [FunctionName("messages")]
    [return: Queue("somequeue")]
    public static async Task<MessagePayload> Messages([HttpTrigger
            (WebHookType = "genericJson")]HttpRequestMessage req) =>
        // return from this Azure Function immediately to avoid timeout warning message 
        // in the chat.
        // just put the request into "somequeue". 
        // We can't pass the whole request via the Queue, so pass only what we need for 
        // the message to be processed by Bot Framework
        new MessagePayload
        {
            RequestUri = req.RequestUri,
            Content = await req.Content.ReadAsStringAsync(),
            AuthScheme = req.Headers.Authorization.Scheme,
            AuthParameter = req.Headers.Authorization.Parameter
        };

    // Do the actual message processing in another Azure Function, which is 
    // triggered by a message enqueued in the Azure Queue "somequeue"
    [FunctionName("processTheMessage")]
    public static async Task ProcessTheMessage([QueueTrigger("somequeue")]
        MessagePayload payload, TraceWriter logger)
    {
        // we don't want the queue to process this message 5 times if it fails, 
        // so we won't throw any exceptions here at all, but we'll handle them properly.
        try
        {
            // recreate the request
            var request = new HttpRequestMessage
            {
                Content = new StringContent(payload.Content),
                RequestUri = payload.RequestUri
            };
            request.Headers.Authorization = new  
                AuthenticationHeaderValue(payload.AuthScheme, payload.AuthParameter);

            // initialize dependency injection container, services, etc.
            var initializer = new SomeInitializer(logger);
            initializer.Initialize();

            // handle the request in a usual way and reply back to the chat
            await initializer.HandleRequestAsync(request);
        }
        catch (Exception ex)
        {
            try
            {
                // TODO: handle the exception
            }
            catch (Exception anotherException)
            {
                // swallow any exceptions in the exceptions handler?
            }
        }
    }

}

[Serializable]
public class MessagePayload
{
    public string Content { get; set; }
    public string AuthParameter { get; set; }
    public string AuthScheme { get; set; }
    public Uri RequestUri { get; set; }
}

(请务必使用不同的 Azure 队列进行本地开发与 Bot Framework 模拟器和云部署的 Function App。否则,当您在计算机上调试时,可能会在本地处理从真实客户发送到您的机器人的消息)

使用 HTTP 请求

当然,同样可以在不使用直接调用另一个 Azure 函数的公共 URL的 Azure 队列https://<my-bot>.azurewebsites.net/api/processTheMessage?code=<function-secret>的情况下完成 - 。此调用必须在另一个线程上完成,而无需等待messages函数中的结果。

[FunctionName("messages")]
public static async Task Run([HttpTrigger(WebHookType = "genericJson")]
    HttpRequestMessage req)
{
    // return from this Azure Function immediately to avoid timeout warning message 
    // in the chat.
    using (var client = new HttpClient())
    {
        string secret = ConfigurationManager.AppSettings["processMessageHttp_secret"];
        // change the RequestUri of the request to processMessageHttp Function's 
        // public URL, providing the secret code, stored in app settings 
        // with key 'processMessageHttp_secret'
        req.RequestUri = new Uri(req.RequestUri.AbsoluteUri.Replace(
            req.RequestUri.PathAndQuery, $"/api/processMessageHttp?code={secret}"));

        // don't 'await' here. Simply send.
#pragma warning disable CS4014
        client.SendAsync(req);
#pragma warning restore CS4014

        // wait a little bit to ensure the request is sent. It will not 
        // send the request at all without this line, because it would 
        // terminate this Azure Function immediately
        await Task.Delay(500);
    }
}

[FunctionName("processMessageHttp")]
public static async Task ProcessMessageHttp([HttpTrigger(WebHookType = "genericJson")]
    HttpRequestMessage req,
    Microsoft.Extensions.Logging.ILogger log)
{
    // first and foremost: initialize dependency 
    // injection container, logger, services, set default culture/language, etc.
    var initializer = FunctionAppInitializer.Initialize(log);

    // handle the request in a usual way and reply back to the chat
    await initializer.HandleRequest(req);
}
于 2018-02-17T21:58:01.040 回答