I'm expanding on a node.js reverse proxy I put together with mikeal's request module: https://github.com/mikeal/request . It is a connect app that uses a small piece of custom middleware to check if the the current request is for an API route and if so it proxies it to and from a different API server like this:
.use(function(req, res, next){
if( /^\/api\//.test(req.url) ){
req.pipe(request(apiUrl + req.url))
.on('error', function(err){
handlePipeError(err, 'Error piping request to proxy server. Details: ');
})
.pipe(res)
.on('error', function(err){
handlePipeError(err, 'Error piping proxy response to client. Details: ')
});
function handlePipeError(err, customMsg) {
console.log( customMsg, err );
res.writeHead(503, {
'Content-Length': Buffer.byteLength(err.message, 'utf8'),
'Content-Type': 'text/plain' });
res.end(err.message);
}
}
else {
next();
}
There is also some other middleware to handle static file serving and some other stuff. Once all the middleware is setup I start the server like this:
var server = require('http').createServer(app);
server.listen(port)
I would now like to expand this script to allow reverse proxying websocket connections. I tried using the nodejitsu/node-http-proxy to do modify the previous portion of the code like so:
var options = {
ws: true,
target: apiUrl,
secure: false,
xfwd: true
};
var wsProxy = require('http-proxy').createProxyServer(options);
var server = require('http').createServer(app);
server.on('upgrade', function (req, socket, head) {
console.log("--- CAUGHT UPGRADE ----");
wsProxy.ws(req, socket, head);
});
server.listen(port);
I notice the upgrade event fires off and the callback function does run but the websockets request isn't actually working (the client seems to never establish a connection). Is there a better way to achieve this
Related
Want to use NodeJS as reverse proxy to another server.
My Scenario is to authenticate all requests (each request will have jwt token), authentication using jwt in nodejs is fine.
Same request should be sent to another server after successful authentication and the response should be sent back to client.
Looked into redbird, node-http-proxy and other readily available nodejs proxy modules. Nothing have a concrete way of authenticating the jwt and redirecting to target.
Is there a module which can be used? If not any idea/what steps I can follow to achieve this? Also I will be adding tls termination.
I was able to get something to work this might not be an optimal solution. This is an http proxy server. Next quest is to modify this to use as https server with tls termination (which is not the scope for this question atleast)
let http = require('http')
let httpProxy = require('http-proxy')
let jwt = require('jsonwebtoken')
let token = require('./token.json')
let proxy = httpProxy.createProxyServer({})
let server = http.createServer(function(req, res) {
jwt.verify(req.headers["authorization"], token.secret, { issuer:'IssuerName' }, function(err, decoded) {
if(err){
console.log("Error: " + err)
res.setHeader('Content-Type','application/json')
res.write(JSON.stringify({"statusCode":401, "message":err}))
res.end()
return
}
proxy.web(req, res, {
changeOrigin: true,
target: 'https://some_url'
})
})
})
proxy.on('proxyReq', function(proxyReq, req, res, options) {
proxyReq.removeHeader('authorization')
})
proxy.on('error', function (err, req, res) {
res.setHeader('Content-Type','application/json')
res.write(JSON.stringify({"statusCode":500, "message":err}))
res.end()
})
let port = process.env.PORT || 9200
server.listen(port)
console.log('HTTP Proxy server is running on port: ' + port)
My express server has to proxy requests to another server listening on a unix socket file.
app.all('*', (req, res) => {
let opts = {
socketPath: 'file.sock',
path: req.url,
method: req.method,
headers: req.headers
};
const proxyReq = http.request(opts, (proxyRes) => {
proxyRes.pipe(res, {
end: true
});
});
req.pipe(proxyReq, { end: true });
});
The problem is that the response headers are not being passed on from the proxied server to the Express response. The client gets the Express server headers instead.
My questions:
How do I correctly pipe all of proxyRes into res?
Is there a way I can avoid having the proxied server response parsed and pipe the raw socket data back to the client? The same applies to the request. I'd like making the proxying as transparent as possible while keeping Express as the top level server.
Using http-proxy (aka node-http-proxy) in node.js, I am having trouble figuring out how to proxy web sockets when the target is determined dynamically (i.e. when processing the request).
Looking at the documentation:
https://github.com/nodejitsu/node-http-proxy
There is an example that shows being able to set the target when processing the request:
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
console.log("listening on port 5050")
server.listen(5050);
There is another example farther down showing support for websockets via proxy.ws(), but it shows the target being set statically rather than depending on the request:
//
// Setup our server to proxy standard HTTP requests
//
var proxy = new httpProxy.createProxyServer({
target: {
host: 'localhost',
port: 9015
}
});
var proxyServer = http.createServer(function (req, res) {
proxy.web(req, res);
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
proxyServer.listen(8015);
I took the first example and added the proxyServer.on('upgrade'... proxy.ws() ... stuff from the second example in order to get an example that sets the target while processing the request and also supports websockets. HTTP web pages seem to work fine, but it throws an exception when handling a websocket request.
'use strict';
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
console.log("listening on port 5050")
server.listen(5050);
The exception happens in the proxy.ws(req, socket, head) call:
Error: Must provide a proper URL as target
at ProxyServer.<anonymous> (...../node_modules/http-proxy/lib/http-proxy/index.js:68:35)
at Server.<anonymous> (...../poc.js:26:9) // the location in my sample code of the proxy.ws(req, socket, head) above
at emitThree (events.js:116:13)
at Server.emit (events.js:194:7)
at onParserExecuteCommon (_http_server.js:409:14)
at HTTPParser.onParserExecute (_http_server.js:377:5)
The code in http-proxy/index.js:68:35 throws this exception if there is no .target or .forward member of the options.
How do I set the target on a per request basis and also get websockets to work?
I have an answer. After looking at this question by Conrad and the comments, and then experimenting:
single-proxyserver-to-multiple-targets
proxy.ws can take an additional argument of options, just like proxy.web.
Here is the working code.
'use strict';
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head, { target: 'ws://127.0.0.1:5060' });
});
console.log("listening on port 5050")
server.listen(5050);
I'm trying to build a reverse proxy in front of a Couchbase SyncGateway. Before sending requests to the sync gateway, I'd like to send them to an authentication server for authentication, then if all is good, send the request on (unmodified from original) to the sync gateway. The database is not staying up to date with the client modifications and I believe this is because I am not successfully proxying PUT/POST requests. Here is the code I have:
var http = require('http');
var httpProxy = require('http-proxy');
var apiProxy = httpProxy.createProxyServer();
var request = require('request').defaults({json: true});
var authServer = 'http://authserverdns:5000';
var syncGateway = 'http://syncgatewaydns:4984';
http.createServer(function (req, res) {
if (req.method == 'POST' || req.method == 'PUT') {
req.body = '';
req.addListener('data', function(chunk) {
req.body += chunk;
});
req.addListener('end', function() {
processRequest(req, res);
});
} else {
processRequest(req, res);
}
}).listen(8080);
function processRequest(req, res) {
request(authServer, function(error, response, body) {
if (body.authenticated) {
console.log('authenticated !!!');
apiProxy.web(this.req, this.res, {target: this.sg});
} else {
console.log('request denied !!!');
}
}.bind({req: req, res: res, sg: syncGateway}));
}
At first I was using an express server and having same issue. As I looked into the problem, it looks like maybe there is an issue with Express and proxying PUT/POST requests. So, I attempted to use some examples out there and this is what I've ended up with, but still not working. Any ideas as to where I'm going wrong here? Authenticated prints, so I know I'm getting to the point of proxying. And the sync gateway seems to be fine with the GET requests.
Thanks
ugh. I wasn't adding the rest of the URL to the forwarding address for the Sync Gateway. The post here helped.
I have a Node.js server that I am sending a web socket upgrade request to. The Authorization header of this request contains login information, which I need to compare against a database entry. I'm unsure how I can stop the web socket connection from opening until after my database query callback is executed.
The following is a simplification of what I am currently doing:
var Express = require('express')
var app = Express()
server = app.listen(app.get("port"), function () {})
server.on("upgrade", function (request, socket) {
//Query database
//On success set "authenticated" flag on request (later accessed through socket.upgradeReq)
//On failure abort connection
})
This works, but there is a brief period of time where the socket is open but I haven't verified the Authorization header, so it would be possible for a malicious user to send/receive data. I'm mitigating this risk in my implementation through the use of an "authenticated" flag, but it seems like there must be a better way.
I tried the following things, but while they seemed to intercept all requests except the upgrade ones:
Attempt #1:
app.use(function (request, response, next) {
//Query database, only call next if authenticated
next()
})
Attempt #2:
app.all("*", function (request, response, next) {
//Query database, only call next if authenticated
next()
})
Possibly worth noting:
I do have an HTTP server as well, it uses the same port and accepts POST requests for registration and login.
Thank you for any assistance, please let me know if additional information is needed.
I'm not sure if this is correct HTTP protocol communication but it seems to be working in my case:
server.on('upgrade', function (req, socket, head) {
var validationResult = validateCookie(req.headers.cookie);
if (validationResult) {
//...
} else {
socket.write('HTTP/1.1 401 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
'\r\n');
socket.close();
socket.destroy();
return;
}
//...
});
verifyClient is implemented for this purpose!
const WebSocketServer = require('ws').Server
const ws = new WebSocketServer({
verifyClient: (info, cb) => {
const token = info.req.headers.token
if (!token)
cb(false, 401, 'Unauthorized')
else {
jwt.verify(token, 'secret-key', (err, decoded) => {
if (err) {
cb(false, 401, 'Unauthorized')
} else {
info.req.user = decoded
cb(true)
}
})
}
}
})
src:
Websocket authentication in Node.js using JWT and WS