I'm doing some investigating around a Teams Bot that I currently have in development. I'm seeing a lot of 404, and in some other cases, 405 errors when I look in Application Insights - I'm trying to understand if I've missed anything.
I have the App Service set to 'Always On' so my assumption is that it's polling the service every 5 minutes to keep it from idling out. However, I'm seeing a lot of 404 failures, specifically pointing to the GET/ endpoint and in other cases a 405 error as well, which is pointing to the api/messages endpoint.
I have the App ID and App Password set in the environment variables and I've set the storage using a Cosmos DB too as shown in the index.js file below. I have also checked the Teams manifest to ensure it's pointing to the Bot ID and recently added the bot domain as well to see if that makes a difference.
const restify = require('restify');
const path = require('path');
// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter, ConversationState, UserState } = require('botbuilder');
// Import required services for bot telemetry
const { ApplicationInsightsTelemetryClient, TelemetryInitializerMiddleware } = require('botbuilder-applicationinsights');
const { TelemetryLoggerMiddleware, NullTelemetryClient } = require('botbuilder-core');
// Import our custom bot class that provides a turn handling function.
const { DialogBot } = require('./bots/dialogBot');
const { ProvisioningProfileDialog } = require('./dialogs/provisioningProfileDialog');
// Read environment variables from .env file
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
// Create the adapter. See https://aka.ms/about-bot-adapter to learn more about using information from
// the .bot file when configuring your adapter.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Define the state store for your bot.
const { CosmosDbPartitionedStorage } = require('botbuilder-azure');
const cosmosStorage = new CosmosDbPartitionedStorage({
cosmosDbEndpoint: process.env.CosmosDbEndpoint,
authKey: process.env.CosmosDbAuthKey,
databaseId: process.env.CosmosDbDatabaseId,
containerId: process.env.CosmosDbContainerId,
compatibilityMode: false
});
// Create conversation state with storage provider.
const conversationState = new ConversationState(cosmosStorage);
const userState = new UserState(cosmosStorage);
// Create the main dialog.
const dialog = new ProvisioningProfileDialog(userState);
const bot = new DialogBot(conversationState, userState, dialog);
dialog.telemetryClient = telemetryClient;
// Catch-all for errors.
const onTurnErrorHandler = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
// Clear out state
await conversationState.delete(context);
};
// Set the onTurnError for the singleton BotFrameworkAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Add telemetry middleware to the adapter middleware pipeline
var telemetryClient = getTelemetryClient(process.env.InstrumentationKey);
var telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(telemetryClient);
var initializerMiddleware = new TelemetryInitializerMiddleware(telemetryLoggerMiddleware);
adapter.use(initializerMiddleware);
// Creates a new TelemetryClient based on a instrumentation key
function getTelemetryClient(instrumentationKey) {
if (instrumentationKey) {
return new ApplicationInsightsTelemetryClient(instrumentationKey);
}
return new NullTelemetryClient();
}
// Create HTTP server.
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }.`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
console.log('\nTo talk to your bot, open the emulator select "Open Bot"');
});
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// Route the message to the bot's main handler.
await bot.run(context);
});
});
Whilst the Bot appears to run okay for the most part, am I missing something with these errors or is this expected behaviour since it's polling for a response?
Thanks in advance
Does your bot contain a web page as well? The node samples do not, but .NET samples do. If not, it would make sense, of course, to receive a 404. I tend to agree with you that the polling might be the cause.
Bots typically (especially when created from template or sample), do not handle GET endpoints to /api/messages. Everything is handled using POST.
Related
So basically I'm doing a game where the server sends messages to clients, and the client who answer first recieve 1 pnt. I'm trying to create rooms to improve the multiplayer mode, but I'm stuck at this point.
I'm trying to connect socket.io to my google Firebase functions, but when I call the function it returns this error:
Billing account not configured. External network is not accessible and quotas are severely limited.
Configure billing account to remove these restrictions
10:13:08.239 AM
addStanza
Uncaught exception
10:13:08.242 AM
addStanza
Error: getaddrinfo EAI_AGAIN at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26)
10:13:08.584 AM
addStanza
Error: function crashed out of request scope Function invocation was interrupted.
This is the code:
//firebase deploy --only functions
const Proverbi = require('./Proverbi.js');
const socketIo = require("socket.io");
const https = require("https");
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var server = https.createServer();
server.listen(443, "https://us-central1-chip-chop.cloudfunctions.net");
var io = socketIo.listen(server);
// Take the text parameter passed to this HTTP endpoint and insert it into the
// Realtime Database under the path /messages/:pushId/original
exports.addStanza = functions.https.onRequest(async (req, res) => {
// Grab the text parameter.
const nome = req.query.nome;
// Push the new message into the Realtime Database using the Firebase Admin SDK.
const snapshot = await admin.database().ref('/stanze').push({ giocatori: { giocatore: { nome: nome, punteggio: 0 } } });
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
//res.redirect(200, nome.toString());
var link = snapshot.toString().split('/');
res.json({ idStanza: link[4] });
});
// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.addFirstPlayer = functions.database.ref('/stanze/{pushId}/giocatori/giocatore/nome')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const nome = snapshot.val();
// const snapshot3 = snapshot.ref('/stanza/{pushId}/giocatori/giocatore').remove();
const snapshot2 = snapshot.ref.parent.parent.remove();
var room = snapshot.ref.parent.parent.parent.val();
// handle incoming connections from clients
io.sockets.on('connection', function (socket) {
// once a client has connected, we expect to get a ping from them saying what room they want to join
socket.on('room', function (room) {
socket.join(room);
});
});
io.sockets.in(room).emit('message', nome + 'Si è unito alla stanza');
return snapshot.ref.parent.parent.push({ nome: nome, punteggio: 0, room:room });
});
exports.addPlayer = functions.https.onRequest(async (req, res) => {
// Grab the text parameter.
const nome = req.query.nome;
const idStanza = req.query.id;
// Push the new message into the Realtime Database using the Firebase Admin SDK.
const snapshot = await admin.database().ref('/stanze/' + idStanza + "/giocatori").push({ nome: nome, punteggio: 0 });
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
var room = idStanza;
// handle incoming connections from clients
io.sockets.on('connection', function (socket) {
// once a client has connected, we expect to get a ping from them saying what room they want to join
socket.on('room', function (room) {
socket.join(room);
});
});
io.sockets.in(room).emit('message', nome + 'Si è unito alla stanza');
//res.redirect(200, nome.toString());
res.json({ success: { id: idStanza } });
});
Is the function crashing only because my firebase plan is limited? Or is there other problems?
It's not possible to use Cloud Functions as a host for socket-based I/O. Calls to "listen" on any port will fail every time. The provided network infrastructure only handles individual HTTP requests with a request and response payload size of 10MB per request. You have no control over how it handles the request and response at the network level.
I have built two docker images. One with nginx that serves my angular web app and another with node.js that serves a basic express app. I have tried to access the express app from my browser in two different tabs at the same time.
In one tab the angular dev server (ng serve) serves up the web page. In the other tab the docker nginx container serves up the web page.
While accessing the node.js express app at the same time from both tabs the data starts to mix and mingle and the results returned to both tabs are a mix mash of the two requests (one from each browser tab)...
I'll try and make this more simple by showing my express app code here...but to answer this question you may not even need to know what the code is at all...so maybe check the question as stated below the code first.
'use strict';
/***********************************
GOOGLE GMAIL AND OAUTH SETUP
***********************************/
const fs = require('fs');
const {google} = require('googleapis');
const gmail = google.gmail('v1');
const clientSecretJson = JSON.parse(fs.readFileSync('./client_secret.json'));
const oauth2Client = new google.auth.OAuth2(
clientSecretJson.web.client_id,
clientSecretJson.web.client_secret,
'https://us-central1-labelorganizer.cloudfunctions.net/oauth2callback'
);
/***********************************
EXPRESS WITH CORS SETUP
***********************************/
const PORT = 8000;
const HOST = '0.0.0.0';
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const whiteList = [
'http://localhost:4200',
'http://localhost:80',
'http://localhost',
];
const googleApi = express();
googleApi.use(
cors({
origin: whiteList
}),
cookieParser(),
bodyParser()
);
function getPageOfThreads(pageToken, userId, labelIds) {
return new Promise((resolve, reject) => {
gmail.users.threads.list(
{
'auth': oauth2Client,
'userId': userId,
'labelIds': labelIds,
'pageToken': pageToken
},
(error, response) => {
if (error) {
console.error(error);
reject(error);
}
resolve(response.data);
}
)
});
}
async function getPages(nextPageToken, userId, labelIds, result) {
while (nextPageToken) {
let pageOfThreads = await getPageOfThreads(nextPageToken, userId, labelIds);
console.log(pageOfThreads.nextPageToken);
pageOfThreads.threads.forEach((thread) => {
result = result.concat(thread.id);
})
nextPageToken = pageOfThreads.nextPageToken;
}
return result;
}
googleApi.post('/threads', (req, res) => {
console.log(req.body);
let threadIds = [];
oauth2Client.credentials = req.body.token;
let getAllThreadIds = new Promise((resolve, reject) => {
gmail.users.threads.list(
{ 'auth': oauth2Client, 'userId': 'me', 'maxResults': 500 },
(err, response) => {
if (err) {
console.error(err)
reject(err);
}
if (response.data.threads) {
response.data.threads.forEach((thread) => {
threadIds = threadIds.concat(thread.id);
});
}
if (response.data.nextPageToken) {
getPages(response.data.nextPageToken, 'me', ['INBOX'], threadIds).then(result => {
resolve(result);
}).catch((err) => {
console.error(err);
reject(err);
});
} else {
resolve(threadIds);
}
}
);
});
getAllThreadIds
.then((result) => {
res.send({ threadIds: result });
})
.catch((error) => {
res.status(500).send({ error: 'Request failed with error: ' + error })
});
});
googleApi.get('/', (req, res) => res.send('Hello World!'))
googleApi.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
The angular app makes a simple request to the express app and waits for the reply...which it properly receives...but when I try to make two requests at the exact same time data starts to get mixed together and results are given back to each browser tab from different accounts...
...and the question is... When running containers in the cloud is this kind of thing an issue? Does one need to spin up a new container for each client that wants to actively connect to the express service so that their data doesn't get mixed?
...or is this an issue I am seeing because the express app is being accessed from locally inside my machine? If two machines with two different ip address tried to access this express server at the same time would this sort of data mixing still be an issue or would each get back it's own set of results?
Is this why people use CaaS instead of IaaS solutions?
FYI: this is demo code and the data will not be actually going back to the consumer directly...plans are to have it placed into a database and then re-extracted from the database to download all of the metadata headers for each email.
-Thank you for your time
I can only clear up a small part of this question:
When running containers in the cloud is this kind of thing an issue?
No. Docker is not causing any of the quirky behaviour that you are describing.
Does one need to spin up a new container for each client?
A docker container generally can serve as much users as the application inside of it can. So as long as your application can handle a lot of users (and it should), you don't have to start the same application in multiple containers. That said, when you expect a very large number of customers, there exist docker tools like Docker Compose, Docker Swarm and a lot of alternatives that will enable you to scale up later. For now, you don't need to worry about this at all.
I think I may have found out the issue with my code...and this is actually very important if you are using the node.js googleapis client library...
It is entirely necessary to create a new oauth2Client for each request that comes in
const oauth2Client = new google.auth.OAuth2(
clientSecretJson.web.client_id,
clientSecretJson.web.client_secret,
'https://us-central1-labelorganizer.cloudfunctions.net/oauth2callback'
);
Problem:
When this oauth2Client is shared it is shared by each and every person that connects at the same time...So it is necessary to create a new one each and every time a user connects to my /threads endpoint so that they do not share the same memory space (i.e. access_token etc.) while the processing is done.
Setting the client secret etc. and creating the oauth2Client just once at the top and then simply resetting the token for each request leads to the conflicts mentioned above.
Solution:
For now simply moving the creation of this oauth2Client into each and every request that comes in makes this work properly.
Each client that connects to the service NEEDS to have their own newly created oauth2Client instance or these types of conflicts will occur...
...it's kind of a no brainer but I still find it odd that there is nothing about this in the docs? and their own examples (https://github.com/googleapis/google-api-nodejs-client) seem to show only one instance being created for the whole of their app...but those examples are snippets so...
i am struggling a bit with my google assistant action. Right now i am using Dialogflow and Firebase for my webhook. In my code i would like to get data from an API, for example this one: API. I am coding with Node.js by the way. Since Node is asynchronous i do not know how to get the Data. When i try to make an Callback it doesnt work e.g.:
app.intent(GetData, (conv) => {
var test= "error";
apicaller.callApi(answer =>
{
test = answer.people[0].name
go()
})
function go ()
{
conv.ask(`No matter what people tell you, words and ideas change the world ${test}`)
}
For some reason this works when i test it in an other application. With Dialogflow it does not work
I have also tried to use asynch for the function app.intent and tried it with await but this did not work too.
Do you have any idea how i could fix this?
Thank you in advance and best regards
Luca
You need to return Promise like
function dialogflowHanlderWithRequest(agent) {
return new Promise((resolve, reject) => {
request.get(options, (error, response, body) => {
JSON.parse(body)
// processing code
agent.add(...)
resolve();
});
});
};
See following for details:
Dialogflow NodeJs Fulfillment V2 - webhook method call ends before completing callback
If this works in another application then I believe you're getting an error because you're attempting to access an external resource while using Firebases free Spark plan, which limits you to Google services only. You will need to upgrade to the pay as you go Blaze plan to perform Outbound networking tasks.
Due to asynchronicity, the function go() is gonna be called after the callback of callapi been executed.
Although you said that you have tried to use async, I suggest the following changes that are more likely to work in your scenario:
app.intent(GetData, async (conv) => {
var test= "error";
apicaller.callApi(async answer =>
{
test = answer.people[0].name
await go()
})
async function go ()
{
conv.ask(`No matter what people tell you, words and ideas change the world ${test}`)
}
First follow the procedure given in their Github repository
https://github.com/googleapis/nodejs-dialogflow
Here you can find a working module already given in the README file.
You have to make keyFilename object to store in SessionsClient object creation (go at the end of post to know how to genearate credentials file that is keyFileName). Below the working module.
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//////////////////////////////////////////////////////////////////
const dialogflow = require("dialogflow");
const uuid = require("uuid");
/**
* Send a query to the dialogflow agent, and return the query result.
* #param {string} projectId The project to be used
*/
async function runSample(projectId = "<your project Id>") {
// A unique identifier for the given session
const sessionId = uuid.v4();
// Create a new session
const sessionClient = new dialogflow.SessionsClient({
keyFilename: "<The_path_for_your_credentials_file>"
});
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: "new page",
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log("Detected intent");
console.log(responses);
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
}
runSample();
Here you can obtain the keyFileName file that is the credentials file using google cloud platform using
https://cloud.google.com/docs/authentication/getting-started
For complete tutorial (Hindi language) watch youtube video:
https://www.youtube.com/watch?v=aAtISTrb9n4&list=LL8ZkoggMm9re6KMO1IhXfkQ
I am tring to write a simple bot that after some task reply with a text.
The bot offline with app.polling is working perfectly. But when I deploy it on heroku, if I write only one ctx.reply(), no message is sent. If I write it twice, one message is sent on telegram. You can see the code on the snippet below, I included only the necessary code (the log show me with console.log that all the function are working and the final text is ready to be sent, I also commented the code a little to exlpain better the situation).
So this appear strange to me,can anyone explain why?
const Telegraf = require('telegraf');
const request = require('request');
const date = require('date-and-time');
const API_TOKEN = process.env.API_TOKEN || ''; //the api token is into env var
const PORT = process.env.PORT || 3000;
const URL = process.env.URL || 'url of my heroku account';
const app = new Telegraf(API_TOKEN);
app.telegram.setWebhook(`${URL}/bot${API_TOKEN}`);
app.startWebhook(`/bot${API_TOKEN}`, null, PORT);
function getdata(ctx,stazione1,stazione2){
let tempo = gettempo();
let linkget = '....';
var options = {
url: linkget,
headers: {
'Referer':'http://m.trenord.it/site-lite/index.html',
'secret': '...'
}
};
let linkget1 = '...';
var options1 = {
url: linkget1,
headers: {
'Referer':'...',
'secret': '...'
}
};
request(options, function(error, response, body){
request(options1, async function(error1, response1, body1){
let text = await gettext(body,stazione1,stazione2);//return text
let text1 = await gettext(body1,stazione2,stazione1);//return text
let final = await text+"\r\n\r\n"+text1;
console.log(ctx);
//here is the problem, if i write only one reply no message is sent on the bot, but if i wrtite it twice one message is sent.
ctx.replyWithMarkdown(final);
ctx.replyWithMarkdown(final);
});
});
}//getdata
app.command('pl', function(ctx){
getdata(ctx,stazione1,stazione2);
});
NEWS
I want to add some feedback while the server works, so I add a ctx.reply("searching...") after the command right before the function getdata is launched. Now all two messages are sent to telegram. On the previous case is possible that heroku "shut down the webhook" and at the first ctx.reply was wake up and than at the second ctx.reply the message was sent?
I'm using node-telegram-bot-api for node.js/Heroku and this config work well for me:
const options = {
webHook: {
// Port to which you should bind is assigned to $PORT variable
// See: https://devcenter.heroku.com/articles/dynos#local-environment-variables
port: process.env.PORT,
// you do NOT need to set up certificates since Heroku provides
// the SSL certs already (https://<app-name>.herokuapp.com)
// Also no need to pass IP because on Heroku you need to bind to 0.0.0.0
},
};
// okk
// Heroku routes from port :443 to $PORT
// Add URL of your app to env variable or enable Dyno Metadata
// to get this automatically
// See: https://devcenter.heroku.com/articles/dyno-metadata
const url = process.env.APP_URL || 'https://my-bot.herokuapp.com:443';
const bot = new Tg(config.BOT_TOKEN, options);
// This informs the Telegram servers of the new webhook.
// Note: we do not need to pass in the cert, as it already provided
bot.setWebHook(`${url}/bot${config.BOT_TOKEN}`);
I am trying to create a Slack bot using LUIS where when the bot sees a greeting in a channel it is added to, it sends a direct message to the user that sent the greeting.
I have looked at Issue #431 and wrote a bot. Here is my code:
var builder = require('botbuilder');
var restify = require('restify');
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log("%s listening to %s", server.name, server.url);
});
server.get(/.*/, restify.serveStatic({
'directory': '.',
'default': 'index.html'
}));
// Create Chat Bot
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
var bot = new builder.UniversalBot(connector, {
persistConversationData: true // need persistent data for dictionary
});
server.post('/api/messages', connector.listen());
// Create LUIS recognizer that points at our model and add it as the root '/' dialog
var model = (omitted);
var recognizer = new builder.LuisRecognizer(model);
var dialog = new builder.IntentDialog({ recognizers: [recognizer] });
bot.dialog('/', dialog);
// Add intent handlers
dialog.matches('Greeting', [
function(session, args, next) {
var language = builder.EntityRecognizer.findEntity(args.entities, 'Language');
next({ response: language });
},
function(session, results) {
bot.beginDialog({
text: 'Hello',
to: {channelId: "emulator", address:"User1", id:"(omitted)", isBot:false},
from: { channelId:"emulator", address:"Bot1", id:"(omitted)", isBot:true}
}, '/');
}
]);
However, currently when the bot receives a greeting, it gives the following error message:
ERROR: ChatConnector: startConversation - address is invalid.
Error: Invalid address.
at ChatConnector.startConversation (C:\..\node_modules\botbuilder\lib\bots\ChatConnector.js:173:18)
at C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:308:27
at UniversalBot.tryCatch (C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:381:13)
at UniversalBot.ensureConversation (C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:302:14)
at C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:163:19
at C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:337:53
at UniversalBot.tryCatch (C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:381:13)
at C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:337:23
at UniversalBot.tryCatch (C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:381:13)
at UniversalBot.lookupUser (C:\..\node_modules\botbuilder\lib\bots\UniversalBot.js:324:14)
(I've omitted part of directory)
I have looked at Issue #687 but still I couldn't figure out the problem. How can I make the bot work?
I am using Botbuilder v3.4.4 and Node v4.6.0.
The way to go here I think is:
Save the session.message.address somewhere as you will have to use it later in the bot.beginDialog you are doing.
Before beginning the new dialog, you need to remove the conversation object as you want to create a new conversation
Use the address to begin the dialog.
It would be something like
// consider making this an array insted
var address
// probably in the function that matches the greeting
address = session.message.address;
// in the step where you want to send the direct messsage
var newConversationAddress = Object.assign({}, address);
delete newConversationAddress.conversation;
// begin dialog with address without conversation
bot.beginDialog(newConversationAddress,...
Take a look to the CreateNewConversation sample. You will see that something pretty similar is being done.