Reverse proxy to CouchDB hangs on POST and PUT in Node.js - node.js

I use request to implement the following reverse proxy to CouchDB in Express:
app.all(/^\/db(.*)$/, function(req, res){
var db_url = "http://localhost:5984/db" + req.params[0];
req.pipe(request({
uri: db_url,
method: req.method
})).pipe(res);
});
When making GET requests, it works: requests go from the client to node.js to CouchDB and back again successfully. POST and PUT requests hang indefinitely. Log statements run until the proxy, but CouchDB doesn't indicate receipt of the request. Why is this happening, and how can it be fixed?

Express' bodyparser middleware modifies the request in a way that causes piping to hang. Not sure why, but you can fix it by making your proxy into middleware that catches before the bodyparser. Like this:
// wherever your db lives
var DATABASE_URL = 'http://localhost:5984/db';
// middleware itself, preceding any parsers
app.use(function(req, res, next){
var proxy_path = req.path.match(/^\/db(.*)$/);
if(proxy_path){
var db_url = DATABASE_URL + proxy_path[1];
req.pipe(request({
uri: db_url,
method: req.method
})).pipe(res);
} else {
next();
}
});
// these blokes mess with the request
app.use(express.bodyParser());
app.use(express.cookieParser());

request makes get requests by default. You need to set the method.
app.all(/^\/db(.*)$/, function(req, res){
var db_url = ["http://localhost:5984/db", req.params[0]].join('/');
req.pipe(request({
url: db_url,
method: url.method
})).pipe(res);
});
(code untested, let me know if it doesn't work, but it should be close)

Related

Avoid global variables in Node, how to return the body of a request from a POST call

I'm still new to Node so I'm sure I'm doing something wrong, but some searching isn't helping so here we are.
I'm making a request to an API to get weather data. I can get the data and log it to the console no problem, but I'm having trouble getting the body of the request to end up in the response to the original POST.
var express = require('express');
var request = require('request');
var bodyParser = require('body-parser');
// create a new express server
var app = express();
// serve the files out of ./public as our main files
app.use(express.static(__dirname + '/public'));
// make the web server use body-parser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// start server on the specified port and binding host
app.listen(appEnv.port, '0.0.0.0', function() {
console.log("server starting on " + appEnv.url);
});
// Send information from the weather API to the console
app.post('/processWeather', function (req, res) {
requestString = 'http://api.openweathermap.org/data/2.5/weather?id=7839805&appid=xxxxxxxx';
request(requestString, function(err, res, body){
if (!err && res.statusCode == 200) {
console.log(body);
}
});
//redirect back to homepage after getting the weather
res.redirect("/");
});
So the problem with this is that I can't simply use the body variable in the app.post callback. I'm suspicious this is to do asynchronous logic but I'm as I'm new I can't wrap my head around the best way to do this without using a global variable to temporarily store the body variable. How can I get the contents of the body variable sent back to the browser? Any help greatly appreciated. Cheers.
Don't use global variables unless it's absolutely necessary!
You can use session.
req.session['weather'] = weatherData; // Weather data
res.redirect("/");
You can use a lot of other ways also. But this is what I'd prefer.
I figured out what I needed. All I had to do was place the request in the res.send
res.send(request(requestString));

node-http-proxy POST request times out

I am using node-http-proxy for the POST request as follows:
route.js
---------
var express = require('express');
var httpProxy = require('http-proxy');
var bodyParser = require('body-parser');
var proxy = httpProxy.createProxyServer({secure:false});
var jsonParser = bodyParser.json();
proxy.on('proxyReq', function(proxyReq, req, res, options) {
logger.debug("proxying for",req.url);
//set headers
logger.debug('proxy request forwarded succesfully');
});
proxy.on('error', function (err, req, res) {
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end('Something went wrong. And we are reporting a custom error message.');
});
proxy.on('proxyRes', function (proxyRes, req, res) {
console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
});
module.exports = function(app){
app.post('/recording',jsonParser,function(req,res){
// update request body
proxy.web(req, res, { target: <<host>>:<<port>>});
});
}
app.js
---------
var express = require('express');
var app = express();
require('./routes')(app);
app.listen(8080);
console.log("Demo server running");
I also use bodyparser middleware and it has a known issue as mentioned in Gitbug issue. So I tried adding this line as the last line in app.js
app.use(require('connect-restreamer')());
But still the POST request hangs and ultimately fails. How do I fix this ? Is there any alternatives for bodyparser ?
Try reversing the order of the bodyParser- and proxy middleware:
module.exports = function(app){
app.post('/recording', function(req,res){
// update request body
proxy.web(req, res, { target: <<host>>:<<port>>});
}, jsonParser);
}
Think this issue is similar to: socket hang up error with nodejs.
To expand on this a bit, what's happening here is that the node request is a stream, it can only be read once, after that the stream data is consumed.
When you use body-parser middleware in express, it will consume the request stream body - if you try to proxy the request after this, there's no body stream to send, so the other end of the proxy receives a POST with a content-length, etc... but waits indefinitely to receive the POST body which never arrives.
If you want to proxy POST/PUT or really any requests that contain a body, you have to do that before any middleware consumes the body. That's why #chimmurai answer above works.
Also, be aware that for the same reason, middleware that executes after you proxy a request will be affected the same way, once the request stream is consumed there won't be anything for subsequent middleware to read. That's the reason for things like connect-restreamer.

How to forward a request to other endpoint in node.js

In my scenario I need forward get request to another end point. In my machine there are two servers php and node.js server. Node.js is like a "man in the middle", PHP server must work in the same way.
Node.js server code
var express = require('express');
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var app = express();
var HTTP_PORT = 3000;
// Create an HTTP service
http.createServer(app).listen(HTTP_PORT,function() {
console.log('Listening HTTP on port ' + HTTP_PORT);
});
//endpoint for tracking
app.get('/track', function(req, res) {
sendRequestToOtherEndPoint(req);
processRequest(req);
res.setHeader('Content-Type', 'application/json');
res.send('Req OK');
});
function processRequest(req){
console.log("request processed");
}
function sendRequestToOtherEndPoint(req){
//magic here :)
}
When this server receive a get request in port 3000, it process request information and it must forward the same requesto to another end point.
For example:
Get localhost:3000/track?param1=1&param2=2
Server process get request
Server forward get request to localhost/final-endpoint?param1=1&param2=2
Depending on what you're trying to do, you can create a new request to the end-point:
//endpoint for tracking
app.get('/track', function(req, res) {
req.get({url: 'http://end-point', headers: req.headers});
processRequest(req);
res.setHeader('Content-Type', 'application/json');
res.send('Req OK');
});
More info: https://github.com/request/request
There are a couple of useful libraries that one could use:
http-proxy-middleware:
const proxy = require('http-proxy-middleware')
var apiProxy = proxy('/track', {target: 'http://end-point'});
app.use(apiProxy)
axios-express-proxy
import express from 'express';
import { Proxy } from 'axios-express-proxy';
const app = express();
const port = 3000;
app.get('/track', (req, res) => Proxy('http://end-point', req, res));
In you case res.redirect might help.
app.get('/track', function(req, res) {
// process the request
// then redirect
res.redirect('/final-endpoint');
});
Then catch the redirected request in final endpont.
app.get('/final-endpoint', function(req, res) {
// proceess redirected request here.
});
See the Express docs
If your second endpoint is on a different server, (e.g. PHP) then you're going to need to either redirect the client (as in sohel's answer), or spoof a request from Node to the PHP server and then send the response back to the client. This latter option is definitely non-trivial so I would question whether it's crucial not to use a client redirect.
If you're talking about two express endpoints, then I think the simplest answer might be not to actually forward at all, but just use the endpoint callback directly instead:
app.get('/track', trackCallback);
app.get('/otherendpoint', otherendpointCallback);
function otherendpointCallback(req, res) {
// do your thing
}
function trackCallback(req, res) {
otherendpointCallback(req, res);
processRequest(req);
res.setHeader('Content-Type', 'application/json');
res.send('Req OK');
};
Depending on exactly what you want to do at the other end point, you might need to spoof some of req's fields (e.g. req.url)

AngularJS $resource makes HTTP OPTIONS request instead of HTTP POST for $save method

I'm in the process of writing a simple library application to get ready for a larger project with AngularJS. After reading a lot online about using $resource to interact with a RESTful API, I decided that it would probably offer some time-saving and scaling benefits to implement it instead of using $http for each request. The problem is that for some reason (I'm no expert on CORS and the request is being sent cross-domain) when using the $save method my Node.js console shows:
OPTIONS /books 200 1ms - 161b
Using the query() method works fine - the Node console shows:
GET /books 200 1ms - 228b
I've been stuck for several hours at this point, trying variations on the below but it always ends up being an OPTIONS request instead of POST (which is what it should be according to the Angular documentation) for the $save method.
AngularJS Web App
app.js
var libraryApp = angular.module('libraryApp', ['ngResource', 'ngRoute', 'libraryControllers']);
libraryApp.factory('$book', ['$resource', function ($resource) {
return $resource('http://mywebserver\\:1337/books/:bookId', { bookId: '#bookId' });
}]);
controllers.js
var libraryControllers = angular.module('libraryControllers', []);
libraryControllers.controller('BookCtrl', ['$scope', '$book', function($scope, $book) {
...
$scope.addBook = function () {
var b = new $book;
b.isbn = "TEST";
b.description = "TEST";
b.price = 9.99;
b.$save();
};
}]);
Node.js with Express REST API
app.js
var express = require('express'),
books = require('./routes/books'),
http = require('http'),
path = require('path');
var app = express();
...
// enable cross-domain scripting
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
// routing
app.get('/books', books.getAll);
app.get('/books/:isbn', books.get);
// This is what I want to fire with the $save method
app.post('/books', books.add);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
./routes/books.js
...
exports.add = function(req, res) {
console.log("POST request received...");
console.log(req.body.isbn);
};
Tried putting this line in my config function delete $httpProvider.defaults.headers.common["X-Requested-With"]; but no change.
I'm no Angular/Node pro but right now I'm thinking that it's something to do with it being cross domain and, like I said, I'm no expert on CORS.
Thanks in advance.
I know it may be in bad taste to answer my own question but I figured out the problem a few days after posting this.
It all comes down to how browsers manage CORS. When making a cross-domain request in JavaScript that is not "simple" (i.e. a GET request - which explains why the query() function worked), the browser will automatically make a HTTP OPTIONS request to the specified URL/URI, called a "pre-flight" request or "promise". As long as the remote source returns a HTTP status code of 200 and relevant details about what it will accept in the response headers, then the browser will go ahead with the original JavaScript call.
Here's a brief jQuery example:
function makeRequest() {
// browser makes HTTP OPTIONS request to www.myotherwebsite.com/api/test
// and if it receives a HTTP status code of 200 and relevant details about
// what it will accept in HTTP headers, then it will make this POST request...
$.post( "www.myotherwebsite.com/api/test", function(data) {
alert(data);
});
// ...if not then it won't - it's that simple.
}
All I had to do was add the details of what the server will accept in the response headers:
// apply this rule to all requests accessing any URL/URI
app.all('*', function(req, res, next) {
// add details of what is allowed in HTTP request headers to the response headers
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Credentials', false);
res.header('Access-Control-Max-Age', '86400');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
// the next() function continues execution and will move onto the requested URL/URI
next();
});
And then insert these few lines before the Express routing to simply return a HTTP 200 status code for every OPTIONS request:
// fulfils pre-flight/promise request
app.options('*', function(req, res) {
res.send(200);
});
Hopefully this will help anyone who stumbles on this page suffering from the same problem.
I didn´t actually try this, but wouldn´t it be enough to tell the Ressource how to handle the $save request?
$resource('http://mywebserver\\:1337/books/:bookId', { bookId: '#bookId' }, {save: {method: 'POST'});

Node.js Http-proxy How to pass information to target server

I am building a web application using node.js and I need to proxy certain routes to another Node.js (express) server
I have the below code
var express = require('express'),
http=require('http'),
httpProxy = require('http-proxy'),
proxy = new httpProxy.RoutingProxy();
var server = express();
server.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
server.use(express.logger());
server.use(express.cookieParser());
server.use(express.query());
server.use(express.session({secret:"secret-key", maxAge:10*60*1000}));
server.use(routeRequest);
var routeRequest = function (req, res, next) {
req.url=req.url.substring(1);
if (req.url.indexOf("client")==0) {
proxyToClientServer(9001,req,res)
}else{
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end();
}
}
var proxyToClientServer=function(port, req, res){
req.headers["data_context"]='customer_details';
proxy.proxyRequest(req, res, {
host: 'localhost',
port: port,
headers:req.headers
})
}
server.listen(8000);
My problem is the request gets routed properly, but the data context (custom header) i added is removed from the request at the target server
Can somebody please advice how to pass more information to the server thats is being proxied
As robertklep mentioned, the code was working , I was looking at the wrong place.
But one thing to remember is that we could only add Strings. Any objects I added was gone but I had to JSON.stringify(obj) and add to header to make it go through.
Objects that could not be stringified (like socket handles) could not be passed along.

Resources