I'm working with serverless, porting two functions, one (call it generator) that is currently a long-running node process and the other (call it checker) triggered by cron, so that they both would be lambdas, checker triggered by a schedule, and generator by receiving an SQS notification. I'm also a big fan of being able to do local dev, so added serverless-offline and serverless-offline-sqs (backed by ElasticMQ).
Ported the generator, and it deploys and runs just fine locally. Here's the function as defined in serverless.yml:
reportGenerator:
handler: src/reportGenerator.handleReportRequest
events:
- sqs:
arn:
Fn::GetAtt:
- ReportRequestQueue
- Arn
I can use the AWS CLI to trigger messages through ElasticMQ (aws account # obfuscated):
$ aws sqs send-message --queue-url https://sqs.us-west-1.amazonaws.com/000000000000/local-report-request --message-body file://./test/reportGenerator/simple_payload.json --endpoint-url http://localhost:9324
{
"MD5OfMessageBody": "267bd9129a76e3f48c26903664824c13",
"MessageId": "12034cd8-3783-403a-92eb-a5935c8759ae"
}
$
And the message is received fine, the generator lambda triggers:
{
"requestId": "ckokpja2e0001nbyocufzdrxk",
"function": "src/reportGenerator.js::exports:14",
"level": "info",
"message": "Found 0 waiting report request(s)"
}
offline: (λ: reportGenerator) RequestId: ckokpja2e0001nbyocufzdrxk Duration: 214.53 ms Billed Duration: 215 ms
Now I want to do that SQS send programmatically for the second function, which is pretty much the only real change to that existing (non-lambda) function. It is also deployed/launched just fine, but the aws-sdk library function to send the same message isn't working.
Here's the function definition in serverless.yml. Note that I commented out the normal cron schedule, since that isn't supported by serverless-offline, and just used the 10 minute rate for testing:
reportChecker:
handler: src/reportScheduler.check
environment:
REPORT_REQUEST_QUEUE_URL: "https://sqs.us-west-1.amazonaws.com/${self:custom.awsAccountId}/${opt:stage, self:custom.defaultStage}-report-request"
events:
# note that cron does not work for offline. Comment these out, and uncomment the rate to test locally
# - schedule:
# rate: cron(0 11 ? * 3 *)
# input:
# reportType: weekly
# - schedule:
# rate: cron(0 11 2 * ? *)
# input:
# reportType: monthly
- schedule:
rate: rate(10 minutes)
input:
reportType: weekly
Here's what I added to the rest of the function as the node version of the above command line:
const AWS = require('aws-sdk');
AWS.config.update({region: process.env.AWS_REGION});
...
try {
const sqs = new AWS.SQS({endpoint: 'http://localhost:9324'});
logger.debug('SQS info', {queueUrl: process.env.REPORT_REQUEST_QUEUE_URL, endpoint: sqs.endpoint});
await sqs.sendMessage({
QueueUrl: process.env.REPORT_REQUEST_QUEUE_URL,
MessageBody: `{"reportType":${checkType}}`
})
.promise();
}
catch(err) {
logger.error('SQS failed to send', {error: err});
}
The output from those logger statements is
{
"requestId": "ckokpulxw0002nbyo19sj87g2",
"function": "src/reportScheduler.js::exports:100",
"data": {
"queueUrl": "https://sqs.us-west-1.amazonaws.com/000000000000/local-report-request",
"endpoint": {
"protocol": "http:",
"host": "localhost:9324",
"port": 9324,
"hostname": "localhost",
"pathname": "/",
"path": "/",
"href": "http://localhost:9324/"
}
},
"level": "debug",
"message": "SQS info"
}
{
"requestId": "ckokpulxw0002nbyo19sj87g2",
"function": "src/reportScheduler.js::exports:109",
"data": {
"error": {
"message": "The specified queue does not exist for this wsdl version.",
"code": "AWS.SimpleQueueService.NonExistentQueue",
"time": "2021-05-12T00:20:00.497Z",
"requestId": "e55124c3-248e-5afd-acce-7dd605fe1fe5",
"statusCode": 400,
"retryable": false,
"retryDelay": 95.52839314049268
}
},
"level": "error",
"message": "SQS failed to send"
}
offline: (λ: reportChecker) RequestId: ckokpulxw0002nbyo19sj87g2 Duration: 477.95 ms Billed Duration: 478 ms
It sure looks to me like the code is using all the same values as the command line, but it just isn't working. Hoping someone else can see something obvious here.
Thanks.
Shouldn't the env variable REPORT_REQUEST_QUEUE_URL locally point to ElasticMQ? E.g. http://0.0.0.0:9324/queue/local-report-request.
Related
I have an action in IBM Cloud Functions that only receives one parameter: "frame". I'm using Postman to test the REST API endpoint provided with the action. However, when I provide the "frame" parameter it returns the following:
"response": {
"result": {
"error": "'frame'"
},
"status": "application error",
"success": false
}
I've experienced this problem when I invoke this action in the IBM Cloud Functions' console. I resolve it by erasing a space in the input modal and adding it again, then it works like a charm in the console. However, I can't do the same thing with an HTTP request.
The way I'm currently doing the HTTP request is like this:
POST https://us-south.functions.cloud.ibm.com/api/v1/namespaces/{namespace}/actions/{action_name}?blocking=true&frame={value}
The action should return the result I'm expecting but it doesn't do that right now. Please help me, any answers would be great!
EDIT:
This is the action's code:
import requests, base64, json, cv2
from PIL import Image
from six import BytesIO
def json_to_dict(json_str):
return json.loads(json.dumps(json_str))
def frame_to_bytes(frame):
frame_im = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pil_im = Image.fromarray(frame_im)
stream = BytesIO()
pil_im.save(stream, format="JPEG")
stream.seek(0)
img_for_post = stream.read()
img_base64 = base64.b64encode(img_for_post)
return img_base64
def main(dict):
cap = cv2.VideoCapture(dict['frame'])
if not cap.isOpened():
return { "error": "Unable to open video source" }
ret, frame = cap.read()
if ret is False:
return { "error": "Unable to read video source" }
# openALPR API part
OPENALPR_SECRET_KEY = {my_secret_key}
url = "https://api.openalpr.com/v2/recognize_bytes?recognize_vehicle=1&country=us&secret_key=%s" % (
OPENALPR_SECRET_KEY)
r = requests.post(url, data=frame_to_bytes(frame))
resp = json_to_dict(r.json())
print(resp)
if not resp['results']:
return { "error": "Plate number not recognized" }
plates = []
for plate in resp['results']:
if plate['confidence'] < 75:
pass
else:
print(plate['plate'])
plates.append(plate['plate'])
return { "plates": plates }
This is the activation response (the status returned was 502 Bad Gateway according to Postman):
{
"activationId": "5a83396b9f53447483396b9f53e47452",
"annotations": [
{
"key": "path",
"value": "{namespace}/{name}"
},
{
"key": "waitTime",
"value": 5531
},
{
"key": "kind",
"value": "python:3.7"
},
{
"key": "timeout",
"value": false
},
{
"key": "limits",
"value": {
"concurrency": 1,
"logs": 10,
"memory": 1024,
"timeout": 60000
}
},
{
"key": "initTime",
"value": 3226
}
],
"duration": 3596,
"end": 1560669652454,
"logs": [],
"name": "{name}",
"namespace": "{namesapce}",
"publish": false,
"response": {
"result": {
"error": "'frame'"
},
"status": "application error",
"success": false
},
"start": 1560669648858,
"subject": "{my_email}",
"version": "0.0.7"
}
EDIT 2:
I've also tried to enable it as a web action to see if it changes anything. However, it's no use. When I use this HTTP request:
https://us-south.functions.cloud.ibm.com/api/v1/web/{namespace}/default/{action_name}?frame={value}
I get:
{
"code": "e1c36666f4db1884c48f028ef58243fc",
"error": "Response is not valid 'message/http'."
}
which is understandable since what my functions returns is json. However, when I use this HTTP request:
https://us-south.functions.cloud.ibm.com/api/v1/web/{namespace}/default/{action_name}.json?frame={value}
I get:
{
"code": "010fc0efaa29f96b47f92735ff763f50",
"error": "Response is not valid 'application/json'."
}
I really don't know what to do here
After googling a bit I found something that works for me right now although it might not work for everyone. Apache has a python "client" example for using an action's REST API which uses the requests library.
Thing is that in order to use it you need to provide your API KEY, which I don't know how to get by any other means than getting it directly from the IBM Cloud CLI. Since I'm trying to access the function from a web server, I would need to save the key in an environment variable or save it in a text file and access it from there or install the CLI on the server, login with my credentials and call ibmcloud wsk property get --auth.
Also, this method didn't work with the web action endpoint when I tried it.
Problem
I am developing a Actions on Google app (intended for the Google Home), using the Actions on Google library for Node.js, hosted on Firebase Google Functions, via Dialogflow. I often, but irregularly (and hard to replicate), encounter an error, forcing the Actions on Google (simulator or Google Home itself) to shut down my app. I route everything from any Dialogflow intent (including fallbacks) to my webhook fulfilment, and - based on the log files - the webhook responds quickly (within ~200ms) and with valid responses (investigating the JSON responses). However, the Actions on Google seems to reject the response and triggers the Dialogflow default text response. My biggest concern, is that it happens at different stages in the conversation, sometimes already at the Welcome event. It is also noticeable that - even though the fulfillment responds in milliseconds (~200), the Actions on Google / Dialogflow takes its time and - what I believe - has a timeout. Below are my explorations into the potential cause. But frankly, I am out of ideas.
-- Edit --
The service seems to run better now - I haven't experience this error. I have changed the code to call admin.firestore() less often, by using a global databse and passing it through. I had a hunch that, potentially the https functions were called simultaneously which might have caused a malformed response somehow.
const database = admin.firestore();
// code
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'functionname') {
exports.functionname = functions.https.onRequest((req, res) => {
require('./functions').functionname(req, res, database);
});
}
I also found a flaw in my intent handeling, which caused no intent to be matched. I've restructured the Dialogflow intents to have less possibility of this no-intent (I basically made a two-step question to determine the user's intention). However, since the webhook did response correctly, I don't believe this was the problem. Still, I find it an odd error - so if anyone has more pointers please!
-- end edit --
Logs
This is a response from the webhook as seen from the Google Functions Log - indicating a send response to the Dialogflow app.
Function execution took 289 ms, finished with status code: 200
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [{
"simpleResponse": {
"textToSpeech": "Welcome back! Which Widget do you want to work with?"
}
}]
}
}
},
"outputContexts": [{
"name": "****anonymized****/contexts/widget",
"lifespanCount": 1
},
{
"name": "***anonymized****/contexts/_actions_on_google",
"lifespanCount": 99,
"parameters": {
"data": "{\"started\":1552071670}"
}
}
]
}
}
However, this is what the Actions on Google console shows. The textToSpeech response is the default text response where DialogFlow can fall back to in case the fulfilment fails (as suggested by https://medium.com/google-developers/debugging-common-actions-on-google-errors-7c8527378d27)
{
"conversationToken": "[]",
"finalResponse": {
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Sorry! I cannot access my online service. Please try again!"
}
}
]
}
},
"responseMetadata": {
"status": {
"code": 14,
"message": "Webhook error (206)"
},
"queryMatchInfo": {
"queryMatched": true,
"intent": "e0bf4b96-9440-4545-a8a6-d0915cacd34f"
}
}
}
The stackdriver logs also indicate the latter case, where the default response from Dialogflow is received.
Thoughts
I have tried to replicate this error on the simulator, on my phone and on a Google Home. It occurs at different moments, though seems to occur more frequently than before. My three hunches are the following:
Overload. My Google Functions hosts 8 functions, including the Dialogflow app. Perhaps if the other functions are also called frequently at a given moment, something goes wrong with the Dialogflow app handeling the https requests from Dialogflow (the Google Assistant). However, I do not see how this should happen. I could replicate (sometimes) the problem by invoking the app on two devices with two different accounts. But since it also happens with one account I don't see this can be the only problem. I've followed this tip to reduce overload (https://stackoverflow.com/a/47985480/7053198) but that did not seem to help my issue. Can then the upscaling of Google Functions be a problem here?
Promise hell. I am using a number of promises (or more callbacks) to communicate with a Firestore database to retreive user data. However, I can invoke these intents quite rapidly, and they resolve nicely - up to a point where they don't. Below is a snippet of such one intent.
Size of my app. The number of Intents has gotten quite large, and there is a lot of communication between the Google Functions and the Firestore database. I just don't know how this might influence the rejection of the seemingly correct response by Dialogflow.
Intent with promises snippet:
app.intent(I.WIDGETTEST, conv => {
let database = admin.firestore();
let offline = [];
return database.collection('users').doc(conv.user.storage.userId).collection('widgets').get()
.then((snapshot) => {
const promises = [];
snapshot.forEach(doc => {
if (doc.data().active) {
if ((moment().unix()-doc.data().server.seen) > 360){
offline.push(doc.data().name);
}
promises.push(doc.ref.update({'todo.test': true}));
}
});
return Promise.all(promises);
})
.then(()=>{
conv.contexts.set(C.WIDGET, 1);
return conv.ask("Testing in progress. Which Widget do you want to work with now?");
})
.catch((err) => {
console.log(err)
throw err;
});
});
Problem Example Log
For good measure, here three log entries from the stackdriver side of things:
{
"textPayload": "Sending request with post data: {\"user\":{\"userId\":\"***anonymized***\",\"locale\":\"en-US\",\"lastSeen\":\"2019-03-08T18:54:47Z\",\"userStorage\":\"{\\\"data\\\":{\\\"userId\\\":\\\"***anonymized***\\\"}}\",\"idToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImNmMDIyYTQ5ZTk3ODYxNDhhZDBlMzc5Y2M4NTQ4NDRlMzZjM2VkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE1NTIwNzEzNjAsImF1ZCI6Ijk4NzgxNjU4MTIzMC1ibWVtMm5wcTRsNnE3c2I5MnVpM3BkdGRhMWFmajJvNy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwMjk3OTk3NzIyNTM3NjY4MDEyOSIsImVtYWlsIjoiZGF2aWR2ZXJ3ZWlqQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiRCBWZXJ3ZWlqIiwicGljdHVyZSI6Imh0dHBzOi8vbGg2Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8taldtOFd3eE5iS3MvQUFBQUFBQUFBQUkvQUFBQUFBQUFyLUUvNEE2UmJiYVNwem8vczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IkQiLCJmYW1pbHlfbmFtZSI6IlZlcndlaWoiLCJpYXQiOjE1NTIwNzE2NjAsImV4cCI6MTU1MjA3NTI2MCwianRpIjoiYjE4MDYwMjc0YmE4MjJhYzFhYzc0MTYwZjI2YWM2MDk3MzBmZDY4ZSJ9.Y9G0qo0Gf28-noF7RYPhtfHRuA7Qo6bCBSuN56Y0AtgIXaQKZjnmYvABIt9u8WQ1qPWwQc3jOLyhfoXIk8j0zhcQ0M0oc7LjkBwVCgFnJHvUAiV5fGEqQa95pZyrZhYmHipTDdwk0UhJHFGJOXAHDPP6oBSHKC9h48jqUjVszz6iEy4frV0XIKIzRR2U2iY6OgJuxPsV0A7xNjvLXiMmwaRUVtlj9CPmiizd3G2PhqD5C54Fy2Qg5ch89qMOA10vNB5B4AX9pmAXHpmtIqFo7ljvAeGAj-pRuqyMllz2awAdvqqOFRERDYfm5Fyh7N0l1OhR2A2XRegsUIL1I1EVPQ\"},\"conversation\":{\"conversationId\":\"ABwppHH8PXibDZg8in1DjbP-caFy67Dtq025k_Uq2ofoPNXKtiPXrbJTmpGVUnVy-aY6H1MeZCFIpQ\",\"type\":\"NEW\"},\"inputs\":[{\"intent\":\"actions.intent.MAIN\",\"rawInputs\":[{\"inputType\":\"KEYBOARD\",\"query\":\"Talk to ***anonymized appname***\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"}]}],\"requestType\":\"SIMULATOR\"}.",
"insertId": "120zsprg2nqfayb",
"resource": {
"type": "assistant_action",
"labels": {
"action_id": "actions.intent.MAIN",
"project_id": "***anonymized***",
"version_id": ""
}
},
"timestamp": "2019-03-08T19:01:00.243055001Z",
"severity": "DEBUG",
"labels": {
"channel": "preview",
"source": "AOG_REQUEST_RESPONSE",
"querystream": "GOOGLE_USER"
},
{
"textPayload": "Received response from agent with body: HTTP/1.1 200 OK\r\nServer: nginx/1.13.6\r\nDate: Fri, 08 Mar 2019 19:01:10 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nContent-Length: 330\r\nX-Cloud-Trace-Context: 7761b69610701e0a7d18cbc69eef9bde/3001560133061614360;o=0\r\nGoogle-Actions-API-Version: 2\r\nAccess-Control-Allow-Credentials: true\r\nVia: 1.1 google\r\nAlt-Svc: clear\r\n\r\n{\"conversationToken\":\"[]\",\"finalResponse\":{\"richResponse\":{\"items\":[{\"simpleResponse\":{\"textToSpeech\":\"Sorry! I cannot access my online service. Please try again!\"}}]}},\"responseMetadata\":{\"status\":{\"code\":14,\"message\":\"Webhook error (206)\"},\"queryMatchInfo\":{\"queryMatched\":true,\"intent\":\"e0bf4b96-9440-4545-a8a6-d0915cacd34f\"}}}.",
"insertId": "120zsprg2nqfayc",
"resource": {
"type": "assistant_action",
"labels": {
"project_id": "***anonymized***",
"version_id": "",
"action_id": "actions.intent.MAIN"
}
},
"timestamp": "2019-03-08T19:01:10.376894030Z",
"severity": "DEBUG",
"labels": {
"channel": "preview",
"source": "AOG_REQUEST_RESPONSE",
"querystream": "GOOGLE_USER"
},
"logName": "projects/***anonymized***/logs/actions.googleapis.com%2Factions",
"trace": "projects/987816581230/traces/ABwppHH8PXibDZg8in1DjbP-caFy67Dtq025k_Uq2ofoPNXKtiPXrbJTmpGVUnVy-aY6H1MeZCFIpQ",
"receiveTimestamp": "2019-03-08T19:01:10.389139428Z"
},
{
"textPayload": "MalformedResponse: Webhook error (206)",
"insertId": "1d4bzl9g3lossug",
"resource": {
"type": "assistant_action",
"labels": {
"project_id": "***anonymized***",
"version_id": "",
"action_id": "actions.intent.MAIN"
}
},
"timestamp": "2019-03-08T19:01:10.377231474Z",
"severity": "ERROR",
"labels": {
"channel": "preview",
"source": "JSON_RESPONSE_VALIDATION",
"querystream": "GOOGLE_USER"
},
"logName": "projects/***anonymized***/logs/actions.googleapis.com%2Factions",
"trace": "projects/987816581230/traces/ABwppHH8PXibDZg8in1DjbP-caFy67Dtq025k_Uq2ofoPNXKtiPXrbJTmpGVUnVy-aY6H1MeZCFIpQ",
"receiveTimestamp": "2019-03-08T19:01:10.388395945Z"
}
Help!
Any help or guidance towards unraveling this issue is much apreciated. Let me know if you have experienced this before, have a potential solution or would like to see more details of the code or logs. Many thanks!
I got the same error when I tried to modify one of my google assistant app.
Everything was working well but I suddently got the same error as you that I was not able to understand and was not related to my new developpement.
{
insertId: "102mhl8g1omvh70"
labels: {
channel: "preview"
querystream: "GOOGLE_USER"
source: "JSON_RESPONSE_VALIDATION"
}
logName: "projects/myprojectID/logs/actions.googleapis.com%2Factions"
receiveTimestamp: "2019-04-22T16:56:11.508733115Z"
resource: {
labels: {
action_id: "actions.intent.MAIN"
project_id: "myprojectID"
version_id: ""
}
type: "assistant_action"
}
severity: "ERROR"
textPayload: "MalformedResponse: Webhook error (206)"
timestamp: "2019-04-22T16:56:11.498787357Z"
trace: "projects/168413137357/traces/ABwppHG8ckJJgXMT5Jedih2WUtGNZZc9i0BVG5S-CkxCT8mkhy7mDr8L9GPd9p_EvXIIlTz3SK2z16jBK8Id"
}
I tried to solve it by disabling the webhook and set the playload in the dialogflow intent. Anyway I still got this error.
So I tried to rollback on my developpement by uploading the old version the incremeneted intent but this didn't work and I still got my error
Watching to my function logs I saw this
SyntaxError: Unexpected token : in JSON at position 6
at Object.parse (native)
at new User (/user_code/node_modules/actions-on-google/dist/service/actionssdk/conversation/user.js:75:43)
at DialogflowConversation.Conversation (/user_code/node_modules/actions-on-google/dist/service/actionssdk/conversation/conversation.js:47:21)
at DialogflowConversation (/user_code/node_modules/actions-on-google/dist/service/dialogflow/conv.js:36:9)
at WebhookClient.conv (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:415:14)
at welcome (/user_code/index.js:37:26)
at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:273:44)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:247:11)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:37:41)
at /var/tmp/worker/worker.js:783:7
I didn't understand how the json from the assistant could not be parsed properly I was very surprised. I compared the JSON input that I got before and after the bug and I saw that the userstorage entry was not well formated.
I got something like that :
"user":{"userStorage":"\"data\":}","lastSeen":"2019-04-22T16:22:10Z","locale":"Mylanguage","userId":"ABwppHGoZFMQwTpdqZaOYBaIh32-hXH2kUN_EPJglUXMl5MeH7QlLyB9O0bCgZ0SQ9L0B2Ks-JQ9rHfRoAh-"}
instead of this :
"user":{"userStorage":"{\"data\":{}}","lastSeen":"2019-04-21T17:55:21Z","locale":"Mylanguage","userId":"ABwppHGoZFMQwTpdqZaOYBaIh32-hXH2kUN_EPJglUXMl5MeH7QlLyB9O0bCgZ0SQ9L0B2Ks-JQ9rHfRoAh-"}
And this json is going to provoke an error when you try to get the conversation from the agent such like that :
let conv = agent.conv();
I still don't understand how this is even possible but I have a trick to solve it.
I fix it by correcting the json before getting the conversation.
In my app I don't need any userstorage so I always intialise the userstorage properly before getting the conversation like that :
request.body.originalDetectIntentRequest.payload.user.userStorage = "{\"data\":{}}";
I am about to contact the dialogflow support to inform them of this I consider to be a bug
I hope this answer is helping you!
After setting up a bot-connector (SAP Conversational AI: https://github.com/SAPConversationalAI/bot-connector), creating a channel for the fb-messenger and configuring a webhook in the fb-app i constantly get http-status 401 (unauthorized) in my bot-connector log when sending a message from my fb-messenger.
Here is what i've done so far: (i fallowed this guide: https://github.com/SAPConversationalAI/bot-connector/wiki/Channel---Messenger)
deployed bot-connector to google cloud as app engine service to get the [bot-connector url]
created bot-connector:
POST on [bot-connector url]/v1/connectors with body:
{"url":[bot-connector endpoint url]}
POST-response:
{
"results": {
"id": [connector id],
"url": [bot-connector endpoint url],
"isTyping": true,
"conversations": [],
"channels": []
},
"message": "Connector successfully created"
}
created channel for fb-messenger:
POST on [bot-connector url]/v1/connectors/[connector id]/channels with body:
{
"slug":"channel-messenger-srtbot-dev",
"type":"messenger",
"token":[fb-app secret],
"apiKey":[fb-app api-key],
"isActivated":true
}
POST response:
{
"results": {
"id": [connector id],
"updatedAt": "2019-01-22T15:03:29.569Z",
"createdAt": "2019-01-22T15:03:29.569Z",
"webhook": "/v1/webhook/[channel id]",
"slug": [name of my channel],
"type": "messenger",
"token": [fb-app secret],
"apiKey": [fb-app api-key],
"connector": [connector id],
"locales": [],
"openingType": "never",
"hasGetStarted": false,
"forwardConversationStart": false,
"isActivated": true,
"isErrored": false,
"webhookToken": [fb webhook token]
},
"message": "Channel successfully created"
}
configured fb-messenger-webhook: (after completing the config i get the http-status 200 in the bot-connector log)
callback-url: [bot-connector url]/v1/webhook/[channel id]
token: [fb webhook token]
subscribed webhook to my fb-page
sent a message in my fb-messenger
Expected results:
after i send a message in the fb-messenger:
http-status 200 in the bot-connector log
messages gets forwarded to [bot-connector endpoint url]
bot responds to the message (i get an answer in the fb-messenger)
Actual results:
after i send a messag in the fb-messenger:
http-status 401 (unauthorized) in the bot-connector log (approximately every 60 seconds after the first fb-messenger message)
nothing else happens
i get no answer in the fb-messenger
When creating the channel, the app secret should be passed as the apiKey attribute and the page token as the token attribute. When the channel is created, it will have a webhookToken attribute. This should be used as the verify token when setting up the webhook on Facebook.
So instead what you should be passing to create the channel, should look like this:
{
"slug":"channel-messenger-srtbot-dev",
"type":"messenger",
"token":[fb-page token],
"apiKey":[fb-app secret],
"isActivated":true
}
I'm having issue when calling S3.waitFor() function from inside Lambda function (Serverless nodejs). I'm trying to asynchronously write a file into Amazon S3 using S3.putObject() from one rest api, and poll the result file from another rest api using S3.waitFor() to see if the writing is ready/finished.
Please see the following snippet:
...
S3.waitFor('objectExists', {
Bucket: bucketName,
Key: fileName,
$waiter: {
maxAttempts: 5,
delay: 3
}
}, (error, data) => {
if (error) {
console.log("error:" + JSON.stringify(error))
} else {
console.log("Success")
}
});
...
Given valid bucketName and invalid fileName, when the code runs in my local test script, it returns error after 15secs (3secs x 5 retries) and generates result as follows:
error: {
"message": "Resource is not in the state objectExists",
"code": "ResourceNotReady",
"region": null,
"time": "2018-08-03T06:08:12.276Z",
"requestId": "AD621033DCEA7670",
"extendedRequestId": "JNkxddWX3IZfauJJ63SgVwyv5nShQ+Mworb8pgCmb1f/cQbTu3+52aFuEi8XGro72mJ4ik6ZMGA=",
"retryable": true,
"statusCode": 404,
"retryDelay": 3000
}
Meanwhile, when it is running inside AWS lambda function, it returns result directly as follows:
error: {
"message": "Resource is not in the state objectExists",
"code": "ResourceNotReady",
"region": null,
"time": "2018-08-03T05:49:43.178Z",
"requestId": "E34D731777472663",
"extendedRequestId": "ONMGnQkd14gvCfE/FWk54uYRG6Uas/hvV6OYeiax5BTOCVwbxGGvmHxMlOHuHPzxL5gZOahPUGM=",
"retryable": false,
"statusCode": 403,
"retryDelay": 3000
}
As you can see that the retryable and statusCode values are different between the two.
On lamba, it seems that it always get statusCode 403 when the file doesn't exists. While on my local, everything works as expected (retried 5 times every 3 seconds and received statusCode 404).
I wonder if it has anything to do with IAM role. Here's my IAM role statements settings inside my serverless.yml:
iamRoleStatements:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "ec2:CreateNetworkInterface"
- "ec2:DescribeNetworkInterfaces"
- "ec2:DeleteNetworkInterface"
- "sns:Publish"
- "sns:Subscribe"
- "s3:*"
Resource: "*"
How to make it work from lambda function?
Thank you in advance!
It turned out that the key is on how you set the IAM Role for the bucket and all the objects under it.
Based on the AWS docs here, it states that S3.waitFor() is relying on the underlying S3.headObject().
Waits for the objectExists state by periodically calling the underlying S3.headObject() operation every 5 seconds (at most 20 times).
Meanwhile, S3.headObject() itself relies on HEAD Object API which has the following rule as stated on AWS Docs here:
You need the s3:GetObject permission for this operation. For more information, go to Specifying Permissions in a Policy in the Amazon Simple Storage Service Developer Guide. If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.
If you have the s3:ListBucket permission on the bucket, Amazon S3
will return a HTTP status code 404 ("no such key") error.
if you don’t have the s3:ListBucket permission, Amazon S3 will return
a HTTP status code 403 ("access denied") error.
It means that I need to add s3:ListBucket Action to the Bucket resource containing the objects to be able to get response 404 when the objects doesn't exist.
Therefore, I've configured the cloudformation AWS::IAM::Policy resource as below, where I added s3:Get* and s3:List* action specifically on the Bucket itself (i.e.: S3FileStorageBucket).
"IamPolicyLambdaExecution": {
"Type": "AWS::IAM::Policy",
"DependsOn": [
"IamRoleLambdaExecution",
"S3FileStorageBucket"
],
"Properties": {
"PolicyName": { "Fn::Join": ["-", ["Live-RolePolicy", { "Ref": "environment"}]]},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect":"Allow",
"Action": [
"s3:Get*",
"s3:List*"
],
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "S3FileStorageBucket"
}
]
]
}
},
{
"Effect":"Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "S3FileStorageBucket"
},
"/*"
]
]
}
},
...
Now I've been able to do S3.waitFor() to poll for file/object under the bucket with only a single API call and get the result only when it's ready, or throw error when the resource is not ready after a specific timeout.
That way, the client implementation will be much simpler. As it doesn't have to implement poll by itself.
Hope that someone find it usefull. Thank you.
I have a nodejs app function that is triggered via HTTP. It calls a cosmosdb stored procedure, sets some output bindings for event hub, then returns to the user.
The issue is the function is working properly, but for some reason it ALWAYS restarts before returning to the user, which even after making the stored procedure handle such event, the function then never returns. In postman it always returns a generic azure 502. In the testing UI it eventually seems to timeout and has a response of 500 with no body.
When I run this via the azure function testing, it always seems to restart mid-way through. Almost always it gets to the very end and even calls Context.done(). I know this because the event hub events are being sent and trigger other functions. HOWEVER, instead of returning the HTTP res to the user, it restarts the app, goes through everything, and either throws an error, which is logged in the .catch but NEVER returns from the HTTP call. It eventually just hits the timeout and returns a 500 or 502.
Even more odd, when looking at the entry logs under the 'monitor' tab, only the second run exists but shows it executed successfully! I've tried running the function via Azure function testing and via postman and the result is the same.
The function used to return a promise, which I've had issues with in the past, so I've converted to calling context.done() with no luck. What is causing this?
Here is an example run that eventually returns a 500 (taken from the function testing log stream):
2018-03-27T17:27:57.636 [Info] Function started (Id=b4c64acc-3120-4dbd-b067-034ef7e6b548)
2018-03-27T17:28:00.436 [Info] 1
2018-03-27T17:28:07.167 [Info] Function started (Id=59ef098a-8ce0-43fa-9a98-c2043f40566a)
2018-03-27T17:28:10.452 [Info] 1
2018-03-27T17:28:10.881 [Info] 2
2018-03-27T17:28:10.886 [Info] 3
2018-03-27T17:29:22 No new trace in the past 1 min(s).
2018-03-27T17:30:22 No new trace in the past 2 min(s).
2018-03-27T17:31:22 No new trace in the past 3 min(s).
2018-03-27T17:32:22 No new trace in the past 4 min(s).
And one that gets all the way to context.done():
2018-03-27T17:33:56.887 [Info] Function started (Id=7e0c9d3d-549b-4f7f-905b-454d69e18cca)
2018-03-27T17:34:00.902 [Info] 1
2018-03-27T17:34:01.324 [Info] 2
2018-03-27T17:34:01.339 [Info] 3
2018-03-27T17:34:02.121 [Info] 4
2018-03-27T17:34:02.121 [Info] 5
2018-03-27T17:34:02.121 [Info] 6
2018-03-27T17:34:08.563 [Info] Function started (Id=ffcdf487-d1e6-4422-856f-ed709470605b)
2018-03-27T17:34:12.090 [Info] 1
2018-03-27T17:34:12.530 [Info] 2
2018-03-27T17:34:12.530 [Info] 3
2018-03-27T17:35:22 No new trace in the past 1 min(s).
2018-03-27T17:36:22 No new trace in the past 2 min(s).
2018-03-27T17:37:22 No new trace in the past 3 min(s).
2018-03-27T17:38:22 No new trace in the past 4 min(s).
2018-03-27T17:39:22 No new trace in the past 5 min(s).
2018-03-27T17:40:22 No new trace in the past 6 min(s).
I have tried restarting the function app which didn't seem to help. This is a consumption plan app if it matters.
The function is as follows:
const DB = require('../Shared/DB');
const Errors = require('../Shared/Errors');
const Auth = require("../Shared/Auth");
const Approvals = require("../Shared/Approval");
module.exports = function (context, req) {
let user;
let found;
let outMessage = 'CheckoutRequested';
let outMessageObj = {'id': req.params.id};
return Auth.get_user_from_request(req).then( (u) => {
context.log(1);
user = u;
outMessageObj['user'] = u.raw();
if (!req.params.id) throw new Errors.BadRequestError("Id is a required parameters.");
const db = new DB();
// Call stored procedure;
return db.check_out(req.params.id, user);
}).then( (res) => {
context.log(2);
found = res;
outMessageObj['found'] = found;
context.log(3);
return Approvals.get_approvers(user, found);
}).then(approvers => {
context.log(4);
// Determine the type of event hub message to send.
if (approvers.length == 0){
outMessage = 'CheckoutApproved';
}
outMessageObj['approvers'] = approvers;
context.bindings[outMessage] = outMessageObj;
context.log(5);
context.bindings.res = {
status: 200,
body: {
"found": found,
"user": user
}
};
context.bindings.output = context.bindings.res;
context.log(6);
context.done();
}).catch( (error) => {
context.log('error');
context.log.error(error);
if (outMessageObj.user){
context.bindings['CheckoutFailed'] = outMessageObj;
}
context.bindings.res = {
status: 500,
body: {
"error": error.message,
}
};
context.done();
});
};
I've got 3 output bindings on 3 different partitions based on the output:
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"route": "checkout/{id}",
"methods": [
"put"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "eventHub",
"name": "CheckoutApproved",
"connection": "********",
"path": "CheckoutApproved",
"direction": "out"
},
{
"type": "eventHub",
"name": "CheckoutRequested",
"connection": "********",
"path": "CheckoutRequested",
"direction": "out"
},
{
"type": "eventHub",
"name": "CheckoutFailed",
"connection": "********",
"path": "CheckoutFailed",
"direction": "out"
}
],
"disabled": false
}
EDIT: To add to this, I've tried setting the output to all the context.res, context.bindings.res, context.done(null, response object), and returning a promise.
Have you tried returning your status code in context.res?
Something like this
context.res = {
status: 200,
body: items
};