Node+ElasticSearch: Sending a body on a GET request? - node.js

I am using Node.js and the request module to create a backend, and we've chose Elasticsearch as our data storage. All fine so far, except it seems Node doesn't support request bodies on GET requests? This is necessary for Elasticsearch's _search API, which expects only GET requests as part of their semantic design. Is there a solution to force Node to send the request body even in the cases of a GET request, or a mean to use _search on Elasticsearch with another HTTP verb?
function elasticGet(url, data) {
data = data || {};
return Q.nfcall(request.get, {
uri: url,
body: JSON.stringify(data) //<-- noop
}).then(function(response) {
return JSON.parse(response[1]);
}, function(err) {
console.log(err);
});
}

The _search API also accepts the POST verb.

For simplicity, why not use their api rather than manually making requests?
simple example:
var elasticsearch = require('elasticsearch'),
client = new elasticsearch.Client({
host: '127.0.0.1:9200',
log: 'trace'
});
client.search({
index: '[your index]',
q: 'simple query',
fields: ['field']
}, function (err, results) {
if (err) next(err);
var ids = []
if (results && results.hits && results.hits.hits) {
ids = results.hits.hits.map(function (h) {
return h._id;
})
}
searchHandler(ids, next)
})
You can combine it with fullscale labs elastic.js to build really complex queries, really fast.
https://github.com/fullscale/elastic.js

I had such an issue a few days ago.
tld;dr use POST
According to https://www.elastic.co/guide/en/elasticsearch/guide/current/_empty_search.html#get_vs_post you can also use POST with elastic.
I tried it with axios but it returns all data like with no body.
So I used POST instead. It works for me and I hope it will help to someone else.

Related

xhttp GET request to an express.js server - nothing returned

I am trying to do a simple xhttp GET request to an express.js server. Unfortunately I get no response data with this code. The connection is fine as I have successfully used "res.send" to send a body back from the server.
I am not sure if my use of "findOne" on the server is incorrect or if my use of xhttp on the client is incorrect. I suspect it is the client.
I'd appreciate any advice.
* CLIENT CODE *
function getfood() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "http://localhost:3000/clientfood", true);
xhttp.send();
}
* SERVER CODE - Express.js / Node *
app.get('/clientfood', cors(), (req, res) => {
//res.send('test'); //this works at least
db.collection('quotes').findOne({
"_id": ObjectId("12345")
},
{
name: 1,
quote: 1
})
})
xhttp GET request to an express.js server - nothing returned
Your server code does not return a response. You need to do something like res.send(...) or res.json(...) to return a response back to the caller and you need to do that in the callback that your database provides for communicating back the result of a query (in most DBs, you can either use a plain callback or a promise).
Your client code does not listen for a response. Example for how to do that shown here on MDN and would typically be:
function getfood() {
var xhttp = new XMLHttpRequest();
xhttp.addEventListener("load", function() {
if (xhttp.status === 200) {
// have data in xhttp.responseText, process it here
} else {
// got some other response here
}
});
xhttp.open("GET", "http://localhost:3000/clientfood", true);
xhttp.send();
}
Thanks so much - especially #jfriend00. I have a lot to learn about how these frameworks work. After taking your advice about SEND I had a little trouble seeing the result on my frontend. I got the message "promise pending". I fixed that with the code suggested in this post.
Express - Promise pending when loop queries
Also I modified my findOne function to grab the entire object for my id.
Final code:
app.get('/clientfood', cors(), (req, res) => {
mydata = db.collection('quotes').findOne(
{
"_id": ObjectId("12345")
})
// promise code
Promise.all([mydata]).then(listOfResults => {
res.send(JSON.stringify(listOfResults)) //for example
}, err => {
res.send(500, JSON.stringify(err)); // for example
});
})

Calling a third party API in loopback after saving data

I have just started with loopback.We are creating a middleware in loopback where a user comes and Fills up a task form. When he submits the form we save the data in our database and then we need to call a third party REST API to store the same data appended with ID which our DB returns.
I read about the REST connector in loopback, but I don't think it serves the purpose. So I decided to go ahead with the request library.
I have created a RemoteMethod where I am doing all this. Here's the code:-
const request = require('request');
const url = "http://third-party-api";
module.exports = function(Tasks) {
function cb(err, data){
return { data: data };
}
Tasks.add = function(data, cb){
Tasks.create({data: data}, function(err, object){
var taskId = object.id
request.get({url: url}, function(error, response, body){
cb(error, body);
});
});
};
Tasks.remoteMethod(
'add',
{
accepts: [{arg: 'data', type: 'object'}],
returns: {arg: 'result', type: 'object'},
http: {path: '/add', verb: "post"}
}
);
};
But this results in an error(IP changed):-
{"error":{"name":"Error","status":500,"message":"connect ECONNREFUSED 0.0.0.0","code":"ECONNREFUSED","errno":"ECONNREFUSED","syscall":"connect","address":"0.0.0.0"}}
I haven't been able to figure out what's going on here. NodeJs, Express, Loopback everything is pretty new to me & I have been going around in circles for quite sometime now.
What am I doing wrong? Is there any other approach that I need to follow
0.0.0.0 means all IP on your local machine, localhost.
Most likely, you forgot to define the port on which the external api is listening.
If your external API is listening on port 3001 for instance, then just modify your code like this
const url = "http://localhost:3001";
Just a note, there is nothing wrong with using request for your use case, but the loopback REST connector will also do exactly what you need.

How to create loopback external CRUD?

I want to use loopback to be a "middle man" to connect between two different api (server1 and server2). When I pass a user's email to this "middle man", it will check with server 1, and if that server returns true, then the "middle man" will get all data from the server 2 save to server 1.
Can someone guide me on how to do this? I tried to follow loopback documentation, but I couldn't get what I want, am I missing something?
So the actual implementation is not really related to Loopback.js framework. This can be implemented in any Node.js code.
And actually your description above is the the right steps to do it using a simple HTTP request to server 1 and 2
For example using request
module this code shows the pseudocode of the middle man.
const request = require('request');
// 1. Call Server 1
request('http://server1.com/api/message', function (err, res, body) {
// 2. Check body message is true
if (body.message === true) {
// 3. Get data from Server 2
request('http://server2.com/api/get', function (err, res, body) {
// 4. Save data to Server 1
request({
url: 'http://server1.com/api/save',
method: "POST",
json: requestData },
function (err, res body) { /* process response here... */ });
});
}
});
Assuming you have set up two datasources that call your external API providers as per my answer to your related question - for this example you would need the following calls:
Server1 datasource:
checkEmail // GET method, returns true or false
save // POST method, returns true or false
Server2 datasource:
getDetails // GET or POST method, returns contact details for the email address
You would also need a Loopback Model with an appropriate method as your entry point. For the example lets call it Contact, with a remote method called store, taking in the email parameter. This code would do all of the work:
Contact.store = function(email, cb) {
var server1 = Contact.app.dataSources.Server1;
var server2 = Contact.app.dataSources.Server2;
server1.checkEmail(email, function(validated) {
if(validated) {
server2.getDetails(email, function(details) {
if(details) {
server1.save(details, function(success) {
if(success) {
cb(null, details);
}
})
}
})
}
})
}
(you can promisify all of the above for cleaner code)

Connecting to a PHP website with Node.js and keep session

I'm making a testbench with Test'em and Mocha (that run on node.js) in order to test a PHP website.
What I want is to request some URL (e.g http://www.my-website/test.php) and get the http status code as well as the content returned.
I'm doing it with the node.js Request module.
The problem is:
I need to be authenticated to access this page, otherwise I'm
redirected to the login page.
So, does it exist a way to log in my application through Node.js and keep the session open to be able to chain tests on any pages I want?
I was thinking on get the PHPSESSID on login request if it is possible. Do you thing it is a good direction ?
Any help would be much appreciated.
Thank you, have a nice day :)
Michaël
If you set jar: true in your options or use your own custom cookie jar, then request will remember cookies set by the server so that you can keep your session between requests.
mscdex thanks for your answer! But unfortunately it did not work for me :/
hyubs thanks to you too.
Finally I carried on to use Mocha + Request.
Basically what I did is:
Connect through a POST request to the login page and get the PHPSESSID cookie that is returned in the response header.
Pass the cookie in the header in the next requests that target a URL where you have to be logged.
Here is my code :
var params = {
email: 'your_username',
password: 'your_password'
};
var paramsString = JSON.stringify(params);
// Login to the application
request.post('http://localhost/biings/front-end/rest/auth',
{
headers: {
"Content-Type" : "application/json",
'Content-Length' : paramsString.length
},
body: paramsString,
},function (error, response, body) {
// get the PHPSESSID (the last one) that is returned in the header. Sometimes more than one is returned
var sessionCookie = response.headers['set-cookie'][response.headers['set-cookie'].length - 1];
sessionCookie = sessionCookie.split(';');
sessionCookie = sessionCookie[0];
// Write it in a file (this is a quick trick to access it globally)
// e.g.: PHPSESSID=ao9a1j0timv9nmuj2ntt363d92 (write it simply as a string)
fs.writeFile('sessionCookie.txt', sessionCookie, function (err)
{
if(err)
{
return console.log(err);
}
});
});
// don't care about this it() function (it's for Mocha)
it("test 1", function(done)
{
// Get the cookie
fs.readFile('sessionCookie.txt','utf8', function (err, data)
{
if(err)
{
throw err;
}
else
{
// Launch a request that includes the cookie in the header
request.get('http://localhost/biings/front-end/rest/group',
{
headers: {"Cookie" : data},
}, function (error, response, body) {
// Check your request reaches the right page
expect(response.statusCode).equals(200);
console.log(body);
done();
});
}
});
});
It works like a charm for me.
Tell me if you see something wrong or which could be optimized :)
Michaël
Instead of using the request module, use headless browsers like PhantomJS and zombie.js. You can even emulate user interaction with these.

Testing an API wrapper

I'm writing an API wrapper for an external API, to be used in our application.
I have adopted a test-driven approach for this project but since I have little to no experience with writing API wrappers, I'm not sure if I'm on the right track.
I understand that I should not be testing the external API, nor should I be hitting the network in my tests. I'm using Nock to mock my requests to the API.
However, I'm not sure I'm doing this correctly.
I made some requests to the API using curl and put the (XML) response in a file, for example: /test/fixtures/authentication/error.js:
module.exports = "<error>Authorization credentials failed.</error>"
Since I don't want to hit the network, but want to make sure my wrapper parses the XML to JSON, I figured I needed sample data.
My test looks like this:
describe("with an invalid application key", function() {
var cl, api;
before(function(done) {
api = nock(baseApi)
.get('/v1/auth/authenticate')
.reply(200, fixtures.authentication.error);
done();
});
after(function(done) {
nock.cleanAll();
done();
});
it("returns an error", function(done) {
cl = new APIClient(auth.auth_user, auth.auth_pass, "abcd1234");
cl.authenticate(function(err, res) {
should.exist(err);
err.should.match(/Authorization credentials failed./);
should.not.exist(res);
api.isDone().should.be.true;
done();
});
});
});
With my tested code looking like this:
APIClient.prototype.authenticate = function(callback) {
var self = this;
request({
uri: this.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: this.user,
auth_pass: this.pass,
auth_appkey: this.appkey
}
}, function(err, res, body) {
if (err) {
return callback('Could not connect to the API endpoint.');
}
self.parser.parseXML(body, function(err, result) {
if (err) { return callback(err); }
if (result.error) { return callback(result.error); }
self.token = result.auth.token[0];
return callback(null, result);
});
});
};
Now, this seems to be working fine for the authentication side of things (I also have a 'success' fixture, which returns the 'success' XML and I check if the returned JSON is actually correct.
However, the API I'm using also has endpoints like:
/data/topicdata/realtime/:reportxhours/:topics/:mediatypes/:pageIndex/:pageSize
I'm not sure how to test all (should I?) possible combinations with URLs like those. I feel like I can hardly put 30 XML responses in my fixtures directory. Also, when mocking responses, I'm afraid to miss out on possible errors, edge cases, etc. the external API might return. Are these valid concerns?
If anyone has any pointers, and/or knows of any open-source and well-tested API wrappers I could take a look at, I'd be very grateful.
I think your concern is very valid and I suggest you to also build tests using Zombie or other simular request-based testing frameworks.

Resources