I've put together a proxy server in Node that needs the ability to tunnel https requests over tls and that all works. Using the the following two packages this was extremely easy to setup: proxy, https-proxy-agent. My issue is that I'm trying to capture HAR files using connect as a middleware layer and I'm getting the following error:
_http_outgoing.js:357
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:357:11)
at ServerResponse.writeHead (_http_server.js:180:21)
at ClientRequest.<anonymous> (/.../node_modules/proxy/proxy.js:233:11)
at emitOne (events.js:96:13)
at ClientRequest.emit (events.js:188:7)
at HTTPParser.parserOnIncomingClient [as onIncoming] (_http_client.js:473:21)
at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
at Socket.socketOnData (_http_client.js:362:20)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
The is as simple as the following and it only seems to happen when I'm using connect and proxying through my local browser(this proxy is actually being used with BrowserStackLocal). When I pass through the proxy from anything other than my local machines browser, it's like it doesn't even know the middleware exists.
So basically, I just need to get connect working in this scenario and I'm not sure if I need to pause something and resume, or what... any ideas would be greatly appreciated. The base code is below:
const path = require('path');
const http = require('http');
const proxy = require('proxy');
const Agent = require('https-proxy-agent');
const connect = require('connect');
const har = require('./har');
const middleware = connect();
middleware.use(har({
harOutputDir: path.resolve(process.cwd(), 'har/')
}));
const server = proxy(http.createServer(middleware));
server.agent = new Agent('http://localhost:8081');
server.listen(8081)
Thanks!
EDIT: Just a note: the har middleware is not modifying headers at all.
proxy hasn't been maintained in a while. Builds are not passing, last commit don't pass tests. The source of the stack trace you've put up is coming from here in Line 233 - buggy library code.
Writing a similar proxy is trivial. Following code illustrates how to create one.
const http = require('http');
const urlP = require('url');
const proxiedServer = 'http://localhost:8888';
// Proxy server
http.createServer((req, res) => {
console.log(`Proxy: Got ${req.url}`);
const _r = http.request(
Object.assign(
{},
urlP.parse(proxiedServer),
{
method: req.method,
path: req.url
}
),
_res => {
res.writeHead(_res.statusCode, _res.headers);
_res.pipe(res);
}
);
req.pipe(_r);
}).listen(3124, () => {
console.log("Listening on 3124");
});
// Regular server. Could be Express
http.createServer((req, res) => {
console.log('Proxied server: ', req.url);
let b = '';
req.on('data', c => {
b += c;
});
req.on('end', () => {
console.log('Proxied server: ', b);
});
res.writeHead(200);
res.end('ok');
}).listen(8888, () => {
console.log('proxied server listening on 8888');
});
Your code using your own custom proxy would look like the following:
const urlP = require('url');
const path = require('path');
const http = require('http');
const connect = require('connect');
const har = require('./har');
const proxiedServer = 'http://localhost:8888';
// Proxy server
http.createServer((req, res) => {
console.log(`Proxy: Got ${req.url}`);
const _r = http.request(
Object.assign(
{},
urlP.parse(proxiedServer),
{
method: req.method,
path: req.url
}
),
_res => {
res.writeHead(_res.statusCode, _res.headers);
_res.pipe(res);
}
);
req.pipe(_r);
}).listen(3124, () => {
console.log("Listening on 3124");
});
const middleware = connect();
middleware.use(har({
harOutputDir: path.resolve(process.cwd(), 'har/')
}));
middleware.use((req, res) => {
console.log('Proxied server: ', req.url);
let b = '';
req.on('data', c => {
b += c;
});
req.on('end', () => {
console.log('Proxied server: ', b);
});
res.writeHead(200);
res.end('ok');
});
http.createServer(middleware).listen(8888, () => {
console.log('proxied server listening on 8888');
});
Related
I was following this article to setup a nodejs server on my local machine (which has 16 gb memory and about 170gb free disk-space) and uploaded a 20 gb file, for the first couple of times the file got uploaded successfully, but after a while i started getting EPIPE error:
error FetchError: request to http://localhost:3200/upload failed, reason: write EPIPE
at ClientRequest.<anonymous> (/Volumes/FreeAgent GoFlex Drive/Test/multer-project/node_modules/node-fetch/lib/index.js:1455:11)
at ClientRequest.emit (events.js:327:22)
at Socket.socketErrorListener (_http_client.js:467:9)
at Socket.emit (events.js:315:20)
at emitErrorNT (internal/streams/destroy.js:100:8)
at emitErrorCloseNT (internal/streams/destroy.js:68:3)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
type: 'system',
errno: 'EPIPE',
code: 'EPIPE'
}
When i checked, the file got uploaded partially and was about 28mb in size. I tried uploading the file from both Postman, browser and a nodejs script, but got the same EPIPE error message. I am not sure why is this happening, googling the error message didn't help. I am not sure how to overcome this. Following is my server and client code.
// server.js
const express = require("express"); // Express Web Server
const busboy = require("connect-busboy"); // Middleware to handle the file upload https://github.com/mscdex/connect-busboy
const path = require("path"); // Used for manipulation with path
const fs = require("fs-extra");
const app = express(); // Initialize the express web server
app.use(
busboy({
highWaterMark: 2 * 1024 * 1024 // Set 2MiB buffer
})
); // Insert the busboy middle-ware
const uploadPath = path.join(__dirname, "uploads/"); // Register the upload path
fs.ensureDir(uploadPath); // Make sure that he upload path exits
/**
* Create route /upload which handles the post request
*/
app.route("/upload").post((req, res, next) => {
req.pipe(req.busboy); // Pipe it trough busboy
req.busboy.on("file", (fieldname, file, filename) => {
console.log(`Upload of '${filename}' started`);
// Create a write stream of the new file
const fstream = fs.createWriteStream(path.join(uploadPath, filename));
// Pipe it trough
file.pipe(fstream);
// On finish of the upload
fstream.on("close", () => {
console.log(`Upload of '${filename}' finished`);
res.send("ok");
});
});
});
/**
* Serve the basic index.html with upload form
*/
app.route("/").get((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.write(
'<form action="upload" method="post" enctype="multipart/form-data">'
);
res.write('<input type="file" name="fileToUpload"><br>');
res.write('<input type="submit">');
res.write("</form>");
return res.end();
});
const server = app.listen(3200, function() {
console.log(`Listening on port ${server.address().port}`);
});
and my client code is:
// client.js
const fs = require("fs");
const FormData = require("form-data");
const fetch = require("node-fetch");
var formdata = new FormData();
formdata.append(
"file",
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile")
);
var requestOptions = {
method: "POST",
body: formdata,
redirect: "follow"
};
fetch("http://localhost:3200/upload", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log("error", error));
Answering my own question.
After strugging for a long time i figured out that this error was coming because the number of bytes getting written on the same is larger than the number of bytes sent to the server, so in my client code, i changed
this
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile")
to this
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile", { highWaterMark: 2 * 1024 * 1024 })
I am getting following error in my server console when user click signout button.
Error: socket hang up
at createHangUpError (http.js:1472:15)
at Socket.socketCloseListener (http.js:1522:23)
at Socket.EventEmitter.emit (events.js:95:17)
at TCP.close (net.js:466:12)
Here is my proxy_server:
var fs=require('fs');
var options = {
key: fs.readFileSync('key/xxxxxxxxx.pem'),
cert: fs.readFileSync('key/xxxxx.pem'),
ca: fs.readFileSync('key/gd_bundle-g2-g1.crt')
};
var express=require('express'),
https=require('https'),
httpProxy = require('http-proxy'),
http=require('http'),
app=express(),
app1=express(),
server=https.createServer(options,app),
serverhttp=http.createServer(app1);
var proxy = httpProxy.createProxy({ target: 'http://localhost:9898',secureOptions:'constants.SSL_OP_NO_TLSv1_2'});
var proxySig = httpProxy.createProxy({target:'http://localhost:8881',secureOptions:'constants.SSL_OP_NO_TLSv1_2'});
var callSig = httpProxy.createProxy({target:'http://localhost:6666',secureOptions:'constants.SSL_OP_NO_TLSv1_2'});
var proxyCdn = httpProxy.createProxy({target:'http://localhost:3030',secureOptions:'constants.SSL_OP_NO_TLSv1_2'});
// var proxyhttps= new httpProxy.createProxy({ target: 'https://localhost:443',secure:false});
var errorhandler = require('errorhandler');
app.all('*', function(req,res){
if(req.hostname=='xxxxxxxx.in')
{
proxy.web(req, res);
}
else if(req.hostname=='xxxx.in')
{
proxyCdn.web(req, res);
}
else if(req.hostname=='xxxxxx.in')
{
proxySig.web(req, res);
}
else if(req.hostname=='xxxxx.in')
{
callSig.web(req, res);
}
});
app1.all('*', function(req,res){
res.redirect('https://'+req.hostname);;
});
serverhttp.listen(80);
server.listen(443);
You need to handle errors on each of your httpProxy objects. For example:
proxy.on('error', function (error, req, res) {
var json;
console.log('proxy error', error);
if (!res.headersSent) {
res.writeHead(500, { 'content-type': 'application/json' });
}
json = { error: 'proxy_error', reason: error.message };
res.end(JSON.stringify(json));
});
This thread was useful to me: https://github.com/nodejitsu/node-http-proxy/issues/527
Add changeOrigin: true, to the options when you set up the proxy. It changes the origin of the host header. When the proxy is "name based" /not an ip/, its a requirement.
Short summary:
Technical prerequisite needed for name-based virtual hosts is a web browser with HTTP/1.1 support (commonplace today) to include the target hostname in the request
I'm trying to create an HTTP/S MitM forwarding proxy using Node.js.
The way I'm tackling this project is by reusing the solution found in ./lib/proxy.js file of the NPM Proxy Cache project created by #runk after he raised the issue on the Node HTTP Proxy project issue tracker.
My Proxy() class looks like this:
var request = require('request')
, https = require('https')
, http = require('http')
, net = require('net')
, url = require('url')
, os = require('os')
, fs = require('fs');
var SOCKET_PATH = os.tmpdir() + 'mitm.sock';
console.log('[SOCKET PATH] ' + SOCKET_PATH);
function Proxy (config) {
config = config || {};
if(fs.existsSync(SOCKET_PATH)) {
fs.unlinkSync(SOCKET_PATH);
}
var options = {
key: fs.readFileSync('./certs/dummy.key', 'utf8'),
cert: fs.readFileSync('./certs/dummy.crt', 'utf8')
};
// HTTPS Server
https.createServer(options, this.handler).listen(config.port + 1, this.hostname, function (e) {
if(e) {
console.log('[HTTPS] Server listen() error !');
throw e;
}
});
// HTTP Server
var server = http.createServer(this.handler);
server.listen(config.port, this.hostname, function (e) {
if(e) {
console.log('[HTTP] Server listen() error !');
throw e;
}
});
// Intercept CONNECT requests for HTTPS handshake
server.addListener('connect', this.httpsHandler);
}
Proxy.prototype.handler = function (req, res) {
var schema = !!req.client.pair ? 'https' : 'http'
, path = url.parse(req.url).path;
var dest = schema + '://' + req.headers['host'] + path;
console.log('(1) - [' + schema.toUpperCase() + '] ' + req.method + ' ' + req.url);
var params = {
rejectUnauthorized: false,
url: dest
};
if(req.method.toUpperCase() !== 'GET') {
return console.log('[HTTP] Request is not HTTP GET.');
}
var onResponse = function (e, response) {
if(e == null && response.statusCode === 200) {
return r.pipe(res);
}
var body = 'Status ' + response.statusCode + ' returned';
if(e) {
body = e.toString();
}
res.end(body);
};
var r = request(params);
r.on('response', onResponse.bind(null, null));
r.on('error', onResponse.bind(null));
};
Proxy.prototype.httpsHandler = function (request, socketRequest, bodyHead) {
var httpVersion = request['httpVersion']
, url = request['url'];
console.log('(2) - [HTTPS] ' + request['method'] + ' ' + request['url']);
var proxySocket = new net.Socket();
// ProxySocket event handlers
proxySocket.connect(SOCKET_PATH, function () {
proxySocket.write(bodyHead);
proxySocket.write('HTTP/' + httpVersion + ' 200 Connection established\r\n\r\n');
});
proxySocket.on('data', function (chunk) {
console.log('ProxySocket - "data"');
socketRequest.write(chunk);
});
proxySocket.on('end', function () {
console.log('ProxySocket - "end"');
socketRequest.end();
});
proxySocket.on('error', function (e) {
console.log('ProxySocket - "error"');
console.log(e);
console.log(e.stack);
socketRequest.write('HTTP/' + httpVersion + ' 500 Connection error\r\n\r\n');
socketRequest.end();
});
// SocketRequest event handlers
socketRequest.on('data', function (chunk) {
console.log('SocketRequest - "data"');
proxySocket.write(chunk);
});
socketRequest.on('end', function () {
console.log('SocketRequest - "end"');
proxySocket.end();
});
socketRequest.on('error', function (e) {
console.log('socketRequest - "error"');
console.log(e);
console.log(e.stack);
proxySocket.end();
});
};
module.exports = Proxy;
And my Index.js file that start my program looks like this:
var Proxy = require('./lib/proxy');
var proxy = new Proxy({
hostname: '127.0.0.1',
port: 8000
});
Here's my directory / file structure this:
/my_project
/certs
dummy.crt // Copied from the NPM Proxy Cache project
dummy.csr // Copied from the NPM Proxy Cache project
dummy.key // Copied from the NPM Proxy Cache project
/lib
proxy.js
index.js
I'm testing my program by setting (in Mac OSX Maverick) an HTTP and HTTPS proxy as IP address 127.0.0.1 and port 8000.
When browsing an HTTP only website everything works fine, but if I browse an HTTPS website I get the following error:
{[Error: connect ENOENT] code: 'ENOENT', errno: 'ENOENT', syscall: 'connect'}
Error: connect ENOENT
at errnoException (net.js:904:11)
at Object.afterConnect [as oncomplete] (net.js:895:19)
Any ideas from where this issue could come from and how to fix this ?
Thank you very much in advance !
(If you want to test my code, the NPM module request is the only dependency needed to run the code.)
EDIT: The certs can be downloaded from here : https://github.com/runk/npm-proxy-cache/tree/master/cert.
I'm an author of npm-proxy-cache. In fact I've created another project called thin https://www.npmjs.org/package/thin and I hope in future the npm proxy cache thing will utilize it. Despite the fact that it's still very rough it's usable and it does what you need.
E.g.
proxy code
var Thin = require('thin')
var proxy = new Thin;
// `req` and `res` params are `http.ClientRequest` and `http.ServerResponse` accordingly
// be sure to check http://nodejs.org/api/http.html for more details
proxy.use(function(req, res, next) {
console.log('Proxying:', req.url);
next();
});
// you can add different layers of "middleware" similar to "connect",
// but with few exclusions
proxy.use(function(req, res, next) {
if (req.url === '/foobar')
return res.end('intercepted');
next();
});
proxy.listen(8081, 'localhost', function(err) {
// .. error handling code ..
});
server code
var express = require('express'); // v3.4
var app = express();
app.use(express.urlencoded({limit: '10mb'}));
app.get('/test', function(req, res){
console.log(req.protocol, 'get req.query', req.query);
res.end('get: hello world');
});
app.post('/test', function(req, res) {
console.log(req.protocol, 'post req.query', req.query);
console.log(req.protocol, 'post req.body', req.body);
res.end('post: hello world');
});
app.listen(3000);
var fs = require('fs');
var https = require('https');
https.createServer({
key: fs.readFileSync('./cert/dummy.key'), // your mitm server keys
cert: fs.readFileSync('./cert/dummy.crt')
}, app).listen(3001);
You need to start proxy and server in two terminal sessions, then
curl -d "foo=baz" -k -x https://localhost:8081 https://localhost:3001/test?foo=bar
curl -d "foo=baz" -x http://localhost:8081 http://localhost:3000/test?foo=bar
After that you should be able to see following output from the server
https post req.query { foo: 'bar' }
https post req.body { foo: 'baz' }
http post req.query { foo: 'bar' }
http post req.body { foo: 'baz' }
Small example for interceptor
curl -d "foo=baz" -k -x https://localhost:8081 https://localhost:3001/foobar
It should return intercepted
Hope that helps :)
I am trying to make a webserver in node.js that downloads an image from Wikipedia and servers it on a page. I cant get it to work. I pasted my code in an online sandbox: http://runnable.com/UXWTyD3pTQ1RAADe.
Heres my code:
var http = require('http');
var fs = require('fs');
var fd = fs.open('name.jpeg', 'r+');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(res) {
res.on('data', function (chunk) {
fs.write(fd, chunk, 0, chunk.length, 0, null);
});
res.on('end',function(){
fd.end();
res.send("<img src='name.jpeg'></img>");
res.end();
});
});
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);
I keep running into:
node server.js
Running...
fs.js:415
binding.write(fd, buffer, offset, length, position, wrapper);
^
TypeError: Bad argument
at Object.fs.write (fs.js:415:11)
at IncomingMessage.<anonymous> (server.js:18:12)
at IncomingMessage.EventEmitter.emit (events.js:96:17)
at IncomingMessage._emitData (http.js:359:10)
at HTTPParser.parserOnBody [as onBody] (http.js:123:21)
at Socket.socketOnData [as ondata] (http.js:1485:20)
at TCP.onread (net.js:404:27)
Working code - saving image file:
/**Try to get an image from Wikipedia and return it**/
var http = require('http');
var fs = require('fs');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(imgRes) {
imgRes.pipe(fs.createWriteStream('name.jpeg'));
res.end("<html><img src='name.jpeg'></img></html>");
});
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);
You would also need node-static (http://www.sitepoint.com/serving-static-files-with-node-js/) for serving static file name.jpeg.
But the other way is to do it manually:
var http = require('http');
var fs = require('fs');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
if(req.url == '/name.jpeg') {
res.writeHead(200, ['Content-Type', 'image/jpg']);
try {
var imgData = fs.readFileSync('name.jpeg');
res.end(fs.readFileSync('name.jpeg'));
} catch(err) {
res.end();
}
}
else {
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(imgRes) {
imgRes.pipe(fs.createWriteStream('name.jpeg'));
res.end("<html><img src='name.jpeg'></img></html>");
});
}
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);
I created a proxy server in node.js using the node-http-proxy module.
Looks like this:
var http = require('http'),
httpProxy = require('http-proxy'),
io = require("socket.io").listen(5555);
var proxy = new httpProxy.RoutingProxy();
http.createServer(function (req, res) {
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 1338
});
}).listen(9000);
So, I need, before sending the response back to the client to get the body from the server that I proxy and analyze it.
But I can't find event or attribute, where I can get this data. I tried:
proxy.on('end', function (data) {
console.log('end');
});
But I can't figure our how to get the mime body from it.
If all you want to do is examine the response (read-only) , you could use :
proxy.on('proxyRes', function(proxyRes, req, res){
proxyRes.on('data' , function(dataBuffer){
var data = dataBuffer.toString('utf8');
console.log("This is the data from target server : "+ data);
});
});
But , note that the 'proxyRes' event is emitted after the response is sent.
Reference in :
https://github.com/nodejitsu/node-http-proxy/issues/382
I found answer:
I rewrite response function for body -
var http = require('http'),
httpProxy = require('http-proxy'),
io = require("socket.io").listen(5555);
var proxy = new httpProxy.RoutingProxy();
http.createServer(function (req, res) {
console.log(req.url);
res.oldWrite = res.write;
res.write = function(data) {
/* add logic for your data here */
console.log(data.toString('UTF8'));
res.oldWrite(data);
}
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 1338
});
}).listen(9000);
This code will console all url request and response form this endpoint.
Based on the answer of #Psycho, following code can be used to modify headers as well
var server = http.createServer(function(req, res) {
res.oldWrite = res.write;
res.write = function(data) {
console.log(data.toString('UTF8'));
res.oldWrite(data);
}
res.oldSetHeader = res.setHeader
res.setHeader = function(k,v) {
console.log(k,v)
res.oldSetHeader(k,v)
}
proxy.web(req, res, { target: proxyPool[key]}); })