How to listen to node http-proxy traffic? - http

I am using node-http-proxy. However, in addition to relaying HTTP requests, I also need to listen to the incoming and outgoing data.
Intercepting the response data is where I'm struggling. Node's ServerResponse object (and more generically the WritableStream interface) doesn't broadcast a 'data' event. http-proxy seems to create it's own internal request, which produces a ClientResponse object (which does broadcast the 'data' event) however this object is not exposed publically outside the proxy.
Any ideas how to solve this without monkey-patching node-http-proxy or creating a wrapper around the response object?

Related issue in issues of node-http-proxy on Github seems to imply this is not possible. For future attempts by others, here is how I hacked the issue:
you'll quickly find out that the proxy is only calling writeHead(), write() and end() methods of the res object
since res is already an EventEmitter, you can start emitting new custom events
listen for these new events to assemble the response data and then use it
var eventifyResponse = function(res) {
var methods = ['writeHead', 'write', 'end'];
methods.forEach(function(method){
var oldMethod = res[method]; // remember original method
res[method] = function() { // replace with a wrapper
oldMethod.apply(this, arguments); // call original method
arguments = Array.prototype.slice.call(arguments, 0);
arguments.unshift("method_" + method);
this.emit.apply(this, arguments); // broadcast the event
};
});
};
res = eventifyResponse(res), outputData = '';
res.on('method_writeHead', function(statusCode, headers) { saveHeaders(); });
res.on('method_write', function(data) { outputData += data; });
res.on('method_end', function(data) { use_data(outputData + data); });
proxy.proxyRequest(req, res, options)

This is a simple proxy server sniffing the traffic and writing it to console:
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
// assign events
proxy.on('proxyRes', function (proxyRes, req, res) {
// collect response data
var proxyResData='';
proxyRes.on('data', function (chunk) {
proxyResData +=chunk;
});
proxyRes.on('end',function () {
var snifferData =
{
request:{
data:req.body,
headers:req.headers,
url:req.url,
method:req.method},
response:{
data:proxyResData,
headers:proxyRes.headers,
statusCode:proxyRes.statusCode}
};
console.log(snifferData);
});
// console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
});
proxy.on('proxyReq', function(proxyReq, req, res, options) {
// collect request data
req.body='';
req.on('data', function (chunk) {
req.body +=chunk;
});
req.on('end', function () {
});
});
proxy.on('error',
function(err)
{
console.error(err);
});
// run the proxy server
var server = http.createServer(function(req, res) {
// every time a request comes proxy it:
proxy.web(req, res, {
target: 'http://localhost:4444'
});
});
console.log("listening on port 5556")
server.listen(5556);

I tried your hack but it didn't work for me. My use case is simple: I want to log the in- and outgoing traffic from an Android app to our staging server which is secured by basic auth.
https://github.com/greim/hoxy/
was the solution for me. My node-http-proxy always returned 500 (while the direct request to stage did not). Maybe the authorization headers would not be forwarded correctly or whatever.
Hoxy worked fine right from the start.
npm install hoxy [-g]
hoxy --port=<local-port> --stage=<your stage host>:<port>
As rules for logging I specified:
request: $aurl.log()
request: #log-headers()
request: $method.log()
request: $request-body.log()
response: $url.log()
response: $status-code.log()
response: $response-body.log()
Beware, this prints any binary content.

Related

How to pass self response data with selfHandleResponse in Http Proxy NextJS

I am using http-proxy to forward all requests in NextJS, I want to handle the self response data with selfHandleResponse option. Specifically, I want to do something like set a some data to cookie, etc...) in Next.js API Routes, but if I set selfHandleResponse to true, none of the webOutgoing passes are called, that resulted in my request never being responded. What should I do? Please help me, I have researched but no hope :(
My code below:
const handleProxyInit = (proxy) => {
proxy.on('proxyRes', (proxyRes, req, res) => {
let body = [];
proxyRes.on('data', function (chunk) {
body.push(chunk);
});
proxyRes.on('end', function () {
body = Buffer.concat(body).toString('utf8');
// what should I do here?
});
});
};
httpProxyMiddleware(req, res, {
...,
selfHandleResponse: true,
changeOrigin: true,
onProxyInit: handleProxyInit,
});

Webhook for Mailgun POST?

I am trying to store email messages as JSON (as parsed by Mailgun) in a Mongo.Collection through a Mailgun webhook. I set up an iron-router server-side route to handle the request, but this.request.body is empty. I am using Mailgun's "Send A Sample POST" to send the request, and the POST looks fine using e.g. http://requestb.in/. I was hoping that request.body would have the data, as mentioned in How do I access HTTP POST data from meteor?. What am I doing wrong?
Router.map(function () {
this.route('insertMessage', {
where: 'server',
path: '/api/insert/message',
action: function() {
var req = this.request;
var res = this.response;
console.log(req.body);
...
I'm not sure that is the right syntax. Have you tried using Router.route ?
Router.route('insertMessage',
function () {
// NodeJS request object
var request = this.request;
// NodeJS response object
var response = this.response;
console.log("========= request: =============");
console.log(request);
// EDIT: also check out this.params object
console.log("========= this.params: =============");
console.log(this.params);
// EDIT 2: close the response. oops.
return response.end();
},
{
where: 'server',
path: '/api/insert/message'
}
);
I think the issue is that Mailgun sends a multipart POST request, e.g. it sends "fields" as well as "files" (i.e. attachments) and iron-router does not set up a body parser for multipart requests. This issue is discussed here and here on iron-router's Github Issues. I found this comment particularly helpful, and now I can parse Mailgun's sample POST properly.
To get this working, in a new Meteor project, I did
$ meteor add iron:router
$ meteor add meteorhacks:npm
In a root-level packages.json I have
{
"busboy": "0.2.9"
}
which, using the meteorhacks:npm package, makes the "busboy" npm package available for use on the server via Meteor.npmRequire.
Finally, in a server/rest-api.js I have
Router.route('/restful', {where: 'server'})
.post(function () {
var msg = this.request.body;
console.log(msg);
console.log(_.keys(msg));
this.response.end('post request\n');
});
var Busboy = Meteor.npmRequire("Busboy");
Router.onBeforeAction(function (req, res, next) {
if (req.method === "POST") {
var body = {}; // Store body fields and then pass them to request.
var busboy = new Busboy({ headers: req.headers });
busboy.on("field", function(fieldname, value) {
body[fieldname] = value;
});
busboy.on("finish", function () {
// Done parsing form
req.body = body;
next();
});
req.pipe(busboy);
}
});
In this way I can ignore files (i.e., I don't have a busboy.on("file" part) and have a this.request.body available in my routes that has all the POST fields as JSON.

Setting the "Content-Type" header in HTTP.call on client side in Meteor

I'm trying to use Meteor's (v1.0) HTTP.call method to communicate with a Python-based server which accepts only application/json content type in header but I cannot set the HTTP header properly in Meteor when calling the API URL from a client side.
With a snippet like this, I get a 415 (Unsupported Media Type) error from Python server:
if (Meteor.isClient) {
Template.testing.events({
'click button': function(event, tpl) {
event.preventDefault();
var method = 'GET';
var url = 'http://localhost:6543/test';
var options = {
headers: {'Content-Type': 'application/json'}
}
HTTP.call(method, url, options, function(error, result) {
if (error) {
console.log('ERRR');
console.log(error);
} else
console.log('RESULT');
console.log(result);
});
}
});
}
However, if I call the same URL from a server side in Meteor like this:
if (Meteor.isClient) {
Template.testing.events({
'click button': function(event, tpl) {
event.preventDefault();
var method = 'GET';
var url = 'http://localhost:6543/test';
var options = {
headers: {'Content-Type': 'application/json'}
}
Meteor.call('APICall', method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
console.log(result);
}
});
}
});
}
if (Meteor.isServer) {
Meteor.methods({
APICall: function (method, url, options) {
HTTP.call(method, url, options, function(error, result) {
if (error) {
console.log('SERVER ERRR');
console.log(error);
} else
console.log('SERVER RESULT');
console.log(result);
});
}
});
}
I get a proper response from the server.
On Python side I enabled CORS origins for all possible requests (e.g. cors_origins=('*')).
So... Is is possible to set header on client-side or should I always call this service from server-side?
I've never had any success on the client-side either, but it is supposed to. Check out the HTTP.call client part of the meteor HTTP package:
https://github.com/meteor/meteor/blob/devel/packages/http/httpcall_client.js
Mostly it uses the browser XHR object on the client-side which can lead to a host of problems, like incompatibilities and stuff. You can even see an issue referenced on one of the code comments (around line 136)
And when you check out the server implementation you can see it uses the request library (from connect), which, in my book, is very reliable and you can generate uniform results across all users (and not dance around browser differences).
My choice and recommendation for you is obviously going to be server-side calls. Not just because it works and it's reliable, it's also 'safer' on your part as you don't have to expose more inner workings of your system to the client/end-user. Who knows? maybe you have sensitive data on your API run on your Python-based server.

Which node.js HTTP proxy implementation is more performant?

Which one of the following node.js HTTP proxy implementations is more performant?
The first implementation is:
var http = require('http');
http.createServer(function(request, response) {
var proxy = http.createClient(80, "google.com")
var proxy_request = proxy.request(request.method, request.url, request.headers);
proxy_request.addListener('response', function (proxy_response) {
proxy_response.addListener('data', function(chunk) {
response.write(chunk, 'binary');
});
proxy_response.addListener('end', function() {
response.end();
});
response.writeHead(proxy_response.statusCode, proxy_response.headers);
});
request.addListener('data', function(chunk) {
proxy_request.write(chunk, 'binary');
});
request.addListener('end', function() {
proxy_request.end();
});
}).listen(8080);
The second one uses stream.pipe() and it's like:
var http = require('http');
http.createServer(function(request, response) {
var proxy = http.createClient(80, "google.com");
var proxy_request = proxy.request(request.method, request.url, request.headers);
proxy_request.on('response', function (proxy_response) {
proxy_response.pipe(response);
response.writeHead(proxy_response.statusCode, proxy_response.headers);
});
request.pipe(proxy_request);
}).listen(8080);
The first one might blow up your process if the file is big and the clients connection is slow or if an uploaded file is big and the servers upload bandwidth is small. Use pipe, it's designed for this kind of stuff.
Also, use an existing module from npm for this:
many features and used in production at nodejitsu: http-proxy
fast: bouncy

Express / NodeJS Can't send headers after they are sent caused by http requests

First time working with NodeJS (yes, it's awesome) and also using Express as well. Got the web app / service working great but I run in to problems when trying to make more than one http request. Here's a video of how the app causes 2 http requests - http://screencast.com/t/yFKdIajs0XD - as you can see I click on 'articles' it loads an rss feed, then click videos and it loads a youtube feed - both work just fine but after the second call is made it throws an exception. I get the following when I attempt two separate http requests using node's http module:
http.js:527
throw new Error("Can't set headers after they are sent.");
^
Error: Can't set headers after they are sent.
at ServerResponse.<anonymous> (http.js:527:11)
at ServerResponse.setHeader (/Users/rickblalock/node/auti_node/node_modules/express/node_modules/connect/lib/patch.js:47:22)
at /Users/rickblalock/node/auti_node/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js:72:19
at [object Object].<anonymous> (fs.js:107:5)
at [object Object].emit (events.js:61:17)
at afterRead (fs.js:878:12)
at wrapper (fs.js:245:17)
Sample code provided here:
Using my controller module to render the request - http://pastie.org/2317698
One of the tabs (article tab) - the video code is identical minus referencing a video feed: http://pastie.org/2317731
try using the "end" event not "data" like this:
var data = "";
app.get('/', function(req, res){
var options = {
host: 'http://www.engadget.com',
path: '/rss.xml',
method: 'GET'
};
if (data === "") {
var myReq = http.request(options, function(myRes) {
myRes.setEncoding('utf8');
myRes.on('data', function(chunk) {
console.log("request on data ");
data += chunk;
});
myRes.on('end', function () {
console.log("request on END");
res.render('index', {
title: 'Express',
data: data
});
});
});
myReq.write('data\n');
myReq.end();
}
else {
res.render('index', {
title: 'Express',
data: data
});
}
});
old answer
i also think that this is the culprit:
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk) {
parseArticle(chunk);
});
});
req.write('data\n');
req.end();
the first line is async so everything inside the callback is called after you do req.write() and req.end()
put these two lines into the callback.

Resources