How can I end a request in koa.js using another request. Lets say I'm keeping the active request contexts in an object. Assume request A is started and takes a long time. How can I make another request, that tells request A to end.
var requests = {};
// middleware to track requests
app.use(function*(next) {
var reqId = crypto.randomBytes(32).toString('hex');
requests[reqId] = {
context: this
}
yield next;
delete requests[reqId];
}
);
// route to kill request using ID generated from middleware above
router.get('/kill/:reqId', function *(next) {
var req = requests[this.params.reqId];
if (req) {
// abort request here
} else {
this.body = {
error: 'Request not found'
};
}
});
You should implement a cancellation token that you check regularly.
Example:
// Factory to create a token
const cancellationToken = () => {
let _cancelled = false;
function check() {
if (_cancelled == true) {
throw new Error('Request cancelled');
}
}
function cancel() {
_cancelled = true;
}
return {
check: check,
cancel: cancel
};
}
const reqs = {};
// Middleware to create tokens.
app.use(function *(next) {
const reqId = crypto.randomBytes(32).toString('hex');
const ct = cancellationToken();
reqs[reqId] = ct;
this.cancellationToken = ct;
yield next;
delete reqs[reqId];
});
// route to kill request using ID generated from middleware above
router.get('/kill/:reqId', function *(next) {
const ct = requests[this.params.reqId];
if (ct) {
ct.cancel();
} else {
this.body = {
error: 'Request not found'
};
}
});
// A request checking for cancellation.
router.get('/longrunningtask', function *(next) {
for (let i = 0; i < 1000; i++) {
yield someLongRunningTask(i);
// This is where you check to see if you're done.
// The method will throw and abort the request.
this.cancellationToken.check();
}
});
You could even pass along the cancellation token to the someLongRunningTask function so you can control cancellation there.
Related
I am running into an issue when I try to load the initial data for my blacklist from a Redis DB in my middleware code. Since the DB request takes some time it starts to fail.
Below is my code which gets fired when app starts via app.use(blacklist.blockRequests());.
When I try to make the function async I get the error that new TypeError('app.use() requires a middleware function').
One of the side effects is also that my array is empty when it's called again.
blockRequests: function() {
this.read();
this.logEvent('info', 'There are ' + this.blacklist.length + ' address(es) on the blacklist');
var self = this;
var interceptor = function(request, response, next) {
var ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (self.isInBlacklist(ip)) {
self.logEvent('warn', 'Rejecting request from ' + ip + ', path and query was ' + request.originalUrl);
response.status(403).send();
} else {
next();
}
}
return interceptor;
},
And here is my read() function code:
read: function() {
try {
// get all records with prefix block:: from redis
redis.redis.keys('block::*', function (err, reply) {
// reply is null when the key is missing
if(err){}
else {
this.blacklist = []
for (let i = 0; i < reply.length; i++) {
let ipInt = reply[i].substring(7)
let ipStr = ipToInt(ipInt).toIP()
this.blacklist.push(ipStr)
}
}
});
} catch (error) {
if (error) {
this.blacklist = [];
}
}
}
If you're trying to make blockRequests() async, then it will start returning a promise and you can't use its return value directly in app.use(). Because then you'd be doing app.use(somePromise) and Express will balk because you have to pass it a function reference, not a promise.
Instead, you will have to use .then() or await to get the return value which is the function which you could then use with app.use().
If you show the larger calling context here (like where you're calling blockRequests() from), then we could offer more ideas on a fuller solution.
Here's a conceptual idea for how you could do this:
blockRequests: function() {
const self = this;
const interceptor = function(request, response, next) {
const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (self.isInBlacklist(ip)) {
self.logEvent('warn', 'Rejecting request from ' + ip + ', path and query was ' + request.originalUrl);
response.status(403).send();
} else {
next();
}
}
return interceptor;
},
read: function() {
// get all records with prefix block:: from redis
return new Promise((resolve, reject) => {
redis.redis.keys('block::*', (err, reply) => {
if (err) {
this.blacklist = [];
reject(err);
} else {
this.blacklist = [];
for (let i = 0; i < reply.length; i++) {
let ipInt = reply[i].substring(7)
let ipStr = ipToInt(ipInt).toIP()
this.blacklist.push(ipStr)
}
}
this.logEvent('info', 'There are ' + this.blacklist.length + ' address(es) on the blacklist');
resolve();
});
});
}
// register middleware for using blacklist
app.use(blacklist.blockRequests());
// now read the blacklist and when that is in place, then start the server
blacklist.read().then(() => {
// now we know that blacklist.blacklist is up-to-date
// start your server here
}).catch(err => {
console.log("Unable to start server - error in reading blacklist");
process.exit(1);
});
I'm currently implementing a WebSocket connection and I'm using a command pattern approach to emit some messages according to the command that users execute.
This is an abstraction of my implementation:
let socketInstance;
const globalName = 'ws'
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand
};
commandsQueue.forEach(command => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
socketInstance.send(message);
}
function create([url]) {
socketInstance = new WebSocket(url);
}
In order to start sending messages, the user should be run:
window.ws.push('create', 'ws://url:port');
window.ws.push('send', 'This is a message');
The problem that I have is the connection is async, and I need to wait until the connection is done to continue to the next command. Is it a good idea to implement an async/await in commandsQueue.forEach or an iterator is a better approach? What other best approaches do you recommend?
The solution that I'm using right now is: I created an empty array of messages at the beginning and then every time that I call the send command I verify if the connection wasn't opened and I added to this array.
Something like that:
const messages = [];
let socketInstance;
let isConnectionOpen = false;
const globalName = "ws";
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand,
};
commandsQueue.forEach((command) => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send,
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
if (isConnectionOpen) {
socketInstance.send(message);
} else {
messages.push(message);
}
}
function onOpen() {
isConnectionOpen = true;
messages.forEach((m) => {
send([m]);
});
messages.length = 0;
}
function create([url]) {
socketInstance = new WebSocket(url);
socketInstance.onopen = onOpen;
}
I would like to know if it is somehow possible to check if an asynchronous operation in Javascript is still pending..
Because I am doing a database request on calling a specific URL... While the db call is still in progress, I want to stop any other incoming db-calls (which means, stop any further calls to that URL in case the db-request is still pending).
Is that somehow possible?
Because the database call takes up to minutes, and I don't want to launch another database-call while the first is still in progress.. The problem is, I somehow cannot figure out how to check if the call has started and is still in progress, because the response comes only after the .then() clause when the process has already finished.
this is my db-call function:
const getWriteIndex = async () => {
return Promise.all(someFunction1, someFunction2...).then(...) {
writeMessageObject = checkDocuments(...);
return Promise.resolve(writeMessageObject);
})).catch((err) => {
return Promise.reject(err);
});
}
This is my URL/Route Call function with express:
router.get("/v1/...", someMiddleware(), async function(req,res,next) {
if (read_cached() && initialised_read) {
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
} else {
try {
//HOW CAN I CHECK HERE IF THE DB-CALL IS ALREADY IN PROGRESS?
readmsg_obj.message = '';
getReadIndex().then((message) => {
initialised_read = true;
readmsg_obj = {...message};
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
}).catch((reject) => {
logger.error(`/../... : ${reject}`);
initialised_read = false;
res.status(500).send(reject);
});
} catch(err) {
logger.error(`/v1/... : ${err}`);
res.status(500).send(err);
};
}
});
hmm I found a workaround here:
https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved
so I wrote that function to check for promise stati, but I am still wondering if it's not somehow possible to query for static promise properties to get their actual state ;) (but weirdly, I didn't find any on the web).
const checkPendingRequest= (promise) => {
if (promise.isResolved) return promise;
// Set initial state
var isPending = true;
var isRejected = false;
var isFulfilled = false;
// Observe the promise, saving the fulfillment in a closure scope.
var result = promise.then(
function(v) {
isFulfilled = true;
isPending = false;
return v;
},
function(e) {
isRejected = true;
isPending = false;
throw e;
}
);
result.isFulfilled = function() { return isFulfilled; };
result.isPending = function() { return isPending; };
result.isRejected = function() { return isRejected; };
return result;
}
So I fixed my function for the request:
router.get("/v1/...", someMiddleware(), async function(req,res,next) {
if (read_cached() && initialised_read) {
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
} else {
try {
readmsg_obj.message = '';
if ((dbQueryPromiseRead != null) && dbQueryPromiseRead.isPending()) {
logger.info(`Database request for Index-read is still pending!`);
return;
}
dbQueryPromiseRead = checkPendingRequest(getReadIndex());
dbQueryPromiseRead.then((message) => {
initialised_read = true;
readmsg_obj = {...message};
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
}).catch((reject) => {
logger.error(`/../... : ${reject}`);
initialised_read = false;
res.status(500).send(reject);
});
} catch(err) {
logger.error(`/v1/... : ${err}`);
res.status(500).send(err);
};
}
});
You need to try add in node.js like global.dbCallState flag if operation is still running.
This global var one for all modules.
Do not change this object like global = new Object();, but you can use child field's.
https://nodejs.org/api/globals.html
You can change it in another module like global.dbCallState = false.
It not best solution, but it can help.
But i don't know, why you want only one connection. Its not good solution to block I/O in node.js
I tried to configure backend factory to obtain data using Amazon Products Api.
Here is the script I execute in nodejs:
var amazon = require('amazon-product-api');
var client = amazon.createClient({
awsId: 'ID',
awsSecret: 'sectret',
awsTag: 'tag',
});
// SERVER
// var Promise = require('bluebird');
var koa = require('koa');
var router = require('koa-router')();
router.get('/node/:index', function* () {
this.body = yield client.browseNodeLookup({
browseNodeId: this.params.index,
});
});
router.get('/', function* (ctx, next) {
var node = yield client.browseNodeLookup({
browseNodeId: 1063498,
});
this.body = node;
});
var app = koa();
app.use(router.routes()).use(router.allowedMethods());
app.listen(8005);
At the frontend I use Promise.map() by bluebird.js to map an array of amazon's product nodes. At the final of the function, I expect to transform links (strings) in the Array to objects (obtained by API).
Here is the function:
someFn(links) { // links is an array of node IDs
return Promise.map(links, (link) => {
var link = link;
if (typeof link === "object" || level > 1) {
return link;
} else {
return loadUrl({
url: 'http://localhost:8005/node/'+link,
action: 'json',
}).then((res) => {
if (isJSON(res)) {
return new Category(res); // transform string to object
} else {
return link;
}
});
}
});
}
Amazon allows at max 10 queries, that's why I need to run the function a few times or loop it to obtain an object for the each string in the Array.
The idea is to wait for the successful answer at the backend or repeat the query (yield client.browseNodeLookup)
Or just pass the array of node IDs and get JSON of each as the result.
I have not a lot experience with nodejs server and routing configuration, so can you help me to configure it properly?
I still did not find the solution to use backend for the task, but I've updated a loadUrl function. The function runs itself until retrieved the successful answer:
function loadUrl(options) {
var options = options;
return new Promise((resolve, reject) => {
$.post('/', options).done((result) => {
if (result != 'Internal Server Error') {
resolve(result);
} else {
(function run(x) {
var x = x || 0;
// 300 - calls limit
// for me 100 is enough
if (x <= 300) {
setTimeout(function() {
$.post('/', options).done((result) => {
if (result != 'Internal Server Error') {
resolve(result);
} else {
console.log('call', x);
run(x+1);
}
});
}, 100);
} else {
resolve(result);
}
})();
}
});
});
}
As I understood, the $.post() has time limit for the answer, and thats why I've got the problem. The data is obtained by the backend factory, but my frontend script was not ready to wait for it and was stopped.
In my node.js app, reading data from MSSQL using tedious, I'm calling the below every 1 second:
Fetch the data from the server (fetchStock function) and save it in temporary array
Send the data saved in the temporary array to the client using the Server-Sent Events (SSE) API.
It looks the 1 second is not enough to recall the fetchStock function before the previous call is completely executed, so I get execution errors from time to time.
I increased it to 5 seconds, but still get the same issue every once in a while.
How can I use Promise().then to be sure the fetchStock function is not re-called before the previouse call be completely executed?
var Request = require('tedious').Request;
var Connection = require('tedious').Connection;
var config = {
userName: 'sa',
password: 'pswd',
server: 'xx.xxx.xx.xxx',
options: {
database: 'DB',
rowCollectionOnRequestCompletion: 'true',
rowCollectionOnDone: 'true'
},
};
var sql = new Connection(config);
var addElem = (obj, elem)=> [].push.call(obj, elem);
var result = {}, tmpCol = {}, tmpRow = {};
module.exports = {
displayStock: function (es) {
var dloop = setInterval(function() {
if(result.error !== null)
if (es) es.send(JSON.stringify(result), {event: 'rmSoH', id: (new Date()).toLocaleTimeString()});
if(result.error === null)
if (es) es.send('connection is closed');
}, 1000);
},
fetchStock: function () {
request = new Request("SELECT ItemCode, WhsCode, OnHand FROM OITW where OnHand > 0 and (WhsCode ='RM' or WhsCode ='FG');", function(err, rowCount, rows) {
if (err) {
result = {'error': err};
console.log((new Date()).toLocaleTimeString()+' err : '+err);
}
if(rows)
rows.forEach(function(row){
row.forEach(function(column){
var colName = column.metadata.colName;
var value = column.value;
addElem(tmpCol, {colName: value})
});
addElem(tmpRow,{'item': tmpCol[0].colName, 'Whs': tmpCol[1].colName, 'Qty': tmpCol[2].colName});
tmpCol = {};
});
result = tmpRow;
tmpRow={}
});
sql.execSql(request);
}
}
I think what you need is a simple variable to check if there's already running request not Promise.
var latch = false;
// It will be called only if the previous call is completed
var doFetchStock = () => sql.execSql(new Request("SQL", (err, rowCount, rows) => {
// Your logic dealing with result
// Initializes the latch
latch = false;
});
module.exports = {
fetchStock: function () {
// Check if the previous request is completed or not
if (!latch) {
// Sets the latch
latch = true;
// Fetches stock
doFetchStock();
}
}
};
Actually I've used this kind of pattern a lot to allow some behavior only once.
https://github.com/cettia/cettia-javascript-client/blob/1.0.0-Beta1/cettia.js#L397-L413
https://github.com/cettia/cettia-javascript-client/blob/1.0.0-Beta1/cettia.js#L775-L797
Since javascript is mono-threaded a simple code like this should be enough on client-side
function () {
if(currentPromise != null){ // define in a closure outside
currentPromise = [..] // call to server which return a promise
currentPromise.then(function(){
currentPromise = null;
});
}
}