Retry http requests in k6 - request

I have a python requests-based suite of API tests that automatically retry every request with a 408 or 5xx response. I'm looking at re-implementing some of them in k6 for load testing. Does k6 have support for retrying http requests?

There is no such functionality in k6, but you can add it fairly simply by wrapping the k6/http functions like:
function httpGet(url, params) {
var res;
for (var retries = 3; retries > 0; retries--) {
res = http.get(url, params)
if (res.status != 408 && res.status < 500) {
return res;
}
}
return res;
}
And then just use httpGet instead of http.get ;)

You can create a reusable retry function and put it in a module to be imported by your test scripts.
The function can be general purpose:
Expect a function to retry
Expect a predicate to discern successful from failed calls
An upper retry limit
function retry(limit, fn, pred) {
while (limit--) {
let result = fn();
if (pred(result)) return result;
}
return undefined;
}
then provide the correct arguments when invoking:
retry(
3,
() => http.get('http://example.com'),
r => !(r.status == 408 || r.status >= 500));
Of course, feel free to wrap it in one or more, more specific, functions:
function get3(url) {
return request3(() => http.get(url));
}
function request3(req) {
return retry(
3,
req,
r => !(r.status == 408 || r.status >= 500));
}
let getResponse = get3('http://example.com');
let postResponse = request3(() => http.post(
'https://httpbin.org/post',
'body',
{ headers: { 'content-type': 'text/plain' } });
Bonus: you can make the calling code more expressive by implementing a cleverly named function, which inverts its result, instead of using the negation operator:
function when(pred) {
return x => !pred(x);
}
then
retry(
3,
() => http.get('http://example.com'),
when(r => r.status == 408 || r.status >= 500));
or turn around the behavior of the predicate altogether and test for failed requests instead for successful ones:
function retry(fn, pred, limit) {
while (limit--) {
let result = fn();
if (!pred(result)) return result;
}
return undefined;
}
function unless(pred) {
return x => !pred(x);
}
retry(
3,
() => http.get('http://example.com'),
r => r.status == 408 || r.status >= 500);
retry(
3,
() => http.get('http://example.com'),
unless(r => r.status != 408 && r.status < 500));

Related

How can I save a value in this React lambda function?

I have this React project template, it uses Spring for backend and a PostgreSQL database.
Here, I have the name of a caregiver, and what i want, is to get the id of that caregiver.
I don't know how to retrieve the id, from the findCaregiver method. When in the function, "result.id" will show the correct id, but when it returns to "handleSubmit", the initial method, the value is gone. I tried storing it in the current components' state, in a global variable, even in localStorage. But nothing works, after the function is done, the value is gone. Any ideas? Thank you!!
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
let c = this.findCaregiver(name);
console.log("id: " + c);
findCaregiver(name) {
return API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
console.log("Successfully found caregiver with id: " + result.id);
//result.id shows correct id here!
} else {
this.setState(({
errorStatus: status,
error: error
}));
}
});
}
getCaregiverByName is asynchronous. When you call it, the gears are set in motion, but then the function finishes immediately. Then you return to handleSubmit, and log out nothing, because it isn't done yet.
Some time later, the get finishes, and the callback function will be called. Any code that you need to run when the result is ready needs to be done in that callback, or something called by that callback.
So if we're sticking with callbacks, you can add a callback to findCaregiver:
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
this.findCaregiver(name, (id) => {
console.log("id: " + id);
});
}
findCaregiver(name, callback) {
API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
callback(result.id);
}
}
}
If you perfer promises to callbacks (most people do), you can instead change your code to return a promise. Perhaps getCaregiverByName could be modified to return a promise, but if you don't have access to that code you can wrap your existing code in a promise like this:
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
this.findCaregiver(name)
.then((c) => {
console.log("id: " + c);
});
}
findCaregiver(name) {
return new Promise((resolve, reject) => {
API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
resolve(result.id);
} else {
reject(error);
}
})
});
}
And if it's returning a promise, you have the option to use async/await:
asycn handleSubmit() {
let name = this.state.formControls.caregiverName.value;
const c = await this.findCaregiver(name);
console.log("id: " + c);
}

Discord await messages timing out | Discord V12

I am stuck on a problem. When "Player 2" (player[1]) types !yes in the channel then reason it times out. I'm not sure what I am doing wrong. player[1] is defined as msg.mentions.members.first();
let answer = true;
if (players[1].user.bot) {
return;
} else {
answer = await msg.channel.awaitMessages((msg) => {
console.log(msg.author.id === players[1].id && msg.content === `!yes`) // returns true
if (msg.author.id === players[1].id && msg.content === `!yes`) {
console.log("Player has accepted") // The console does print "Player has accepted"
return true;
}
return false;
}, {maxMatches: 1, time: 30000, errors: ['time']})
.catch(() => {
console.log("Timed out!") // The console does print "Timed Out as well"
return false;
});
}
// if user refuses to play
if (!answer) {
return channel.send(`${players[1]} preferred to run away.`);
}
You have incorrect syntax for awaitMessages() - the first argument should be a CollectorFilter (see here), not a callback.
Consider using createMessageCollector() instead. It reads much more nicely than awaitMessages() and makes more sense than forcing async/await into a collector. Should look something like this:
const filter = m => (m.author.id===players[1].id && m.content==="!yes");
const collector = msg.channel.createMessageCollector(filter, {max: 1, time: 30000});
collector.on("collect", (m) => {
// Player has accepted... do whatever
});
collector.on("end", (reason) => {
if(reason === "time") {
// Ran out of time
}
});

How can I send a random image?

I am trying to post a random dog picture when someone says toggledoggo. Every time I try to run the command, it gives me an error message in the terminal, and does nothing.
Here is my code:
const Discord = require('discord.js');
const client = new Discord.Client();
client.on("message", message => {
var prefix = 'toggle';
var doggos = ['dog1', 'dog2'];
var dog1 = message.channel.send({ files: ['dog1.jpg'] });
var dog2 = message.channel.send({ files: ['dog2.jpg'] });
if (message.content.startsWith(prefix + 'doggo')) {
Math.floor(Math.random() * doggos.length);
};
});
client.login('token');
If you want a truly random picture (pseudo-random but still) use an api. I use dog.ceo. Heres a code that, while big, gives a user plenty of options to choose from.
if(msg.split(' ')[0] === prefix + "doggo"){
let breeds = []; // Initializing an array to use later
let breeds2 = []; // Due to the size, we need to split it.
request('https://dog.ceo/api/breeds/list/all', async function (error, response, body) { // Begin a request to list all breeds.
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
for(var name in importedJSON.message){ // looping through every breed and pushing it into the array made earlier
breeds.push(name)
if(importedJSON.message[name].length != 0){
for(var i = 0; i < importedJSON.message[name].length; i++){
breeds.push("- " + importedJSON.message[name][i]) // Pushing sub-breeds into the array too
}
}
}
for(var j = 0; j < breeds.length/2; j++){ // Halving the previous array because it's too big for one message.
let toArray = breeds.shift()
breeds2.push(toArray)
}
}
})
let args = msg.split(' ').slice(1)
if(!args[0]){ // if no breed, then send a random image
request('https://dog.ceo/api/breeds/image/random', async function (error, response, body) {
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
return await message.channel.send(importedJSON.message)
}
})
}
if(args[0] === "breeds"){ // Command to list the breeds.
setTimeout(async function(){
let m = breeds2.map(name => name.charAt(0).toUpperCase() + name.slice(1)) // Creating a map of the arrays earlier to send as a message
let m2 = breeds.map(name => name.charAt(0).toUpperCase() + name.slice(1))
await message.channel.send(m)
return await message.channel.send(m2)
}, 1000);
}
if(args[0]){
setTimeout(async function(){
for(var i = 0; i < breeds.length; i++){ // Looping through every breed in the array to see if it matches the input
if(args[0] == breeds[i]){
let input = breeds[i]
args.shift() // This is to check for a sub breed input, which is taken into account in the else statement of this if statement
if(!args[0] || args[0] == undefined){
request(`https://dog.ceo/api/breed/${input}/images/random`, async function (error, response, body) {
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
return await message.channel.send(importedJSON.message)
}else{
await message.channel.send(`You must have typed something wrong, do ${prefix}doggo breeds for a list of breeds.`)
return
}
})
return
}else{
request(`https://dog.ceo/api/breed/${input}/${args[0]}/images/random`, async function (error, response, body) { // requesting a dog of a certain breed and sub breed
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
return await message.channel.send(importedJSON.message)
}else{
await message.channel.send(`You must have typed something wrong, do ${prefix}doggo breeds for a list of breeds.`)
return
}
})
return
}
}
}
for(var i = 0; i < breeds2.length; i++){ // This is identical to the above, but with the other array
if(args[0] == breeds2[i]){
let input = breeds2[i]
args.shift()
if(!args[0] || args[0] == undefined){
request(`https://dog.ceo/api/breed/${input}/images/random`, async function (error, response, body) {
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
return await message.channel.send(importedJSON.message)
}else{
await message.channel.send(`You must have typed something wrong, do ${prefix}doggo breeds for a list of breeds.`)
return
}
})
return
}else{
request(`https://dog.ceo/api/breed/${input}/${args[0]}/images/random`, async function (error, response, body) {
if (!error && response.statusCode == 200) {
var importedJSON = JSON.parse(body);
return await message.channel.send(importedJSON.message)
}else{
await message.channel.send(`You must have typed something wrong, do ${prefix}doggo breeds for a list of breeds.`)
return
}
})
return
}
}
}
}, 1000);
}
};
The acceptable command for this are: (prefix)doggo, (prefix)doggo breeds, (prefix)doggo (breed), (prefix)doggo (breed) (sub breed)
You will need the "request" module you can read up on it here: https://www.npmjs.com/package/request. Hopefully the comments I put in the code are enough to explain whats going on if not, ask for clarification. If you just wanted a small command for 2 pictures then you can also tell me and I'll delete this post entirely.

How to Run an API Calls in Parallel (Node.js)

I am trying to run some API calls in parallel, but am having problems since I am trying to call a function again before the API data has been returned.
I am thinking that I could possibly use the new command in Node, but am not sure how to structure it into this scheme. I am trying to avoid recursion, as I already have a recursive version working and it is slow.
Currently I am trying to this code on the server.
loopThroughArray(req, res) {
for(let i=0; i<req.map.length; i++) {
stack[i] = (callback) => {
let data = getApi(req, res, req.map[i], callback)
}
}
async.parallel(stack, (result) => {
res.json(result)
})
}
....
function getApi(req, res, num, cb) {
request({
url: 'https://example.com/api/' + num
},
(error, response, body) => {
if(error) {
// Log error
} else {
let i = {
name: JSON.parse(body)['name'],
age: '100'
}
console.log(body) // Returns empty value array.length > 1 (req.map[i])
cb(i)
}
})
Is there a way to spawn new instances of the function each time it's called and accumulate the results to send back as one result to the client?
Here's an example of calling Web APIs (each with different parameters), using the Async library, we start by creating an array of N function variables.
const async = require('async');
const request = require('request');
//Set whatever request options you like, see: https://github.com/request/request#requestoptions-callback
var requestArray = [
{url: 'https://httpbin.org/get'},
{url: 'https://httpbin.org/ip'}
];
let getApi = function (opt, callback) {
request(opt, (err, response, body) => {
callback(err, JSON.parse(body));
});
};
const functionArray = requestArray.map((opt) => {
return (callback) => getApi(opt, callback);
});
async.parallel(
functionArray, (err, results) => {
if (err) {
console.error('Error: ', err);
} else {
console.log('Results: ', results.length, results);
}
});
You can easily switch the Url and Query values to match whatever you need. I'm using HttpBin here, since it's good for illustrative purposes.

Stuck in async loop with wrapAsync

My goal is to go through a loop asynchronously:
client.js:
abc = function() {
for (var i = 0; i <= 49; i++) {
console.log(i);
Meteor.call('testBla', i)
}
}
server.js
testBla: function(i) {
function asyncCall() {
console.log('inside asyncCall', i)
return 'done';
}
var syncCall = Meteor.wrapAsync(asyncCall);
console.log('a');
var res = syncCall(i);
console.log('b')
return res;
}
Console:
a
inside asyncCall 0
Why does it stuck?
Functions you can pass to Meteor.wrapAsync must have a specific signature : their arguments must end with a callback given 2 arguments : error and result.
Inside an async function body, you must invoke the callback with either an error in case the function fails, or the result if everything is OK.
function asyncHelloWorld(callsCount, callback){
// simulate fake error every 5 calls
if(callsCount % 5 === 0){
callback("error");
}
callback(null,);
}
for(var i = 0; i < 50; i++){
asyncHelloWorld(i, function(error, result){
if(error){
console.log(error.reason);
return;
}
console.log(result);
});
}
You can only wrap functions that respect this signature and behavior, which is a standard inherited from Node.JS.
When you wrap async functions, don't forget to use a try/catch block if you want to handle the potential error.
Meteor.methods({
helloWorld: function(i){
var syncHelloWorld = Meteor.wrapAsync(asyncHelloWorld);
console.log("a");
try{
var res = syncHelloWorld(i);
console.log("b")
return res;
}
catch(exception){
console.log(exception);
console.log("c");
// do not recover, propagates the exception back to the client (standard behavior)
throw exception;
}
}
});

Resources