How can I pass a proxied response when 'selfHandleResponse' is true? - node.js

I am running a development proxy-server where on startup I make a request for an auth token with our cluster architecture.
However, this token expires after some time. Then instead of the developer restarting their proxy-server, I would like to make another request to get another valid auth token. To be able to handle the response by myself I set the node-http-proxy option selfHandleResponse to true.
app.use(
'/',
index({
target: config.hostname,
changeOrigin: true,
onProxyReq,
onProxyRes,
selfHandleResponse: true
})
);
Now, I only want to handle responses with a 401, because it is the only case I need to request another token.
const onProxyRes = (proxyRes, req, res) => {
if (proxyRes.statusCode === 401) {
getToken()
.then(forwardRequest(req))
.then((response) => {
res.send(response.body);
})
.catch(() => {
res.sendStatus(500);
});
}
// How to pass back the original proxyRes including body, headers etc.?
});
EDIT: forwardRequest is my function to retry the original request and then hand it back to the client when successful.
My trouble starts here, since I have no idea on how to pass the proxied non-401 request back to the client. I found myself starting to implement body parsing logic dependent on header content-type, but I very much hope there is an easier way.
Any ideas?

The solution is simple...
proxyRes.pipe(res);
Found in the source code of node-http-proxy.

Related

How does a pre-flight request receive headers without having a response?

I've been reading about CORS and pre-flight requests, and have understood that basically it is a type of an OPTIONS request that is sent before making the actual request and if the server permits it then the actual request is sent again. But what's confusing me the most is that how is the browser able to receive response headers without receiving the actual response from the server?? Are headers sent regardless if a response has been sent or not?? Like in the below nodejs code snippet you could see that I've created a middleware which receives the OPTIONS request and responds with the headers required for the actual request to be sent. And once it is validated on the same login route browser sends the actual request. But you must've noticed that I'm not sending a response from the handler of the OPTIONS middleware function I'm only sending the headers. Then how is this all still working out?? how are the headers sent to the browser without a response?
app.options("/login", (req, resp, next) => {
resp.header("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
resp.header("Access-Control-Allow-Methods", "GET, POST, PUT");
resp.header("Access-Control-Allow-Headers", "Content-type");
next();
});
app.post("/login", (req, resp) => {
resp.header("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
resp.header("Access-Control-Allow-Methods", "GET, POST, PUT");
resp.header("Access-Control-Allow-Headers", "Content-type");
resp.json({
msg:"Sucess"
});
});
To answer this question you need to take a look at the express source code. In router\index.js (which will be eventually executed after you called next() from your handler), you'll find the following code:
// for options requests, respond with a default if nothing else responds
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}
In your case the if condition is false since you did not respond in your handler resulting in a call to sendOptionsResponse, which is defined as follows:
// send an OPTIONS response
function sendOptionsResponse(res, options, next) {
try {
var body = options.join(',');
res.set('Allow', body);
res.send(body);
} catch (err) {
next(err);
}
}
And that's why a response is actually sent even though you did not explicitly send it from your handler.
Btw: you might wanna take a look at the cors-package which greatly helps to facilitate the cors-setup.

Using https for REST requests from inside expressjs applicaiton

From inside my expressJS application I have to verify that a cookie token is valid with a back-end server. So the relevant code involved in this is as follows:
app.get('*', (req, res, next) => {
console.log('GET: ' + req.path);
// ...
const payload = JSON.stringify({ authnToken: token });
const opts = { ... authServerOptions };
opts.headers['Content-Length'] = payload.length;
// build request
const restReq = https.request(authServerOptions, result => {
console.log('back-end response' + result.statusCode);
result.on('data', data => {
next(); // token is good now proceed.
});
result.on('error', error => {
res.redirect('somewhere'); // token is bad or timeout
});
});
restReq.write(token);
restReq.end();
}
So the main get function sets the REST request in motion and then just returns without calling next() or anything.
Questions:
Is this the right code for doing this? What happens if the callbacks are never called?
Is the application blocked from processing other requests until the back-end server returns or times out?
If so is there some way of freeing up the thread to process more requests?
Thanks in advance for any help. I haven't found many examples for this code pattern so if there is one a link would be appreciated.
Yes, I think the general idea of your implementation is correct.
I would also suggest, as done in the comments, to use a client such as axios to handle the request in a less verbose and more comprehensive manner, which would leave your code looking something like this:
const axios = require('axios');
app.get('*', (req, res, next) => {
const payload = JSON.stringify({ authnToken: token });
const opts = { ... authServerOptions };
opts.headers['Content-Length'] = payload.length;
axios.post(url, payload, opts)
.then(response => next())
.catch(error => {
console.error(error);
res.redirect('somewhere');
});
});
A bit more to the point, but functionally almost equivalent to your implementation. The one thing you are missing is the onerror callback for your request object, which currently may fail and never return a response as you correctly suspected. You should add:
restReq.on('error', error => {
console.error(error);
res.redirect('somewhere');
});
On the same vein, it would probably be more fitting to call next on result end, instead of doing so while reading response data:
result.on('end', () => {
next();
});
Then you'd be covered to guarantee that a callback would be invoked.
Neither implementation blocks the processing of future requests, as the call to the token validation service is done asynchronously in both cases.

Conditional redirect with express / request-promise

I am rather new with express together with the request-promise module,
and need to create a service S
that is called from serverA
and after S has asked ServerB for some additional info,
it redirects the request of serverA to ServerC.
Since I get a
Error: Can't set headers after they are sent.
even though I do not add something by myself, I wonder someone could help me to get this workflow straight.
This is the code:
`
const express = require('express')
const rp = require('request-promise')
...
app.get('/dispatch', cors(), (req, res, next) => {
var options = {
uri: 'https://ServerB/calc-something..',
headers: {
'User-Agent': 'its-me',
'Data': data_from_serverA
},
resolveWithFullResponse: true, // Get statuscode
json: true // Parse the JSON string in the response
};
rp(options) // Do request to serverB
.then(function (response) {
console.log(`ServerB responded with statuscode ${response.statusCode}`)
// No error, so redirect original res
res.redirect('https://serverC/...') // error occurs here
return next(response)
})
.catch(function (err) {
console.log(`ServerB responded with error ${err}`)
return next(err) // send 500 to serverA
})
})
`
Your cors() middleware is setting CORS headers. This is causing the headers to be sent while your promise is resolving.
A redirect ALSO sends headers, and this is the issue. A redirect sets a location header, but you've already sent the headers so that won't work.
The solution is to split your final middleware into two. First, check to see if a redirect is needed and if so, do that. Otherwise, set whatever data you need on the req object and handle this AFTER the cors call.
Your final route will look something like:
app.get('/dispatch', checkRedirect, cors(), (req, res, next) => {
//do something useful, or send your error
})
The contents of your checkRedirect function will be pretty similar to what you have above. However, you do not pass data to the next() function. That just passes control to the next middleware. Instead, put any data you need on the req object and handle it in the final middleware, AFTER cors. If all you are doing is setting a 500 error, you don't even need CORS for that.
According to #Rampant 's answer,
this is how I did it with request-promise (rp):
function checkPrecondition(req, res, next){
req.precondition = false
rp({ method: 'POST',
...
})
.then((data) => {
...
req.precondition = true
next()
})
.catch((data) => {
...
next()
})
}
and in the express handler:
app.post('/query', checkPrecondition, cors(), (req, res, next) => {
if(!req.precondition){
res.status(400).send(JSON.stringify({status: 'insufficient'}))
return
}
res.redirect('target.host')
})
Thanks for clearifying the CORS issue.

Fetch/React not redirecting with Node/Express res.redirect()

On my React front-end, I make a fetch() GET call to my Node/Express server. The Node/Express server checks if response has a certain cookie, and if not should redirect it. My code is something like this:
React code
componentDidMount(){
fetch('/example',{redirect: "follow"})
.then(data => data.json())
.then(res => {
//do something with response
}
})
}
Node/Express:
router.use((req, res, next) => {
let { token } = req.cookies;
if(token){
next();
}
else{
res.redirect(303, '/login');
}
});
router.get('/example', (req, res) =>{
//sends a json response back
})
It might be important to note that I am using react-router-dom as a browser router, thought I'm not sure what this does to server redirects. I've searched and saw people who said to set redirect to "follow". However, even when setting status to a 3xx and setting redirect to "follow", the page still doesn't redirect properly. Instead, it receives a response with 200 OK status(which I don't understand how it could have changed, unless Node/Express didn't actually set it to '303'), redirected: true, and a response.url pointing to the redirect url, but still stays on the same page. I've made sure that the cookie isn't there, so that's not it. I know I can probably check response.redirected every time and manually set window.location = response.url, but I still don't understand why the redirect did not work. Do I have to set it manually everytime then?

Express Session and Request Modules

In my website, everything server-side is stored in sessions of express-session.
But I can't understand why, when I make an HTTP request with request module, the req.session parameter isn't within the request.
I mean, follow the comments :
app.get('/prefix', async function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is true, and so it works
if (req.session.login == false || req.session.login == null) return;
var options = {
url: 'https://bot-dreamsub.glitch.me/getPermission',
json: true,
jar: true,
withCredentials: true
}
request(options, async function(err, res, json) {
if (err) {
throw err;
}
console.log(json);
if (json == true) {
res.sendFile(__dirname + '/prefix/prefix.html');
} else {
return;
}
});
});
app.get('/getPermission', function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is undefined, and so it sends null to the user
try {
if (req.session.login == false || req.session.login == undefined) {
res.send(null);
} else {
// some other code
}
} catch(err) {
console.log(err);
};
});
I don't know why request doesn't send sessions within the HTTP request even with
withCredentials: true
What can I do to accomplish it?
An express-session works by setting a cookie in the client's browser that made the original request. Future requests with that same cookie will offer access to that same session. When you do request() yourself from your server, you aren't presenting the same cookie that came in with the original /prefix request so you won't have access to the same session.
Since it appears you are just trying to use request() to call your own server, I'd suggest you just use a function call and pass the original req.session to that function call so that you will have it available.
You then use normal code factoring to factor out some common code between your /getPermissions route and what you want to use in your /prefix route so that they can both use and share a common function that you pass the current req and res to. Then you don't need to solve this cookie issue because you'll already have the right req object and thus the correct req.session in this factored common function.
Alternatively, you could build the right cookie and send that with your request() so that it will appear to be coming from the original browser that has the cookie (and thus session) that you want, but that's kind of the long way to do things when you already have the req.session you want and you could just pass it in a function call rather than start all over and try to simulate a cookie that will get you to the right session.
I don't know why request doesn't send sessions within the HTTP request even with
First off, session aren't sent with a request. Cookies are. Your server then uses the cookie to find the right session object.
Your call to request() does not have the right cookie in the cookie jar you use so when that requests gets to your server, it isn't able to find the right session object. So, when the request is received by your server, it appears to be coming from a different client that does not yet have a session so a new cookie and a new session are probably created for it.
FYI, if also looks like you may be confusing two definitions of res in your request() call. There's a res defined as an argument in this app.get('/prefix', async function(req, res) { and then you have a separate res in request(options, async function(err, res, json) { that will override the previous one in that scope. It appears to me when you do res.sendFile(__dirname + '/prefix/prefix.html');, you are probably using the wrong res. Probably the best way to solve this is to not use request() at all as suggested above (using a function call to your own server). But, if you were going to still use request(), then you need to name the two res arguments differently so you can still access them both and can use the correct one for your situation.

Resources