I am trying to use node-http-proxy inside an AdonisJS controller, but I get the error
The "url" argument must be of type string. Received type function
The line causing the error is the proxy.web(request, response, { target: urlToProxy });
async proxy({ request, response }){
var resource = await Resource.query().where('uri', request.url()).with('service.user').with('userSecurity').first()
resource = resource.toJSON()
if(!resource.service.active){
return response.status(404).send(`Parent service '${resource.service.title}' is disabled`)
}
if(!resource.active){
return response.status(404).send(`Resource is disabled`)
}
if(resource.userSecurity.length <= 0) {
return response.status(403).send(`You are not permitted to access that resource. Contact ${resource.service.user.first_name} ${resource.service.user.last_name} (${resource.service.user.email})`)
}
var urlToProxy = url.resolve(resource.service.basepath, request.originalUrl())
var proxy = httpProxy.createProxyServer()
proxy.web(request, response, { target: urlToProxy });
}
In the end I got a bit closer but not a full fix. The getting close bit was to realise the http-proxy was delivering data via buffer so I had to do something like this
proxy.web(req, res, { target: data.service.basepath})
proxy.on('error', function(err, req, res){
console.log("There was an error", err)
})
proxy.on('proxyRes', async (proxyRes, request, response) =>{
var body = new Buffer('')
proxyRes.on('data', (data)=>{
body = Buffer.concat([body, data])
})
proxyRes.on('end', ()=>{
body = body.toString()
try{
res.send(body)
}catch(err){
}
})
});
However, I still could not get it to work as the controller was returning before http-proxy had completed the request.
In the end and probably for the best, I wrote a stand alone proxy app and used the main app just to validate JWT tokens before they go through the Proxy.
You were so close, I wanted to do something similar and wrapped the proxy in a promise so we can wait for the proxy to return before responding with our response:
const proxy = httpProxy.createProxyServer();
const prom = new Promise((resolve, reject) => {
proxy.web(request.request, response.response, {
target: urlToTarget
}, (e) => {
reject(e);
});
proxy.on('proxyRes', function (proxyRes, req, res) {
let body = [];
proxyRes.on('data', function (chunk) {
body.push(chunk);
});
proxyRes.on('end', function () {
body = Buffer.concat(body).toString();
resolve(body);
});
});
});
const result = await prom;
response.body(result);
return response;
I thought I'd give you a complete answer for anyone else that comes across this.
Related
I'm trying to create REST API. My API should return a list of users taken from a 3rd party (after some manipulations) and return it.
Here is my code:
function getUsersFrom3rdParty(options) {
https.get(options, (resp) => {
let data ='';
// A chunk of data has been received.
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
console.log(JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty();
//do the manilupations and return to the api
};
I don't get the data in getUsers function.
I'd suggest using something like axios - npmjs - for making asynchronous calls to a 3rd party API:
const axios = require('axios')
function getUsersFrom3rdParty(options) {
const processResponse = (response) => {
const processedResponse = ...
// do whatever you need to do, then return
return processedResponse
}
return axios.get('/example.com')
.then(processResponse)
}
// then, you can use `getUsersFrom3rdParty` as a promise
exports.getUsers = (req, res, next) => {
const handleResponse = (data) => {
res.json({ data }) // or whatever you need to do
}
const handleError = (err) => {
res.json({ error: 'Something went wrong!' }) // or whatever you need to do
}
getUsersFrom3rdParty(...)
.then(handleResponse)
.catch(handleError)
}
This way, you're waiting for your API call to finish before you render something and/or return a response.
You are not passing options variable when you are calling getUsersFrom3rdParty function
var data = getUsersFrom3rdParty(options);
You have to pass options to make it work and I suggest to use request module .It works better than https module.
Here is your code using request
const request = require("request");
function getUsersFrom3rdParty(options) {
request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
//Returned data
console.log(JSON.parse(body));
}
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty(options);
};
I'm working on an Actions on Google chatbot using Dialogflow and Cloud Functions. The runtime is Node.js 6.
Why does this function return the empty string?
function getJSON(url) {
var json = "";
var request = https.get(url, function(response) {
var body = "";
json = 'response';
response.on("data", function(chunk) {
body += chunk;
json = 'chunk';
});
response.on("end", function() {
if (response.statusCode == 200) {
try {
json = 'JSON.parse(body)';
return json;
} catch (error) {
json = 'Error1';
return json;
}
} else {
json = 'Error2';
return json;
}
});
});
return json;
}
This is the intent in which I want to access the json data:
app.intent('test', (conv) => {
conv.user.storage.test = 'no change';
const rp = require("request-promise-native");
var options = {
uri: 'https://teamtreehouse.com/joshtimonen.json',
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (user) {
conv.user.storage.test = user.name;
})
.catch(function (err) {
conv.user.storage.test = 'fail';
});
conv.ask(conv.user.storage.test);
});
You can try to use the request module for node.js, I tried my self reproducing your use case and worked fine. The code should be something similar to this:
const request = require('request');
request(url, {json: true}, (err, res, body) => {
if (err) { res.send(JSON.stringify({ 'fulfillmentText': "Some error"})); }
console.log(body);
});
Also, you need to add "request": "2.81.0" in the dependencies section inside your package.json file.
The function returns the empty string because https sets up a callback function, but the program flow continues to the return statement before the callback is called.
In general, when working with Dialogflow Intent Handlers, you should be returning a Promise instead of using callbacks or events. Look into using request-promise-native instead.
Two clarifying points:
You must return the Promise. Otherwise Dialogflow will assume the Handler has completed. If you return the Promise, it will wait for the Promise to finish.
Everything you want to send back must be done inside the then() block. This includes setting any response. The then() block runs after the asynchronous operation (the web API call) completes. So this will have the results of the call, and you can return these results in your call to conv.ask().
So it might look something like this:
return rp(options)
.then(function (user) {
conv.add('your name is '+user.name);
})
.catch(function (err) {
conv.add('something went wrong '+err);
});
You can use Axios as well
To install Axios : npm install axios
OR
You can add it in package.json as a dependency
"dependencies": {
"axios": "^0.27.2",
}
index.js
const axios = require('axios').default;
exports.makeRequest = async (req, res) => {
axios.get('https://jsonplaceholder.typicode.com/todos/1')// Dummy URL
.then(function (response) {
// handle success
res.status(200).json({
success:true,
result:response.data
})
})
.catch(function (error) {
// handle error
console.log(error);
})
};
OR
const Axios = require("axios");
exports.makeRequest = async (req, res) => {
const { data } = await
Axios.get('https://jsonplaceholder.typicode.com/todos/1')
res.status(200).send({data})
};
Right now i have this code
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data)
postData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Events.list().then(data=> {
var jsonOutput=JSON.stringify(data)
eventData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data)
messageData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Users.list().then(data=> {
var jsonOutput=JSON.stringify(data)
userData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
//Then when all data from colections is retrieve i want to use the 4 variables that i created in the beggining
});
So basicly im trying to retrieve the data from my mongo database and then assign the results to that 4 variables that i create, but im not getting success.
For what i´ve been seeing i have to use async but im having some trouble doing it.
I don't like too much mrlanlee solution. This is a typical situation where using async / await can really make sense. Anyway, the Hugo's solution (the second one, with async await), even if it just works, will make the four queries in sequence, one after another to. If you want a clean, working and parallel solution, check this:
router.get('/export', async function(req, res, next) {
let data
try {
data = await Promise.all([
Posts.list(),
Events.list(),
Messages.list(),
Users.list()
]);
// at this point, data is an array. data[0] = Posts.list result, data[1] = Events.list result etc..
res.status(200).json(data)
} catch (e) {
res.status(500).send('error');
}
});
The other answer from Sashi is on the right track but you will probably run into errors. Since your catch statement on each promise returns 500, if multiple errors are caught during the query, Express will not send an error or 500 each time, instead it will throw an error trying to.
See below.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
try {
postData = Posts.list().then(data=> {
return JSON.stringify(data);
});
eventData = Events.list().then(data=> {
return JSON.stringify(data)
});
messageData = Messages.list().then(data=> {
return JSON.stringify(data);
})
userData = Users.list().then(data=> {
return JSON.stringify(data)
});
} catch (err) {
// this should catch your errors on all 4 promises above
return res.status(500).send('error')
}
// this part is optional, i wasn't sure if you were planning
// on returning all the data back in an object
const response = {
postData,
eventData,
messageData,
userData,
};
return res.status(200).send({ response })
});
For explanation of why you weren't able to mutate the variables, see Sashi's answer as he explains it.
The variables defined outside the async code is out of scope of the async functions. Hence you cannot store the returned value from the async functions in those variables.
This should work.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
postData = Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
eventData = Events.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
messageData = Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
userData = Users.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
});
Using Async/Await is a much neater solution.
router.get('/export', async function(req, res, next) {
var postData, eventData, messageData, userData;
try{
postData = await Posts.list();
eventData = await Events.list();
messageData = await Messages.list()
userData = await Users.list();
catch (e){
res.status(500).send('error');
}
});
sorry for the generic title. I'm pretty new to nodejs as well as the idea of async/await.
So I have an express app, which makes an HTTP get request as a callback function. The callback function gets the body object, and returns it to getBody function. But when I try to assign getBody to a variable, it returns undefined.
Yes I know. The getBody function returns body before body gets filled up with data, but I just don't know how to write a getter method for this body object. So my question is, how can I run the get request and access body object in the global scope at the same time, so all functions depending on body object can run without further problems.
async function getBody (req, res, next) {
let body = await makeRequest(req, res);
return body; // THIS RETURNS UNDEFINED
}
router.get('/', getBody);
function makeRequest (req, res){
let uri;
let options = {
uri: uri,
};
request(options, function (error, response, body) {
if (error){
console.log('error:', error);
} else {
console.log('Success! statusCode:', response && response.statusCode);
let jsonObject = JSON.parse(body);
return jsonObject;
}
});
}
I did my research, but I just could not find a useful resource. Thanks in advance.
await and async should be used with a promise, these kind of method cannot return data. return is used to return a value from a synchronous method.
So you may return a promise from your makeRequest method like this,
async function getBody(req, res, next) {
let body = await makeRequest(req, res);
return body; // NOW BODY IS NOT UNDEFINED, call next() or send response here
}
router.get('/', getBody);
function makeRequest(req, res) {
return new Promise((resolve, reject) => {
let uri;
let options = {
uri: uri,
};
request(options, function (error, response, body) {
if (error) {
console.log('error:', error);
return reject(error);
} else {
console.log('Success! statusCode:', response && response.statusCode);
let jsonObject = JSON.parse(body);
return resolve(jsonObject);
}
});
})
}
FYI,
let body = await makeRequest(req, next)
is equals to
makeRequest(req, next).then(body => { /* YOUR CODE HERE */ })
and if you didn't knew, you have to process the body and send the response, return body won't send the response to the client.
OK, #JanithKasun did a great job of answering your original question fully. This answer is intended to expand on that a little to get a the problem you're having conceptually that isn't explicitly asked in your question.
As I understand your code, you're trying to fetch some data from a third party resource based on information in the request coming to your app's handler. Ideally, you want to separate your code in a way to make it more reusable/maintainable. I note that the code in question doesn't actually use the request or response object at all, but I'm going to assume you would have some kind of parameter to the getBody function that helps you construct the URI it's requesting. So, to that end:
// ./lib/get-body.js
const BASE_URI = 'https://example.com/search?q='
async function getBody (query) {
let body = await makeRequest(query);
return body;
}
function makeRequest(query) {
return new Promise((resolve, reject) => {
let uri = `${BASE_URI}{query}`; // results in something like 'https://example.com/search?q=cats'
let options = {
uri: uri,
};
// Note: removed console statements here to centralize error handling
request(options, function (error, response, body) {
if (error) {
return reject(error);
} else {
let jsonObject = JSON.parse(body);
return resolve(jsonObject);
}
});
})
}
// export the top level function for reuse
module.exports = getBody;
Now, in your routing code:
// ./index.js or wherever
const express = require('express');
const getBody = require('./lib/get-body');
//...whatever other setup...
app.get('/', async (req, res, next) => {
const query = req.query.terms; // or whatever
try {
const body = await getBody(query);
return res.send(body);
} catch (e) {
return next(e); // if you don't do this, process hangs forever on error
}
});
// handle the errors. obviously you can do something smart like
// figure out the error code and send back something other than a 500 if appropriate.
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('I am Bender; please insert girder.');
});
Hope that helps!
I'm writing unit tests for separate middleware functions in Node/Express using Jest.
A simple example of the middleware:
function sendSomeStuff(req, res, next) {
try {
const data = {'some-prop':'some-value'};
res.json(data);
next();
} catch (err) {
next(err);
}
}
And a sample of my test suite:
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
let response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
// How to 'capture' what is sent as JSON in the function?
});
});
});
I have to provide a callback to populate the next parameter, which is called in the function. Normally, this would 'find the next matching pattern', and pass the req and res objects to that middleware. However, how can I do this in a test set-up? I need to verify the JSON from the response.
I don't want to touch the middleware itself, it should be contained in the test environment.
Am I missing something here?
Found a fix!
Leaving this here for someone else who might struggle with the same.
When returning data using res.send(), res.json() or something similar, the response object (from const response = httpMocks.createResponse();)
itself is updated. The data can be collected using res._getData():
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
const response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
});
const { property } = JSON.parse(response._getData());
expect(property).toBe('someValue');
});
});
});
I did a different way by utilising jest.fn(). For example:
if you wanna test res.json({ status: YOUR_RETURNED_STATUS }).status(200);
const res = {};
res.json = jest.fn(resObj => ({
status: jest.fn(status => ({ res: { ...resObj, statusCode: status }
})),
}));
Basically, I mock the res chain methods(json and status).
That way you can do expect(YOUR_TEST_FUNCTION_CALL).toEqual({ res: { status: 'successful', statusCode: 200 }}); if your response structure is like that.