简体   繁体   中英

Bot Framework Sending Unnecessary Error Messages

I create a bot, called picturesaver, using Microsoft's Bot Framework, I added a GroupMe channel, and I have it hosted in Azure. The bot works perfectly, saving pictures to Google Drive.

However, the bot gives an error saying "Service Error:POST to picturesaver timed out after 15s" Is it possible to extend the timeout time? Or even stop the bot from posting anything at all. Could this be an Azure issue or is it a GroupMe issue?

The Bot Connector service has a 15s timeout so you need to make sure any async API calls are handled in that timeframe, or make sure your bot responds with some kind of message if it's waiting for some other operation to complete. Currently the 15s timeout cannot be modified.

The solution to process the message on another thread, and acknowledge the call right away is good only for a bot on an App Service .

But as for a Functions Bot doing so will finish the Azure Function if I immediately return from this method.

I tried it. The Azure Function stops running , and the real response to the chat never comes. So it's not a solution at all for the Function Bots.

I ended up with this code for a Functions Bot , which resolves this problem.

Using Azure Queues

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; }
}

(Be sure to use different Azure Queues for local development with Bot Framework emulator and for a cloud-deployed Function App. Otherwise, the messages sent to your bot from real customers may be processed locally while you are debugging on your machine)

Using an HTTP request

Of course, the same can be done without using an Azure Queue with a direct call to another Azure Function's public URL - https://<my-bot>.azurewebsites.net/api/processTheMessage?code=<function-secret> . This call has to be done on another thread, without waiting for the result in the messages function.

[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);
}

If your bot performs an operation that takes longer than 15 seconds to process a message, you can process the message on another thread, and acknowledge the call right away. Something like:

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
}

This will avoid the 15 second timeout between connector and bot.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM