我使用 Microsoft 的 Bot Framework 创建了一个名为 picturesaver 的机器人,我添加了一个 GroupMe 频道,并将它托管在 Azure 中。该机器人完美运行,将图片保存到 Google Drive。
但是,机器人给出了一个错误,说“服务错误:POST 到图片保护程序在 15 秒后超时”是否可以延长超时时间?或者甚至阻止机器人发布任何内容。这可能是 Azure 问题还是 GroupMe 问题?
我使用 Microsoft 的 Bot Framework 创建了一个名为 picturesaver 的机器人,我添加了一个 GroupMe 频道,并将它托管在 Azure 中。该机器人完美运行,将图片保存到 Google Drive。
但是,机器人给出了一个错误,说“服务错误:POST 到图片保护程序在 15 秒后超时”是否可以延长超时时间?或者甚至阻止机器人发布任何内容。这可能是 Azure 问题还是 GroupMe 问题?
Bot 连接器服务有 15 秒的超时,因此您需要确保在该时间范围内处理任何异步 API 调用,或者确保您的机器人在等待其他操作完成时以某种消息进行响应。目前无法修改 15s 超时。
如果您的机器人执行处理消息所需时间超过 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。
在另一个线程上处理消息并立即确认调用的解决方案仅适用于 App Service 上的机器人。
但是对于Functions Bot,如果我立即从此方法返回,这样做将完成 Azure Function。
我尝试过这个。Azure Function 停止运行,聊天的真正响应永远不会到来。所以这根本不是功能机器人的解决方案。
我最终得到了 Functions Bot 的这段代码,它解决了这个问题。
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。否则,当您在计算机上调试时,可能会在本地处理从真实客户发送到您的机器人的消息)
当然,同样可以在不使用直接调用另一个 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);
}