среда, 25 апреля 2018 г.

Node.js - Proxy Server

Файл index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Proxy Server</title>
</head>
<body>
    <form method="GET" action="proxy">
        <input name="proxyreq" type="text" value="http://127.0.0.2:80/" />
        <input type="submit" value="Показать" />
    </form>
</body>
</html>

Файл proxy.js (загружает также картинку favicon.png)

var http = require('http')
    , url = require('url')
    , querystring = require('querystring')
    , path = require('path')
    , fs = require('fs')
    , replaceHTML = require('./replaceHTML')
    , replaceCSS = require('./replaceCSS')
    , replaceJavaScript = require('./replaceJavaScript');

var proxyHost = '127.0.0.1'
    , proxyPort = 80;

var proxyServer = http.createServer();

proxyServer.on('request', function (proxyRequest, proxyResponse) {
    var reqUrl = url.parse(proxyRequest.url)
        , pathname = reqUrl.pathname;
    if (pathname === '/' || pathname === 'index.html') {
        fs.readFile(path.resolve(path.join(__dirname, 'index.html')), function (error, data) {
            if (error) {throw error;}
            proxyResponse.writeHead(200, 'OK', {'Content-Type': 'text/html'});
            proxyResponse.write(data);
            proxyResponse.end();
        });
    } else if (pathname === '/favicon.ico') {
        fs.readFile('./favicon.png', function (error, data) {
            if (error) {throw error;}
            response.writeHead(200, 'OK', {'Content-Type': 'image/png', 'Content-Length': Buffer.byteLength(data)});
            response.write(data);
            response.end();
        });
    } else if (pathname.indexOf('/proxyform/') === 0) {
        var proxyreq = pathname.slice('/proxyform/'.length);
        proxyreq = querystring.unescape(proxyreq);
        proxyreq = path.posix.normalize(proxyreq);
        proxyreq = proxyreq.replace('http:/', 'http://');
        var proxyUrl = url.parse(proxyreq);
        var requestOptions = {
              method: proxyRequest.method
            , protocol: proxyUrl.protocol
            , hostname: proxyUrl.hostname
            , port: proxyUrl.port
            , path: proxyUrl.pathname + (proxyRequest.method === 'GET' ? url.parse(proxyRequest.url).search : '')
            , headers: proxyRequest.headers
        };
        var requestToOutterServer = http.request(requestOptions);
        requestToOutterServer.on('response', function (outterServerResponse) {
            var outterServerResponseData = [];
            outterServerResponse.on('data', function (chunk) {outterServerResponseData.push(chunk);});
                outterServerResponse.on('end', function () {
                    if (outterServerResponse.headers['content-type'] === 'text/html') {
                        outterServerResponseData = replaceHTML(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else if (outterServerResponse.headers['content-type'] === 'text/css') {
                        outterServerResponseData = replaceCSS(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                            , path.parse(proxyUrl.pathname).dir
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else if (outterServerResponse.headers['content-type'] === 'text/javascript') {
                        outterServerResponseData = replaceJavaScript(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else {
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        outterServerResponseData.forEach(function (chunk) {
                            proxyResponse.write(chunk);
                        });
                        proxyResponse.end();
                    }
                });
        });
        requestToOutterServer.on('error', function (error) {
            console.log(requestOptions);
            /*
            console.error(error);
            proxyResponse.writeHead(500, 'Internal Server Error', {'Content-Type': 'text/html'});
            proxyResponse.write('500 Internal Server Error');
            proxyResponse.end();
            */
            throw error;
        });
        if (proxyRequest.method === 'GET') {
            requestToOutterServer.end();
        } else if (proxyRequest.method === 'POST') {
            proxyRequest.on('data', function (chunk) {requestToOutterServer.write(chunk);});
            proxyRequest.on('end', function () {requestToOutterServer.end();});
        }
    } else if (pathname.indexOf('/proxy') === 0) {
        if (reqUrl.search) {
            var searchData = reqUrl.search.slice(1);
            searchData = querystring.parse(searchData);
            var proxyreq = searchData.proxyreq;
            proxyreq = querystring.unescape(proxyreq);
            proxyreq = path.posix.normalize(proxyreq);
            proxyreq = proxyreq.replace('http:/', 'http://');
            var proxyUrl = url.parse(proxyreq);
            delete searchData.proxyreq;
            var requestOptions = {
                  method: proxyRequest.method
                , protocol: proxyUrl.protocol
                , hostname: proxyUrl.hostname
                , port: proxyUrl.port
                , path: proxyUrl.pathname + (proxyRequest.method === 'GET' ? querystring.stringify(searchData) : '')
                , headers: proxyRequest.headers
            };
            var requestToOutterServer = http.request(requestOptions);
            requestToOutterServer.on('response', function (outterServerResponse) {
                var outterServerResponseData = [];
                outterServerResponse.on('data', function (chunk) {outterServerResponseData.push(chunk);});
                outterServerResponse.on('end', function () {
                    if (outterServerResponse.headers['content-type'] === 'text/html') {
                        outterServerResponseData = replaceHTML(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else if (outterServerResponse.headers['content-type'] === 'text/css') {
                        outterServerResponseData = replaceCSS(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                            , path.parse(proxyUrl.pathname).dir
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else if (outterServerResponse.headers['content-type'] === 'text/javascript') {
                        outterServerResponseData = replaceJavaScript(
                              outterServerResponseData.join('')
                            , 'http://' + proxyHost + ':' + proxyPort + '/proxy/?proxyreq='
                            , proxyUrl.protocol + '//' + proxyUrl.hostname + ':' + proxyUrl.port
                        );
                        outterServerResponse.headers['content-length'] = Buffer.byteLength(outterServerResponseData);
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        proxyResponse.end(outterServerResponseData);
                    } else {
                        proxyResponse.writeHead(outterServerResponse.statusCode, outterServerResponse.statusMessage, outterServerResponse.headers);
                        outterServerResponseData.forEach(function (chunk) {
                            proxyResponse.write(chunk);
                        });
                        proxyResponse.end();
                    }
                });
            });
            requestToOutterServer.on('error', function (error) {
                console.log(requestOptions);
                /*
                console.error(error);
                proxyResponse.writeHead(500, 'Internal Server Error', {'Content-Type': 'text/html'});
                proxyResponse.write('500 Internal Server Error');
                proxyResponse.end();
                */
                throw error;
            });
            if (proxyRequest.method === 'GET') {
                requestToOutterServer.end();
            } else if (proxyRequest.method === 'POST') {
                proxyRequest.on('data', function (chunk) {requestToOutterServer.write(chunk);});
                proxyRequest.on('end', function () {requestToOutterServer.end();});
            }
        } else {
            proxyResponse.writeHead(200, 'OK', {'Content-Type': 'text/html'});
            proxyResponse.write('<p>Адрес не задан.</p>');
            proxyResponse.write('<a href="/">Вернуться на страницу ввода адреса.</a>');
            proxyResponse.end();
        }
    } else {
        proxyResponse.writeHead(404, 'Not Found', {'Content-Type': 'text/html'});
        proxyResponse.write('404 Not Found');
        proxyResponse.end();
    }
});

proxyServer.on('error', function (error) {throw error;});

proxyServer.listen(proxyPort, proxyHost, function () {
    console.log('Proxy server started at ' + proxyHost + ':' + proxyPort);
});

Файл replaceHTML.js

var querystring = require('querystring');

function replacePath (htmlString, regexp, proxyServerAddress, outterServerAddress) {
    return htmlString.replace(regexp, function (match, $1, $2, $3) {
        var newPath;
        if ($2.indexOf('http') === 0) {
             newPath = proxyServerAddress + querystring.escape($2);
        } else if ($2.indexOf('/') === 0) {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + $2);
        } else {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + '/' + $2);
        }
        return $1 + newPath + $3;
    });
}

function replaceHref (htmlString, proxyServerAddress, outterServerAddress) {
    return replacePath(htmlString, /(href=")([^"]+)(")/gi, proxyServerAddress, outterServerAddress)
}

/*
console.log(replaceHref([
      '<link href="style1.css" rel="stylesheet" type="text/css" />'
    , '<link href="/style2.css" rel="stylesheet" type="text/css" />'
    , '<link href="http://www.google.com/style3.css" rel="stylesheet" type="text/css" />'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceSrc (htmlString, proxyServerAddress, outterServerAddress) {
    return replacePath(htmlString, /(src=")([^"]+)(")/gi, proxyServerAddress, outterServerAddress)
}

/*
console.log(replaceSrc([
      '<img src="image1.png" />'
    , '<img src="/image2.png" />'
    , '<img src="http://www.google.com/image3.png" />'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceAction (htmlString, proxyServerAddress, outterServerAddress) {
    proxyServerAddress = proxyServerAddress.replace('/proxy/?proxyreq=', '/proxyform/');
    return htmlString.replace(/(action=")([^"]+)(")/gi, function (match, $1, $2, $3) {
        var newPath;
        if ($2.indexOf('http') === 0) {
             newPath = proxyServerAddress + querystring.escape($2);
        } else if ($2.indexOf('/') === 0) {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + $2);
        } else {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + '/' + $2);
        }
        return $1 + newPath + $3;
    });
    // return replacePath(htmlString, /(action=")([^"]+)(")/gi, proxyServerAddress, outterServerAddress)
}

/*
console.log(replaceAction([
      '<form action="submit" method="GET"><input name= "a" type="texy" value="1" /><input type="submit" value="Submit 1" /></form>'
    , '<form action="/submit" method="GET"><input name= "b" type="texy" value="2" /><input type="submit" value="Submit 2" /></form>'
    , '<form action="http://127.0.0.2:80/submit" method="GET"><input name= "c" type="texy" value="3" /><input type="submit" value="Submit 3" /></form>'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceData (htmlString, proxyServerAddress, outterServerAddress) {
    return replacePath(htmlString, /(data=")([^"]+)(")/gi, proxyServerAddress, outterServerAddress)
}

/*
console.log(replaceData([
      '<object data="video.mp4" />'
    , '<object data="/video.mp4" />'
    , '<object data="http://www.google.com/video.mp4" />'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceXMLHttpRequest (htmlString, proxyServerAddress, outterServerAddress) {
    return htmlString.replace(/<head>/gi, function () {
        return '<head>' + [
                    '<script type="text/javascript">'
                        + ';(function (open) {'
                            + 'window.XMLHttpRequest.prototype.open = function (method, url, async) {'
                                + 'var newUrl;'
                                + 'if (url.indexOf("http") === 0) {'
                                     + 'newUrl = "' + proxyServerAddress + '" + window.encodeURIComponent(url);'
                                + '} else if (url.indexOf("/") === 0) {'
                                     + 'newUrl = "' + proxyServerAddress + '" + window.encodeURIComponent("' + outterServerAddress + '" + url);'
                                + '} else {'
                                     + 'newUrl = "' + proxyServerAddress + '" + window.encodeURIComponent("' + outterServerAddress + '" + "/" + url);'
                                + '}'
                                + 'open.call(this, method, newUrl, async);'
                            + '};'
                        + '})(window.XMLHttpRequest.prototype.open);'
                    + '</script>'
                ].join('');
    });
}

/*
console.log(replaceXMLHttpRequest([
    '<!DOCTYPE html>'
    + '<html>'
    + '<head>'
    + '<link href="favicon.png" rel="icon" type="image/png" />'
    + '<link href="static/css/style1.css" rel="stylesheet" type="text/css" />'
    + '<link href="/static/css/style2.css" rel="stylesheet" type="text/css" />'
    + '<link href="http://127.0.0.2:80/static/css/style3.css" rel="stylesheet" type="text/css" />'
    + '<script src="static/js/script1.js"></script>'
    + '<script src="/static/js/script2.js"></script>'
    + '<script src="http://127.0.0.2:80/static/js/script3.js"></script>'
    + '<meta charset="utf-8" />'
    + '<title>Test page</title>'
    + '</head>'
].join('\n'), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

module.exports = function replaceHTML (htmlString, proxyServerAddress, outterServerAddress) {
    htmlString = replaceHref(htmlString, proxyServerAddress, outterServerAddress);
    htmlString = replaceSrc(htmlString, proxyServerAddress, outterServerAddress);
    htmlString = replaceAction(htmlString, proxyServerAddress, outterServerAddress);
    htmlString = replaceData(htmlString, proxyServerAddress, outterServerAddress);
    htmlString = replaceXMLHttpRequest(htmlString, proxyServerAddress, outterServerAddress);
    return htmlString;
};

Файл replaceCSS.js

function replaceUrl (cssString, proxyServerAddress, outterServerAddress, rootPath) {
    return cssString.replace(/url\([^\)]+\)/gi, function (match) {
        match = match.replace(/url\(/gi, '');
        match = match.replace(/\)/gi, '');
        match = match.replace(/"/gi, '');
        match = match.replace(/'/gi, '');
        match = match.replace(/^\s+/, '');
        match = match.replace(/\s+$/, '');
        if (!(match.indexOf('/') === 0 || match.indexOf('http') === 0)) {
            match = joinPaths(rootPath, match);
        }
        var newPath;
        if (match.indexOf('http') === 0) {
            newPath = proxyServerAddress + match;
        } else if (match.indexOf('/') === 0) {
             newPath = proxyServerAddress + outterServerAddress + match;
        } else {
             newPath = proxyServerAddress + outterServerAddress + '/' + match;
        }
        return 'url("' + newPath + '")';
    });
}

function joinPaths (root, path) {
    if (path.indexOf('../') === 0) {
        if (root.indexOf('/') === root.lastIndexOf('/')) {
            if (root[root.length - 1] === '/') {root = root.slice(0, root.length - 1);}
            if (path[0] === '/') {path = path.slice(1);}
            return root + '/' + path;
        } else {
            path = path.slice(3);
            root = root.slice(0, root.lastIndexOf('/'));
            return joinPaths(root, path);
        }
    } else {
        if (root[root.length - 1] === '/') {root = root.slice(0, root.length - 1);}
        if (path[0] === '/') {path = path.slice(1);}
        return root + '/' + path;
    }
}

/*
console.log(replaceUrl([
      'body {background: url(image.png) repeat-x repeat-y;}'
    , 'div {background: url(/image.png) repeat-x repeat-y;}'
    , 'p {background: url(http://www.google.com/image.png) repeat-x repeat-y;}'

    , 'body {background: url("image.png") repeat-x repeat-y;}'
    , 'div {background: url("/image.png") repeat-x repeat-y;}'
    , 'p {background: url("http://www.google.com/image.png") repeat-x repeat-y;}'

    , '@import url(\'style2.css\');'
    , '@import url("/style2.css");'
    , '@import url("http://www.google.com/style2.css");'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

module.exports = function replaceCSS (cssString, proxyServerAddress, outterServerAddress, rootPath) {
    cssString = replaceUrl(cssString, proxyServerAddress, outterServerAddress, rootPath);
    return cssString;
};

Файл replaceJavaScript.js

var querystring = require('querystring');

function replacePath (javascriptString, regexp, proxyServerAddress, outterServerAddress) {
    return javascriptString.replace(regexp, function (match, $1, $2, $3, $4) {
        var newPath;
        if ($3.indexOf('http') === 0) {
             newPath = proxyServerAddress + querystring.escape($3);
        } else if ($3.indexOf('/') === 0) {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + $3);
        } else {
             newPath = proxyServerAddress + querystring.escape(outterServerAddress + '/' + $3);
        }
        return $1 + $2 + newPath + $4;
    });
}

function replaceVarPath (javascriptString, regexp, proxyServerAddress, outterServerAddress) {
    return javascriptString.replace(regexp, function (match, $1, $2) {
        return $1 + '"' + proxyServerAddress + querystring.escape(outterServerAddress) + '/" + ' + $2;
    });
}

function replaceVarSrc (javascriptString, proxyServerAddress, outterServerAddress) {
    return replaceVarPath(javascriptString, /(\.\s*src\s*=\s*)([a-zA-Z$_]+)/gi, proxyServerAddress, outterServerAddress);
}

/*
console.log(replaceVarSrc([
      'img . src = someVar'
    , 'img.src = someVar + "/path"'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceSrc (javascriptString, proxyServerAddress, outterServerAddress) {
    return replacePath(javascriptString, /(\.\s*src\s*=\s*)('|")([^'"]+)('|")/gi, proxyServerAddress, outterServerAddress);
}

/*
console.log(replaceSrc([
      'img . src = \'path\''
    , 'img.src = "/path"'
    , 'img.src="http://www.google.com/path"'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceHref (javascriptString, proxyServerAddress, outterServerAddress) {
    return replacePath(javascriptString, /(\.\s*href\s*=\s*)('|")([^'"]+)('|")/gi, proxyServerAddress, outterServerAddress);
}

/*
console.log(replaceHref([
      'location . href = \'path\''
    , 'location.href = "/path"'
    , 'location.href="http://www.google.com/path"'
    , 'a . href = \'path\''
    , 'a.href = "/path"'
    , 'a.href="http://www.google.com/path"'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

function replaceVarHref (javascriptString, proxyServerAddress, outterServerAddress) {
    return replaceVarPath(javascriptString, /(\.\s*href\s*=\s*)([a-zA-Z$_]+)/gi, proxyServerAddress, outterServerAddress);
}

/*
console.log(replaceVarSrc([
      'img . href = someVar'
    , 'img.href = someVar + "/path"'
].join(' | '), 'http://127.0.0.1:80/?proxyreq=', 'http://127.0.0.2:80'));
*/

module.exports = function replaceJavaScript (javascriptString, proxyServerAddress, outterServerAddress) {
    javascriptString = replaceSrc(javascriptString, proxyServerAddress, outterServerAddress);
    javascriptString = replaceVarSrc(javascriptString, proxyServerAddress, outterServerAddress);
    javascriptString = replaceHref(javascriptString, proxyServerAddress, outterServerAddress);
    javascriptString = replaceVarHref(javascriptString, proxyServerAddress, outterServerAddress);
    return javascriptString;
};

пятница, 6 апреля 2018 г.

Node.js Socket Client HTTP Request example

var net = require('net');

var clientSocket = new net.Socket();

clientSocket.on('connect', function () {
    console.log('Connected to Google.');
});

clientSocket.on('data', function (data) {
    console.log(data.toString());
});

clientSocket.on('end', function () {
    console.log('Google sent all data.');
});

clientSocket.on('close', function () {
    console.log('Connection closed.');
});

clientSocket.on('error', function (error) {
   throw error;
});

clientSocket.connect(80, 'google.com', function (error) {
    if (error) {throw error;}
});

clientSocket.write('GET /index.html HTTP/1.0'); // Передаем тип запроса GET или POST и какую страницу хотим загрузить - index.html
clientSocket.write('Accept-Encoding: gzip, compress');
// clientSocket.write('Host: 127.0.0.1');
// clientSocket.write('User-Agent: My programm');
// clientSocket.write('Accept-Language: en');
clientSocket.write(''); // Обязательно должна в конце отправлять пустая строка.
clientSocket.end();