botframework hosted in AWS API Gateway - aws-lambda

I have been trying to deploy my botframework bot in to AWS API Gateway. I am using the aws serverless express framework to host this application.
The only thing I can find is this conversation which gives some good ideas on how to get this working.
https://gitter.im/Microsoft/BotBuilder?at=57832060bdafd191075d92b3
The suggestion from the above was to pass to the request to listener function which work well
{ body:{}, headers: {} }
and use the following for the response where end can be a no-op.
{ status: function (code) {}, end: () {} }
My code that runs but doesn't receive a response from the bot is this.
var connectorListener = connector.listen();
function listen() {
return function (req, res) {
var response = res;
connectorListener(req, { status: (code) => { response.status(code); }, end: () => { response.end(););
}
}
If I remove res.end() and leave as a no op as suggested I get a response back from the chatbot in 1-2 seconds, but it also makes the lambda run for 30 seconds which is the timeout for API Gateway.
Is there a way to make this work so I can receive messages back without making the lambda timeout?

Related

Websocket request succeeds in writing to DynamoDB but returns Internal Server Error

I created an AWS API Gateway route for Websocket connections. I started with the AWS provided Simple Web Chat templates but have modified it to fit my needs. The API Gateway calls a Lambda function that writes to a DynamoDB table.
I am able to make a websocket connection but when I make my next request to insert some data the data appears successfully in my DynamoDB table but the response I get back is Internal Server Error.
I don't understand what is causing the Internal Server Error. When I look in the CloudWatch logs I just see normal traffic with no errors.
I could use some help understanding what is going wrong or how I can troubleshoot this better.
Here is the Lamba function that is being called:
const AWS = require("aws-sdk");
const customId = require("custom-id");
const ddb = new AWS.DynamoDB.DocumentClient({
apiVersion: "2012-08-10",
region: process.env.AWS_REGION,
});
exports.handler = async (event) => {
const uniqueId = customId({
randomLength: 1,
});
const data = {
uniqueId: uniqueId,
members: [
{
connectionId: event.requestContext.connectionId,
},
],
events: [],
parameters: [],
};
const putParams = {
TableName: process.env.EVENT_TABLE_NAME,
Item: data,
};
try {
await ddb.put(putParams).promise();
} catch (err) {
return {
statusCode: 400,
body: "Failed to create: " + JSON.stringify(err),
};
}
return { statusCode: 200, body: putParams };
};
Image of AWS CloudWatch Logs
The error returned by wcat looks like this:
{"message": "Internal server error", "connectionId":"NZxV_ddNIAMCJrw=", "requestId":"NZxafGiyoAMFoAA="}
I just had the same problem. The issue in my case was because API Gateway did not have permission to call the Lambda function in order to process a message arriving from the websocket. The 'internal server error' in this case is API Gateway saying it had some problem when it tried to invoke the Lambda function to handle the websocket message.
I was using CDK to deploy the infrastructure, and I created one WebSocketLambdaIntegration for the connect, disconnect and default websocket handlers, but this doesn't work. You have to create separate WebSocketLambdaIntegration instances even if you are calling the same Lambda function for all websocket events, otherwise CDK does not set the correct permissions.
I could see this was the problem because 1) I was using the same Lambda function for the connect, disconnect and default routes, and 2) in CloudWatch Logs I was only seeing log messages for one of these routes, in this case the 'connect' one. When I sent a message over the websocket, I was not seeing the expected log messages from the Lambda that was supposed to be handling incoming websocket messages. When I disconnected from the websocket, I did not see the expected log messages from the 'disconnect' handler.
This was because CDK had only given Lambda invoke permission to specific routes on the API Gateway websocket stage, and it had only authorised the 'connect' route, not the others.
Fixing the CDK stack so that it correctly assigned permissions, allowing API Gateway to invoke my Lambda for all websocket routes, fixed the problem.
I see it now. It was the last line. I changed it and now it works fine.
return { statusCode: 200, body: JSON.stringify(putParams) };

Can't get oauth token from google smart home action sync intent in aws lambda

I am using aws lambda function for google smart home action. I used aws api gateway for fulfillment url to reach lambda. I can successfully handle google assistant's intents with below code:-
const {smarthome} = require('actions-on-google');
const app = smarthome();
app.onExecute((body, headers) => {
return {
requestId: 'ff36...',
payload: {
// ...
},
};
});
app.onQuery((body, headers) => {
return {
requestId: 'ff36...',
payload: {
// ...
},
};
});
app.onSync((body, headers) => {
console.log("body: "+JSON.stringify(body));
console.log("headers: "+JSON.stringify(headers));
return {
requestId: 'ff36...',
payload: {
// ...
},
};
});
exports.handler = app;
On hard coding device details in this function, It can successfully reflect in google home app. But to get actual devices of user I need to get oauth token from "SYNC" intent. But all I got from this code is this output:-
body: {"inputs":[{"intent":"action.devices.SYNC"}],"requestId":"5604033533610827657"}
headers: {}
Unlike "Discover Directive" of Alexa's skill, which contains token in request.directive.endpoint.scope.token, google's intent doesn't seems to carry it. For O Auth, I am using AWS Cognito which works fine with Alexa Account linking and for google home too it can successfully link the account and show devices which I hardcode in lambda function.
As per this answer, the token is in
headers.authorization.substr(7)
I've tried that and got nothing. It shows
"Cannot read property 'substr' of undefined".
The lambda handler in the Actions on Google client library assumes that the request headers are present at event.headers within the input event parameter of a Lambda Proxy Integration. If you have a custom Lambda integration or have otherwise modified the input mapping, you may need to edit your mapping template to ensure the headers are placed where the client library expects.

Error from cloudfront in serverless framework lambda function

I am trying to deploy a simple Slack lambda api which uses the #slack/client library to remove members and pinned messages from a specific channel. The issue that I am running into is the function executes without a problem, and it is removing the channel members without a problem, but my Lambda function keeps returning:
HTTP/1.1 502 Bad Gateway
...
X-Cache: Error from cloudfront
...
{
"message": "Internal server error"
}
as the response body. When I check the logs using sls logs -f api, I dont see any errors there either. I see the console.log of my function successfully executing.
My serverless.yml is as follows:
provider:
name: aws
runtime: nodejs10.x
profile: serverless
functions:
api:
handler: handler.api
timeout: 30
events:
- http:
method: POST
path: clean
And my api code, i have removed the unnecessary function codes as they are doing their work, is :
module.exports.api = async (event, context, callback) => {
let channel = JSON.parse(event.body).ctf
let id = await findChannelId(channel)
removeMembersFromChannel(id[0]).then(() => {
removePinsFromChannel(id[0]).then(() => {
callback(null, {
statusCode: 200,
body: JSON.stringify({
message: `Cleaned ${channel} ${id}`,
}, null, 2),
})
})
})
};
Things I have tried:
returning the response instead of using the callback
using promises and async await
testing the function locally using sls invoke local
most of my search shows that this could be a permission issue, but all the references are for s3 usage which is something i am not using.
Questions
Why am I getting this error, and how I can resolve this?
After referenceing this In the handler function, I am using JSON.stringify. Using the serverless-framework, how can i avoid using Lambda proxy integration?
Please, add console.log for detailed logging via cloudwatch and use x-ray. Some typical problems with cloudfront:
- a lot of time to propagate to edge locations (maybe u need recreate your cdn)
- logs from lambda#edge locates in invoked region

Twilio awaits response, I don't want server to respond

I am using a Slack webhook to process incoming SMS messages from Twilio. However, the way I have it set up, It seems that Twilio is expecting the web server (slack) to respond to it. This causes errors to be generated in Twilio, and I obviously don't want errors because I'll be getting emails.
I am using the twilio-ruby gem in Ruby to send out the SMS messages, and using the slack-ruby-client to monitor incoming messages from Slack.
How do I stop Twilio from trying to expect a response from the web server when it POSTS to the Slack webhook? Is that even possible or do I have this all configured incorrectly?
EDIT
Here's the function that I have which sends the forwarded SMS to Slack:
const https = require("https");
// Make sure to declare SLACK_WEBHOOK_PATH in your Environment
// variables at
// https://www.twilio.com/console/runtime/functions/configure
exports.handler = (context, event, callback) => {
// Extract the bits of the message we want
const { To, From, Body } = event;
// Construct a payload for slack's incoming webhooks
const slackBody = JSON.stringify({
text: `!asi SMS\nFrom: ${From}\nMessage: ${Body}`
});
// Form our request specification
const options = {
host: "hooks.slack.com",
port: 443,
path: context.SLACK_WEBHOOK_PATH,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": slackBody.length
}
};
// send the request
const post = https.request(options, res => {
// only respond once we're done, or Twilio's functions
// may kill our execution before we finish.
res.on("end", () => {
// respond with an empty message
callback(null, new Twilio.twiml.MessagingResponse());
});
});
post.write(slackBody);
post.end();
};
Twilio developer evangelist here.
Twilio is always going to expect at least a 200 response or will timeout at 15 seconds for incoming message webhooks.
You could avoid the error messages by using something in between Twilio and Slack, like Zapier (example in this blog post) or using a Twilio Function (as described here) or with Twilio Studio (from the documentation here).
Hope one of those ideas helps!
Update
Further to my earlier answer, and given the code you used to make the call, I have an update.
When making a request using Node's built in https module you will not get the end event until you have read the data. This is what is causing the timeout between Twilio and the Twilio Function, you are never responding to it because you don't consume the data from the request.
In a quick test I found that just listening for the data event meant that the end event did fire. So update your function to:
const post = https.request(options, res => {
// only respond once we're done, or Twilio's functions
// may kill our execution before we finish.
res.on("data", () => {});
res.on("end", () => {
// respond with an empty message
callback(null, new Twilio.twiml.MessagingResponse());
});
});
And it should work.

How to send message back to device from Lambda function?

Is there any way we can send a message to a device from Lambda function which is invoked by Alexa Skill. The message contains some values collected by Lambda function.
So basically I want to do this:
Device ---> Voice command ---> Alexa Skill --(Trigger)--> Lambda function
Lambda function(collect values) ---- message ---> Device
Is there any example in Java?
Thanks for any pointer/help.
-James
Invoke Alexa device from lambda function is a very similar question, with the answer: "it's not possible YET"
I will elaborate. You can send notifications to all users of a skill such as a new feature, however, you cannot send a notification to a specific user that invokes a function.
To send notifications to all users of an Alexa skill who have notifications enabled, see this design.
Reference this thread for more information on the limitations of sending a notification to a specific user.
What you are asking can be done.
First the voice command does not come from a human from your diagram.
A device talks to Alexa. Alexa invokes or triggers Lambda. Lambda function messages device.
The function inside Lambda is http or https. If your device can handle https or TLS encryption then good. But most of the device are small and have limited computing power, so you will end up using http. As of now 2020, AWS allows http, but a year from now it requires you to use https or TLS 1.3 due to federal regulations. But we don't know until it happens.
Below is a sample of Lambda http post in NodeJS. The trigger data comes in request. So you should know what JSON will come in and extract your data from JSON using the if statement.
NodeJS website has good examples for http.
Now your device is the server. It has to anticipate the Lambda request and process it and reply to Lambda if needed.
Now your device talks and receives information.
const http = require('http');
exports.handler = async (request, context) => {
if (request.directive.header.namespace === 'FromAlexaSkill') {
httpost("This is your data to device", "192.168.1.2");
}
//**********************************************
let httpPost =async (postData, servername) => {
let dataString = '';
const response = await new Promise((resolve, reject) => {
const options = {
hostname: servername,
port: 1777,
path: '/dim',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, (res) =>
{
res.setEncoding('utf8');
res.on('data', chunk => {
dataString += chunk;
});
res.on('end', () => {
resolve({
"body":dataString
});
});
});//http.request
req.on('error', (e) => {
console.error("problem with request: "+e.message);
reject({
statusCode: 500,
body: 'Something went wrong!'
});
});
// Write data to request body
req.write(postData);
req.end();
}); //Promise
return response;
};//httpPost
}

Resources