I am trying to handle a modal submission in slack, but there are some database operations in between which are taking a few seconds of time, due to this delay, I am getting: We had some trouble connecting error when submitting slack dialog (Slack API)
I know in node.js we can do something like this:
app.post('/', async (req, res){
res.status(200).send({text: 'Acknowledgement received !'});
// handle other task
return res.json({done: 'Yipee !'})
})
But in AWS Lambda function, I have no idea how will I handle this acknowledgement response in 3 sec.
module.exports.events = async (event, context, callback) => {
??? -> How to handle acknowledgement here, it must be handled at top.
// handle task
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({text: 'Done !'})
}
}
If all you want to do is to get notified for a successfull invocation and then have the lambda keep doing its own thing you can invoke lambda asynchronously by setting the InvocationType parameter to Event. https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestSyntax
Slack's API can be difficult to handle with a serverless architecture, as most serverless implementations like the response to be the last thing they do, rather than the first. One approach would be to wrap any required behaviour inside a promise, and only resolve that promise once you have handled the task. See here for an example of this.
Related
_
MY CHALLENGE:
I would like to access a third party Rest API from within my Lambda function. (e.g."http://www.mocky.io/v2/5c62a4523000004a00019907").
This will provide back a JSON file which I will then use for data extraction
_
MY CURRENT CODE:
var http = require('http');
exports.handler = function(event, context, callback) {
console.log('start request to Mocky');
http.get('http://www.mocky.io/v2/5c62a4523000004a00019907', function(res) {
console.log(res);
})
.on('error', function(e) {
console.log("Got error: " + e.message);
});
};
This does not throw an error but also does not seem to provide back the JSON
_
MY OPEN QUESTIONS:
1) How can I extract the JSON so that I can work on it
2) I will probably need to also send through an Authentification in the request header (Bearer) in the future. Will this also be possible with this method?
The problem is likely that your lambda function is exiting before logging the response.
We use Authorization headers all the time to call our lambdas. The issue of if you can use one to call the third party API is up to them, not you, so check the documentation.
Since your HTTP call is executed asynchronously, the execution of the lambda continues while that call is being resolved. Since there are no more commands in the lambda, it exits before your response returns and can be logged.
EDIT: the http.get module is difficult to use cleanly with async/await. I usually use superagent, axios, or request for that reason, or even node-fetch. I'll use request in my answer. If you must use the native module, then see EG this answer. Otherwise, npm install request request-promise and use my answer below.
The scheme that many people use these days for this kind of call uses async/await, for example (Requires Node 8+):
var request = require('request-promise')
exports.handler = async function(event, context, callback) {
console.log('start request to Mocky');
try {
const res = await request.get('http://www.mocky.io/v2/5c62a4523000004a00019907')
console.log(res)
callback(null, { statusCode: 200, body: JSON.stringify(res) })
}
catch(err) {
console.error(err.message)
callback('Got error ' + err.message)
}
};
The async/await version is much easier to follow IMO.
Everything inside an async function that is marked with await with be resolved before the execution continues. There are lots of articles about this around, try this one.
There are a lot of guys having an equal problem already solved... Look at that
or that
-- The background to situation --
I'm making an e-form signup for a client of our business marketing strategy service blah blah blah...
the form is done and looks great. now I need to hook it up to the existing API of the service our business uses to hold/sort/query/etc the submitted information.
I'm a very junior developer and the API is very complex. I just want to make sure my ES6/javascript is in proper working order. The ajax calls are working, no bugs in my code etc. So it seemed the quickest easiest thing to do in order to test things was just make a simple local server so I can test my calls get everything working BEFORE I start going through tons of API documentation and getting it properly hooked up to our service. The first call seems to work fine. But I couldn't get my lil' baby server to "respond" properly with some static info to parse through. I'm primarily a front-end developer, but I'm obsessed with figuring this little server problem out at this point... So help would be VERY appreciated.
-- the fetch request --
fetch('http://localhost:4000/send-zip')
.then(
(response) => {
response.json(),
console.log('begin fetch to local server'),
console.log(response),
populate_store_selector(response)
})
.catch(
(error)=> console.log('basic fetch request failed' + error)
)
-- that other function in case people ask --
(it is simply meant to iterate through and populate an html
input type="select" )
function populate_store_selector(arg) {
for (i of arg) {
let new_option = document.createElement('option')
new_option.innerHTML = arg[i]
select_shop.appendChild(new_option)
}
}
-- my little baby server --
const express = require('express')
const server = express()
server.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
server.get('/send-zip', function (req, res) {
res.send({ "options": ['option1', 'option2', 'option3'] })
})
server.listen(4000, () => console.log('mock server listening on port 4000!'))
the server works just fine and does it's job OTHER than I'm never able to get it to send a JSON object back :(
I've tried lots of things so far. Honestly it doesn't matter as my request on the front end works just fine but I'm too obsessed to let this go right now...
-- what the console.log() shows in the browser --
begin fetch to local server
main.js:104
Response {type: "cors", url: "http://localhost:4000/send-zip", redirected: false, status: 200, ok: true, …}body: (...)bodyUsed: trueheaders: Headers {}ok: trueredirected: falsestatus: 200statusText: "OK"type: "cors"url: "http://localhost:4000/send-zip"__proto__: Response
main.js:108
basic fetch request failedTypeError: arg is not iterable
You might try parsing the response after the response stream completes, and then taking action on the data.
fetch('http://localhost:4000/send-zip')
.then(
(response) => {
return response.json();
}).then(
(response_json) => {
console.log('begin fetch to local server'),
console.log(response_json),
populate_store_selector(response_json)
})
.catch(
(error)=> console.log('basic fetch request failed' + error)
)
The reason that you need to include the additional .then step and return response.json() is that the http response object returns the body data as a readable stream.
The JSON function is designed to accept a stream and convert it into JSON after the stream completes. This may feel somewhat unintuitive for anyone familiar with axios or other AJAX convenience libraries, as that part of the process is abstracted from view.
What this basically means is that after you wait for the response http object to be returned, you need to wait again for the stream to also complete.
There are a few different methods available which can act upon a stream upon completion including arrayBuffer,blob, and text (there are a few more I think as well). Usually they tend to convert the data into the format you prefer after it has completed.
I am sending a post request this way(the baseURL is correct, the path /api/backend/valuePairs exists on the server).
sendValues(valuepairList:{x:number;fx:number}[]): Observable<boolean> {
const headers = new Headers({'Content-Type': 'application/json'});
const options = new RequestOptions({headers: headers});
console.log("sending to server: ",JSON.stringify(valuepairList)); //this seems ok too
return this.http.post(this.baseUrl + '/api/backend/valuePairs', {'data': JSON.stringify(valuepairList)}, options)
.map(FunctionapproximationService.extractData)
.catch(FunctionapproximationService.handleError);
}
But looking in Chrome at the website in Inspect mode / Networks nothing is sent during the execution of this function.(It is executed, the logging inside the function appears). Does someone has idea about what I am doing wrong? Thank you.
Nothing is sent because observables are lazy by default. You need to subscribe to them in order to invoke the HTTP request.
sendValues(args).subscribe(result => console.log(result))
The function passed to subscribe function will be called when the response arrives. It also accepts other functions for error handling and one that is called when the stream is completed, but I leave that to you to figure out from the documentation.
In RxJS worl, .subscribe() is basically like calling a usual JS function with parans: (). To execute a function, you need to call it. To execute an observable, you need to subscribe to it.
An AngularJS version 1.4.8 app is getting an unhandled 403 error when its login form sends data to a backend REST authentication service after the user's browser has been left open for many (16 in this case) hours. Upon deeper analysis, the root cause is that the client AngularJS app has outdated cookies for XSRF-TOKEN and JSESSIONID, which causes the backend Spring Security to reject the request to the public /login1 service because Spring thinks the request is cross site request forgery.
The problem can be resolved manually if the user closes all browser windows and then re-opens a new browser window before making the request again. But this is not an acceptable user experience. I have read the AngularJS documentation at this link, and I see that I can add an errorCallback function, but how specifically should i re-write the function to handle the 403 error?
Here is the original this.logForm() method in the authorization service, which you can see does not handle 403 errors:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http.post('/login1', usercredentials)
.then(
function(response, $cookies) {
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
}
);
}
};
Here is my very rough attempt at a revised version of the this.logForm() method attempting to handle a 403 error following the example in the AngularJS documentation:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http({ method: 'POST', url: '/login1', usercredentials })
.then(
function successCallback(response, $cookies) {
// this callback will be called asynchronously when the response is available
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
},
function errorCallback(response, status) {// is status a valid parameter to place here to get the error code?
// called asynchronously if an error occurs or server returns response with an error status.
if(status == 403){
this.clearCookies();
// try to call this POST method again, but how? And how avoid infinite loop?
}
}
);
}
};
What specific changes need to be made to the code above to handle the 403 error due to server-perceived XSRF-TOKEN and JSESSIONID issues? And how can the post be called a second time after deleting the cookies without leading to an infinite loop in the case where deleting the cookies does not resolve the 403 error?
I am also looking into global approaches to error handling, but there is a combination of public and secure backend REST services, which would need to be handled separately, leading to complexity. This login form is the first point of user entry, and I want to handle it separately before looking at global approaches which would retain a separate handling of the login form using methods developed in reply to this OP.
You could restructure your http calls to auto retry, and use promises in your controllers (or whatever)
var httpPostRetry = function(url, usercredentials) {
var promise = new Promise(function(resolve, reject) {
var retries = 0;
var postRetry = function(url, usercredentials) {
if (retries < 3) {
$http({ method: 'POST', url: '/login1', usercredentials })
.then(function(result) {
resolve(result);
}).catch(function(result) {
retries ++;
postRetry(url, usercredentials);
});
} else {
reject(result);
}
};
}.bind(this));
return promise;
}
and then you would call
httpPostRetry(bla, bla).then(function(result) {
// one of the 3 tries must of succeeded
}).catch(function(result) {
// tried 3 times and failed each time
});
To handle specific http errors you can broadcast that specific error and handle that case in a specific controller. Or use a service to encapsulate the status and have some other part of your code handle the UI flow for that error.
$rootScope.$broadcast('unauthorized http error', { somedata: {} });
Does this help?
Have a look at the angular-http-auth module and how things are done there. I think one key element you would want to use is a http interceptor.
For purposes of global error handling, authentication, or any kind of
synchronous or asynchronous pre-processing of request or
postprocessing of responses, it is desirable to be able to intercept
requests before they are handed to the server and responses before
they are handed over to the application code that initiated these
requests. The interceptors leverage the promise APIs to fulfill this
need for both synchronous and asynchronous pre-processing.
After playing around with interceptors you can look at the angular-http-auth http buffer and the way they handle rejected requests there. If their interceptor receives a responseError, they add the config object - which basically stores all information about your request - to a buffer, and then any time they want they can manipulate elements in that buffer. You could easily adept their code to manipulate the config's xsrfHeaderName, xsrfCookieName, or parameters on your behalf when you receive a 403.
I hope that helps a little.
I have been trying different error handling techniques with Express. First I was just using process.on('uncaughtException'). I quickly learned that it was bad practice. After that, I tried using the new "Domain" feature in node js. I wrapped each request in a domain, however, if I would send a response and then do some more work on the server (dealing with the same request) it would not catch the errors from those functions after the response was sent. I then tried moving to the built-in error handling with Express using next(err). However, I am running into the same situation. If I send a response and then a function has an error after the response has been sent, my error handler is not called. Here is some code as an example.
async.waterfall([
function(after)
{
hashPassword(password, after);
},
function(hash, after)
{
makeToken(hash, after);
},
function(hash, token, after)
{
insertUserInfo(email, username, hash, ip, token, after);
},
function(token, id, after)
{
req.session.attempts = 0;
res.json({ err: 0, attempts: 0 }); //Response is sent
after(null, token, id);
},
function(token, id, after)
{
sendEmail(token, email, renderEmail); //Errors not caught
makeFolder(id, after); //Errors not caught
}
], function(err) {
if(err)
next(err);
}
);
As you can see from the code, I am registering a new user. Now, I could wait until I have completed all of my logic to send back the response but I thought that it would make the request appear much faster for the user if I did some of the less important things after the response has been sent. I am willing to change my code to perform everything THEN send the response, but I want to make sure that there is no solution out there that I have not tried yet.
As awesome as Express JS is, it doesn't have a time travel feature allowing you to unsend a request :) This is really the root of your problem. If your response must display the outcome, then it must wait until all the relevant actions to complete.