Error: BAD
─────────────────────────────
at Object.<anonymous> (D:\Trace\lib.js:215:7)
─────────────────────────────
213 » }
214 » */
215 » throw new Error('BAD');
------------^
216 »
217 »
at Module.Module._compile [as _compile] (module.js:556:32)
at Object.Module._extensions..js [as .js] (module.js:565:10)
at Module.Module.load [as load] (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load [as _load] (module.js:424:3)
at Module.Module.require [as require] (module.js:483:17)
at require (internal/module.js:20:19)
────────────────────────────
at Object.<anonymous> (D:\Trace\test.js:9:9)
────────────────────────────
7 »
8 »
9 » var lib = require('./lib.js');
-------------^
10 »
11 »
at Module.Module._compile [as _compile] (module.js:556:32)
Код файла с ошибкой app.js
require('./stacktrace.js')();
require('fs').readFile('./test.js', function () {
throw new Error('BAD');
});
Код файла stacktrace.js
/*
─────────────────────
/path/to/your/code.js
─────────────────────
189 »
190 » function bar (x) {
191 » throw new Error('x: ' + x);
---------------^
192 » }
193 »
194 » foo(bar);
*/
var LINES_BEFORE = 2
, LINES_AFTER = 3
, MAX_COLUMNS = 80
, DEFAULT_INDENT = 4
, GUTTER_CONTENT = ' » '
, ENDASH_CHAR = '-'
, UP_ARROW_CHAR = '^'
, ELLIPSIS_CHAR = '…'
, EMDASH_CHAR = '─'
, ERR_FILE_NOT_EXIST = 'ENOENT'
, DEFAULT_LIBRARY_REGEX = /node_modules/
, OUTPUT_PREFIX = repeatCharacter(' ', DEFAULT_INDENT); // ==> repeatCharacter()
module.exports = function () {
Error.prepareStackTrace = function (error, frames) {
var lines = [];
try {
lines.push(error.toString());
} catch (error) {
try {
lines.push('<error: ' + error + '>');
} catch (e) {
lines.push('<error>');
}
}
frames.forEach(function (frame) {
var line;
try {
line = formatFrame(frame); // ==> formatFrame()
} catch (error) {
try {
line = '<error: ' + error + '>';
} catch (e) {
line = '<error>';
}
}
lines.push(line);
});
return lines.join('\n');
};
};
function formatFrame (frame) {
var line = ''
, underline = ''
, betterLine = ''
, addPrefix = true
, fileLocationAndContext = getFileLocationAndContextFrom(frame) // ==> getFileLocationAndContextFrom()
, fileLocation = fileLocationAndContext.fileLocation
, context = fileLocationAndContext.context
, functionName = frame.getFunctionName()
, methodName = frame.getMethodName()
, isConstructor = frame.isConstructor()
, isMethodCall = !(frame.isToplevel() || isConstructor);
if (isMethodCall) {
line += frame.getTypeName() + '.';
if (functionName) {
line += functionName;
if (methodName && (methodName != functionName)) {
line += ' [as ' + methodName + ']';
}
} else {
line += methodName || '<anonymous>';
}
} else if (isConstructor) {
line += 'new ' + (functionName || '<anonymous>');
} else if (functionName) {
line += functionName;
} else {
line += fileLocation;
addPrefix = false;
}
if (addPrefix) {
line += ' (' + fileLocation + ')';
}
line = 'at ' + line;
if (context) {
underline = OUTPUT_PREFIX + line.replace(/./g, EMDASH_CHAR);
betterLine = line.replace(/./g, EMDASH_CHAR) + '\n' + OUTPUT_PREFIX + [line, underline, context].join('\n');
return OUTPUT_PREFIX + betterLine + '\n';
} else {
return OUTPUT_PREFIX + line;
}
}
function getFileLocationAndContextFrom (frame) {
var fileLocation = ''
, context = null
, fileName
, lineNumber
, columnNumber;
if (frame.isNative()) {
fileLocation = 'native';
} else if (frame.isEval()) {
fileLocation = 'eval at ' + frame.getEvalOrigin();
} else {
fileName = frame.getFileName();
if (fileName) {
fileLocation += fileName;
lineNumber = frame.getLineNumber();
if (lineNumber != null) {
fileLocation += ':' + lineNumber;
columnNumber = frame.getColumnNumber();
if (columnNumber) {
fileLocation += ':' + columnNumber;
}
try {
if (shouldCollapseDirectory(fileName)) { // ==> shouldCollapseDirectory()
context = null;
} else {
context = formatContext(fileName, lineNumber, columnNumber); // ==> formatContext()
}
} catch(error) {
if (error.code === ERR_FILE_NOT_EXIST) {
context = null;
} else {
context = error;
}
}
}
}
}
if (!fileLocation) {
fileLocation = 'unknown source';
}
return {
fileLocation: fileLocation
, context: context
};
}
function shouldCollapseDirectory (fileName) {
return DEFAULT_LIBRARY_REGEX.test(fileName);
}
function formatContext (fileName, lineNumber, columnNumber) {
var code;
try {
code = require('fs').readFileSync(fileName).toString();
} catch(error) {
if (error.code === ERR_FILE_NOT_EXIST) {
throw error;
}
return OUTPUT_PREFIX + error.toString();
}
// Figure out the lines of context before and after
var lines = code.split('\n')
, preLines = lines.slice(lineNumber - LINES_BEFORE - 1, lineNumber)
, postLines = lines.slice(lineNumber, lineNumber + LINES_AFTER);
// Collect formatted versions of all the lines
var formattedLines = []
, maxLineNumber = lineNumber + LINES_AFTER
, currentLineNumber = lineNumber - LINES_BEFORE;
function renderLines (lines) {
while (lines.length) {
formattedLines.push(
formatCodeLine(currentLineNumber, lines.shift(), maxLineNumber) // ==> formatCodeLine()
);
currentLineNumber++;
}
}
renderLines(preLines);
formattedLines.push(formatCodeArrow(currentLineNumber - 1, columnNumber, maxLineNumber)); // ==> formatCodeArrow()
renderLines(postLines);
return OUTPUT_PREFIX + formattedLines.join('\n' + OUTPUT_PREFIX);
}
function formatCodeLine (lineNumber, line, maxLineNumber) {
var pad = maxLineNumber.toString().length - lineNumber.toString().length
, padding = '';
while (pad-- > 0) {
padding += ' ';
}
if (line.length > MAX_COLUMNS) {
line = line.slice(0, MAX_COLUMNS - 1) + ELLIPSIS_CHAR;
}
return padding + lineNumber + GUTTER_CONTENT + line;
}
function formatCodeArrow (lineNumber, columnNumber, maxLineNumber) {
var length = (GUTTER_CONTENT + maxLineNumber).length + columnNumber;
return repeatCharacter(ENDASH_CHAR, length).slice(0, length - 1) + UP_ARROW_CHAR; // ==> repeatCharacter()
}
function repeatCharacter (character, length) {
return new Array(length).join(character) + character;
}
вторник, 27 сентября 2016 г.
понедельник, 19 сентября 2016 г.
Создание TCP, HTTP и HTTPS прокси на Node.js
HTTP и HTTPS прокси на Node.js
ejz.ru/63/node-js-http-https-proxy
С HTTP все очень просто.
Файл http-proxy.js
var http = require('http')
, url = require('url');
var server = http.createServer(function(request, response) {
console.log(request.url);
var fromURL = url.parse(request.url);
var options = {
port: fromURL.port
, hostname: fromURL.hostname
, method: request.method
, path: fromURL.path
, headers: request.headers
};
var proxyRequest = http.request(options);
proxyRequest.on('response', function (proxyResponse) {
response.writeHead(proxyResponse.statusCode, proxyResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
proxyResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
});
proxyResponse.on('end', function() {
response.end();
});
proxyResponse.on('error', function (err) {
console.log('Error with client ', err);
});
});
request.on('data', function (chunk) {
proxyRequest.write(chunk, 'binary')
});
request.on('end', function () {
proxyRequest.end()
});
request.on('error', function (err) {
console.log('Problem with request ', err);
});
});
server.listen(8080);
Тестируем...
node http-proxy.js
http://google.com
http_proxy='127.0.0.1:8080' wget -q -O - 'http://google.com/' | grep -i meta
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="description" content="Google" />
Отлично! С HTTP запросами разобрались.
С HTTPS протоколом прокси работает через метод CONNECT.
Это означает, что после CONNECT метода прокси соединяется с запрошенным хостом и начинает туннелировать через себя весь трафик между клиентом и хостом (в том числе и обмен сертификатами).
На уровне кода это выглядит как обработка события 'connect' с последующим созданием туннелирующего сокета.
Файл https-proxy.js
var http = require('http')
, net = require('net')
, url = require('url');
var server = http.createServer(function (request, response) {
console.log(request.url);
var fromURL = url.parse(request.url);
var options = {
port: fromURL.port
, hostname: fromURL.hostname
, method: request.method
, path: fromURL.path
, headers: request.headers
};
var proxyRequest = http.request(options);
proxyRequest.on('response', function (proxyResponse) {
response.writeHead(proxyResponse.statusCode, proxyResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
proxyResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
});
proxyResponse.on('end', function () {
response.end();
});
});
request.on('data', function (chunk) {
proxyRequest.write(chunk, 'binary');
});
request.on('end', function () {
proxyRequest.end();
});
});
server.on('connect', function (request, socketRequest, head) {
console.log(request.url);
var fromURL = url.parse('http://' + request.url);
var socket = net.connect(fromURL.port, fromURL.hostname, function() {
// Сказать клиенту, что соединение установлено
socket.write(head);
socketRequest.write("HTTP/" + request.httpVersion + " 200 Connection established\r\n\r\n");
})
// Туннелирование к хосту
socket.on('data', function (chunk) {socketRequest.write(chunk);});
socket.on('end', function () {socketRequest.end();});
socket.on('error', function () {
// Сказать клиенту, что произошла ошибка
socketRequest.write("HTTP/" + request.httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
})
// Туннелирование к клиенту
socketRequest.on('data', function (chunk) {socket.write(chunk);});
socketRequest.on('end', function () {socket.end();});
socketRequest.on('error', function () {socket.end();});
})
server.listen(8080);
Проверим отработку...
node https-proxy.js
http://ipdb.at/
ipdb.at:443
http_proxy='127.0.0.1:8080' https_proxy='127.0.0.1:8080' wget -q -O - 'http://ipdb.at/' | grep -i 'your ip'
<div id="status-bar">Your IP is <span>10.20.0.1</span></div>
Первым ответом был 302 редирект.
Далее, обратите внимание, при CONNECT методе прокси сервер не знает полностью запрашиваемый урл.
Урл уже передается по шифрованному туннелю.
Давайте усложним задачу.
Представим, что наш прокси-сервис должен перенаправлять запросы на другой прокси (например, для балансировки трафика).
Трафик идет раздельно: HTTP идет по HTTP, HTTPS идет CONNECT'ом.
На пользовательский request вещается 'connect' обработчик.
Таким образом можно при успешном соединении на шлюз можно трафик из сокета шлюза кидать на сокет запроса.
Файл proxy-to-proxy.js
var assert = require('assert')
, gateway = 'proxy://login:passwd@10.20.30.40:3128/' // Прокси для редиректа
, http = require('http')
, net = require('net')
, url = require('url');
if (process.env.gateway) {gateway = process.env.gateway;}
var server = http.createServer(function (request, response) {
console.log(request.url);
var fromGateway = url.parse(gateway);
var options = {
port: parseInt(fromGateway.port)
, hostname: fromGateway.hostname
, method: request.method
, path: request.url
, headers: request.headers || {}
};
if (fromGateway.auth) {
options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(fromGateway.auth).toString('base64');
}
var gatewayRequest = http.request(options);
gatewayRequest.on('error', function (err) {
console.log('[error] ' + err);
response.end();
});
gatewayRequest.on('response', function (gatewayResponse) {
if (gatewayResponse.statusCode === 407) {
console.log('[error] AUTH REQUIRED');
process.exit();
}
response.writeHead(gatewayResponse.statusCode, gatewayResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
gatewayResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
})
gatewayResponse.on('end', function () {
response.end();
});
});
request.on('data', function (chunk) {
gatewayRequest.write(chunk, 'binary');
})
request.on('end', function () {
gatewayRequest.end();
});
gatewayRequest.end();
});
server.on('connect', function (request, socketRequest, head) {
console.log(request.url);
var fromURL = url.parse('http://' + request.url)
, fromGateway = url.parse(gateway);
var options = {
port: fromGateway.port
, hostname: fromGateway.hostname
, method: 'CONNECT'
, path: fromURL.hostname + ':' + (fromURL.port || 80)
, headers: request.headers || {}
}
if (fromGateway.auth) {
options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(fromGateway.auth).toString('base64');
}
var gatewayRequest = http.request(options);
gatewayRequest.on('error', function (err) {
console.log('[error] ' + err);
process.exit();
});
gatewayRequest.on('connect', function (res, socket, head) {
assert.equal(res.statusCode, 200);
assert.equal(head.length, 0);
socketRequest.write("HTTP/" + request.httpVersion + " 200 Connection established\r\n\r\n"); // имеет смысл перенести вниз после всех слушателей событий
// Туннелирование к хосту
socket.on('data', function (chunk) {socketRequest.write(chunk, 'binary');});
socket.on('end', function () {socketRequest.end();});
socket.on('error', function () {
// Сказать клиенту, что произошла ошибка
socketRequest.write("HTTP/" + request.httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
})
// Туннелирование к клиенту
socketRequest.on('data', function (chunk) {socket.write(chunk, 'binary');});
socketRequest.on('end', function () {socket.end();});
socketRequest.on('error', function () {socket.end();});
}).end();
});
server.listen(8080, '127.0.0.1');
Проверка работоспособности...
node proxy-to-proxy.js
http://ipdb.at/
ipdb.at:443
http_proxy='127.0.0.1:8080' https_proxy='127.0.0.1:8080' wget -q -O - 'http://ipdb.at/' | grep -i 'your ip'
<div id="status-bar">Your IP is <span>10.20.30.40</span></div>
Теперь создадим TCP proxy server: client —> proxy -> remote
Файл tcp-proxy.js
var net = require('net');
var REMOTE_ADDR = "192.168.1.25"
, REMOTE_PORT = 6512;
var server = net.createServer(function (socket) {
socket.on('data', function (message) {
console.log(' ** START **');
console.log('<< From client to proxy ', message.toString());
var serviceSocket = new net.Socket();
serviceSocket.connect(REMOTE_PORT, REMOTE_ADDR, function () {
console.log('>> From proxy to remote', message.toString());
serviceSocket.write(message);
});
serviceSocket.on('data', function (data) {
console.log('<< From remote to proxy', data.toString());
socket.write(data);
console.log('>> From proxy to client', data.toString());
});
});
});
server.listen(8080);
console.log('TCP server accepting connection on port: 8080');
Теперь пример прокси-сервера для Express.js, который изменяет ответ перед отправкой в браузер
Файл express-proxy.js
var http = require('http')
, express = require('express');
var app = express();
app.get('/*', function (clientRequest, clientResponse) {
var options = {
hostname: 'google.com'
, port: 80
, path: clientRequest.url
, method: 'GET'
};
var googleRequest = http.request(options, function (googleResponse) {
var body = '';
if (String(googleResponse.headers['content-type']).indexOf('text/html') !== -1) {
googleResponse.on('data', function (chunk) {
body += chunk;
});
googleResponse.on('end', function () {
// Внесение изменений в HTML-код полученного файла перед его отправкой в браузер
body = body.replace(/google.com/gi, host + ':' + port);
body = body.replace(/<\/body>/, '<script src="http://localhost:3000/new-script.js" type="text/javascript"></script></body>');
clientResponse.writeHead(googleResponse.statusCode, googleResponse.headers);
clientResponse.end(body);
});
} else {
googleResponse.pipe(clientResponse, {end: true});
}
});
googleRequest.end();
});
ejz.ru/63/node-js-http-https-proxy
С HTTP все очень просто.
Файл http-proxy.js
var http = require('http')
, url = require('url');
var server = http.createServer(function(request, response) {
console.log(request.url);
var fromURL = url.parse(request.url);
var options = {
port: fromURL.port
, hostname: fromURL.hostname
, method: request.method
, path: fromURL.path
, headers: request.headers
};
var proxyRequest = http.request(options);
proxyRequest.on('response', function (proxyResponse) {
response.writeHead(proxyResponse.statusCode, proxyResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
proxyResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
});
proxyResponse.on('end', function() {
response.end();
});
proxyResponse.on('error', function (err) {
console.log('Error with client ', err);
});
});
request.on('data', function (chunk) {
proxyRequest.write(chunk, 'binary')
});
request.on('end', function () {
proxyRequest.end()
});
request.on('error', function (err) {
console.log('Problem with request ', err);
});
});
server.listen(8080);
Тестируем...
node http-proxy.js
http://google.com
http_proxy='127.0.0.1:8080' wget -q -O - 'http://google.com/' | grep -i meta
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="description" content="Google" />
Отлично! С HTTP запросами разобрались.
С HTTPS протоколом прокси работает через метод CONNECT.
Это означает, что после CONNECT метода прокси соединяется с запрошенным хостом и начинает туннелировать через себя весь трафик между клиентом и хостом (в том числе и обмен сертификатами).
На уровне кода это выглядит как обработка события 'connect' с последующим созданием туннелирующего сокета.
Файл https-proxy.js
var http = require('http')
, net = require('net')
, url = require('url');
var server = http.createServer(function (request, response) {
console.log(request.url);
var fromURL = url.parse(request.url);
var options = {
port: fromURL.port
, hostname: fromURL.hostname
, method: request.method
, path: fromURL.path
, headers: request.headers
};
var proxyRequest = http.request(options);
proxyRequest.on('response', function (proxyResponse) {
response.writeHead(proxyResponse.statusCode, proxyResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
proxyResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
});
proxyResponse.on('end', function () {
response.end();
});
});
request.on('data', function (chunk) {
proxyRequest.write(chunk, 'binary');
});
request.on('end', function () {
proxyRequest.end();
});
});
server.on('connect', function (request, socketRequest, head) {
console.log(request.url);
var fromURL = url.parse('http://' + request.url);
var socket = net.connect(fromURL.port, fromURL.hostname, function() {
// Сказать клиенту, что соединение установлено
socket.write(head);
socketRequest.write("HTTP/" + request.httpVersion + " 200 Connection established\r\n\r\n");
})
// Туннелирование к хосту
socket.on('data', function (chunk) {socketRequest.write(chunk);});
socket.on('end', function () {socketRequest.end();});
socket.on('error', function () {
// Сказать клиенту, что произошла ошибка
socketRequest.write("HTTP/" + request.httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
})
// Туннелирование к клиенту
socketRequest.on('data', function (chunk) {socket.write(chunk);});
socketRequest.on('end', function () {socket.end();});
socketRequest.on('error', function () {socket.end();});
})
server.listen(8080);
Проверим отработку...
node https-proxy.js
http://ipdb.at/
ipdb.at:443
http_proxy='127.0.0.1:8080' https_proxy='127.0.0.1:8080' wget -q -O - 'http://ipdb.at/' | grep -i 'your ip'
<div id="status-bar">Your IP is <span>10.20.0.1</span></div>
Первым ответом был 302 редирект.
Далее, обратите внимание, при CONNECT методе прокси сервер не знает полностью запрашиваемый урл.
Урл уже передается по шифрованному туннелю.
Давайте усложним задачу.
Представим, что наш прокси-сервис должен перенаправлять запросы на другой прокси (например, для балансировки трафика).
Трафик идет раздельно: HTTP идет по HTTP, HTTPS идет CONNECT'ом.
На пользовательский request вещается 'connect' обработчик.
Таким образом можно при успешном соединении на шлюз можно трафик из сокета шлюза кидать на сокет запроса.
Файл proxy-to-proxy.js
var assert = require('assert')
, gateway = 'proxy://login:passwd@10.20.30.40:3128/' // Прокси для редиректа
, http = require('http')
, net = require('net')
, url = require('url');
if (process.env.gateway) {gateway = process.env.gateway;}
var server = http.createServer(function (request, response) {
console.log(request.url);
var fromGateway = url.parse(gateway);
var options = {
port: parseInt(fromGateway.port)
, hostname: fromGateway.hostname
, method: request.method
, path: request.url
, headers: request.headers || {}
};
if (fromGateway.auth) {
options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(fromGateway.auth).toString('base64');
}
var gatewayRequest = http.request(options);
gatewayRequest.on('error', function (err) {
console.log('[error] ' + err);
response.end();
});
gatewayRequest.on('response', function (gatewayResponse) {
if (gatewayResponse.statusCode === 407) {
console.log('[error] AUTH REQUIRED');
process.exit();
}
response.writeHead(gatewayResponse.statusCode, gatewayResponse.headers); // имеет смысл перенести вниз после всех слушателей событий
gatewayResponse.on('data', function (chunk) {
response.write(chunk, 'binary');
})
gatewayResponse.on('end', function () {
response.end();
});
});
request.on('data', function (chunk) {
gatewayRequest.write(chunk, 'binary');
})
request.on('end', function () {
gatewayRequest.end();
});
gatewayRequest.end();
});
server.on('connect', function (request, socketRequest, head) {
console.log(request.url);
var fromURL = url.parse('http://' + request.url)
, fromGateway = url.parse(gateway);
var options = {
port: fromGateway.port
, hostname: fromGateway.hostname
, method: 'CONNECT'
, path: fromURL.hostname + ':' + (fromURL.port || 80)
, headers: request.headers || {}
}
if (fromGateway.auth) {
options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(fromGateway.auth).toString('base64');
}
var gatewayRequest = http.request(options);
gatewayRequest.on('error', function (err) {
console.log('[error] ' + err);
process.exit();
});
gatewayRequest.on('connect', function (res, socket, head) {
assert.equal(res.statusCode, 200);
assert.equal(head.length, 0);
socketRequest.write("HTTP/" + request.httpVersion + " 200 Connection established\r\n\r\n"); // имеет смысл перенести вниз после всех слушателей событий
// Туннелирование к хосту
socket.on('data', function (chunk) {socketRequest.write(chunk, 'binary');});
socket.on('end', function () {socketRequest.end();});
socket.on('error', function () {
// Сказать клиенту, что произошла ошибка
socketRequest.write("HTTP/" + request.httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
})
// Туннелирование к клиенту
socketRequest.on('data', function (chunk) {socket.write(chunk, 'binary');});
socketRequest.on('end', function () {socket.end();});
socketRequest.on('error', function () {socket.end();});
}).end();
});
server.listen(8080, '127.0.0.1');
Проверка работоспособности...
node proxy-to-proxy.js
http://ipdb.at/
ipdb.at:443
http_proxy='127.0.0.1:8080' https_proxy='127.0.0.1:8080' wget -q -O - 'http://ipdb.at/' | grep -i 'your ip'
<div id="status-bar">Your IP is <span>10.20.30.40</span></div>
Теперь создадим TCP proxy server: client —> proxy -> remote
Файл tcp-proxy.js
var net = require('net');
var REMOTE_ADDR = "192.168.1.25"
, REMOTE_PORT = 6512;
var server = net.createServer(function (socket) {
socket.on('data', function (message) {
console.log(' ** START **');
console.log('<< From client to proxy ', message.toString());
var serviceSocket = new net.Socket();
serviceSocket.connect(REMOTE_PORT, REMOTE_ADDR, function () {
console.log('>> From proxy to remote', message.toString());
serviceSocket.write(message);
});
serviceSocket.on('data', function (data) {
console.log('<< From remote to proxy', data.toString());
socket.write(data);
console.log('>> From proxy to client', data.toString());
});
});
});
server.listen(8080);
console.log('TCP server accepting connection on port: 8080');
Теперь пример прокси-сервера для Express.js, который изменяет ответ перед отправкой в браузер
Файл express-proxy.js
var http = require('http')
, express = require('express');
var app = express();
app.get('/*', function (clientRequest, clientResponse) {
var options = {
hostname: 'google.com'
, port: 80
, path: clientRequest.url
, method: 'GET'
};
var googleRequest = http.request(options, function (googleResponse) {
var body = '';
if (String(googleResponse.headers['content-type']).indexOf('text/html') !== -1) {
googleResponse.on('data', function (chunk) {
body += chunk;
});
googleResponse.on('end', function () {
// Внесение изменений в HTML-код полученного файла перед его отправкой в браузер
body = body.replace(/google.com/gi, host + ':' + port);
body = body.replace(/<\/body>/, '<script src="http://localhost:3000/new-script.js" type="text/javascript"></script></body>');
clientResponse.writeHead(googleResponse.statusCode, googleResponse.headers);
clientResponse.end(body);
});
} else {
googleResponse.pipe(clientResponse, {end: true});
}
});
googleRequest.end();
});
понедельник, 29 августа 2016 г.
React Redux Router My CheatSheet
// React Redux Router
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import {Router, Route, hashHistory} from 'react-router';
import {syncHistoryWithStore, routerReducer, routerMiddleware, push} from 'react-router-redux';
function reducer (state = {name: 'Boris'}, action) {
switch (action.type) {
case 'CHANGE_NAME': return {name: action.name};
default: return state;
}
};
const reducers = combineReducers({
user: reducer
, routing: routerReducer
});
const initialState = {user: {name: 'Nick'}};
const store = createStore(reducers, applyMiddleware(routerMiddleware(hashHistory)));
const history = syncHistoryWithStore(hashHistory, store);
class App extends Component {
render () {
return (
<div>Привет {store.getState().user.name} <a onClick={this.handleClick.bind(this)}>Измени имя</a> {this.props.children}</div>
);
}
handleClick (event) {
event.preventDefault();
store.dispatch({type: 'CHANGE_NAME', name: 'Vova'});
store.dispatch(push('/change_name'));
}
}
class List extends Component {
render () {
return (
<div>{store.getState().user.name}</div>
);
}
}
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route path="change_name" component={List} />
</Route>
<Route path="*" component={App} />
</Router>
</Provider>
, document.getElementById('root')
);
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import {Router, Route, hashHistory} from 'react-router';
import {syncHistoryWithStore, routerReducer, routerMiddleware, push} from 'react-router-redux';
function reducer (state = {name: 'Boris'}, action) {
switch (action.type) {
case 'CHANGE_NAME': return {name: action.name};
default: return state;
}
};
const reducers = combineReducers({
user: reducer
, routing: routerReducer
});
const initialState = {user: {name: 'Nick'}};
const store = createStore(reducers, applyMiddleware(routerMiddleware(hashHistory)));
const history = syncHistoryWithStore(hashHistory, store);
class App extends Component {
render () {
return (
<div>Привет {store.getState().user.name} <a onClick={this.handleClick.bind(this)}>Измени имя</a> {this.props.children}</div>
);
}
handleClick (event) {
event.preventDefault();
store.dispatch({type: 'CHANGE_NAME', name: 'Vova'});
store.dispatch(push('/change_name'));
}
}
class List extends Component {
render () {
return (
<div>{store.getState().user.name}</div>
);
}
}
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route path="change_name" component={List} />
</Route>
<Route path="*" component={App} />
</Router>
</Provider>
, document.getElementById('root')
);
Full React Router My CheatSheet
// Full React Router
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, IndexRoute, IndexRedirect, withRouter, hashHistory, Link} from 'react-router';
class NavLink extends Component {
render () {
// onlyActiveOnIndex={false} - ссылка всегда выделена при переходе на дочерние адреса
// onlyActiveOnIndex={true} - с сылки снимается выделение при переходе на дочерине адреса
return (
<Link to={this.props.to} onlyActiveOnIndex={this.props.onlyActiveOnIndex} activeClassName="active">{this.props.children}</Link>
);
}
}
class App extends Component {
render () {
return (
<div id="main-menu">
<style>{'.active {font-weight: bold;}'}</style>
<ul>
<li style={{display: 'inline-block'}}><Link to="/page-1" activeClassName="active" onlyActiveOnIndex={true}>Страница 1</Link></li>{' '}
<li style={{display: 'inline-block'}}><Link to="/page-2" activeClassName="active" onlyActiveOnIndex={true}>Страница 2</Link></li>{' '}
<li style={{display: 'inline-block'}}><NavLink to="/page-3" onlyActiveOnIndex={true}>Страница 3</NavLink></li>
<li style={{display: 'inline-block'}}><NavLink to="/form" onlyActiveOnIndex={true}>Форма</NavLink></li>
</ul>
{this.props.children}
</div>
);
}
}
class Page1 extends Component {render () {return (<p>Содержимое страницы 1</p>);}}
class Page2 extends Component {
constructor (props) {
super(props);
}
render () {
return (<p>Содержимое страницы 2</p>);
}
componentDidMount () {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
}
routerWillLeave() {
let answer = window.confirm('Вы уверены?');
if (!answer) {return false;}
}
}
Page2 = withRouter(Page2);
class Page3 extends Component {
render () {
return (
<div>
<p>Содержимое страницы 3</p>
<ul>
<li style={{display: 'inline-block'}}><Link to="/page-3/section-1/text_for_section_1">Раздел 1</Link></li>{' '}
<li style={{display: 'inline-block'}}><Link to="/page-3/section-2/text_for_section_2">Раздел 2</Link></li>
</ul>
{this.props.children}
</div>
);
}
}
class Page3Section1 extends Component {render () {return (<p>Секция 1: {this.props.params.text}</p>);}}
class Page3Section2 extends Component {
render () {
if (this.props.params.text.replace(/_/g, ' ') === 'text for section 2') {
return (<p>Секция 2: {this.props.params.text}</p>);
} else {
return (<p>Секция 2: текст по умолчанию</p>);
}
}
}
class Form extends Component {
render () {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input ref="inp" type="text" value="text_for_section_1" onChange={this.handleOnChange.bind(this)} />
<input type="submit" value="Отправить" />
</form>
);
}
handleSubmit (event) {
event.preventDefault();
this.context.router.push(`/page-3/section-1/${this.refs.inp.value}`); // Redirect
}
handleOnChange () {}
}
Form.contextTypes = {
router: PropTypes.object.isRequired
};
class NotFound extends Component {render () {return (<p>Страница не найдена. <Link to="/page-1">Вернуться на главную.</Link></p>);}}
function sayGoodbye () {console.log('Goodbye');}
Page2.sayHello = function () {console.log('Hello');}
let routes = (
<div>
{/* полностью перерисовать компонент App и всё его содержимое при абсолютно любом переходе */}
<Route path="/" component={App}>
{/*<IndexRoute component={Page1} />*/}
<IndexRedirect to="page-2" />
<Route path="page-1" component={Page1} onLeave={sayGoodbye} />
<Route path="page-2" component={Page2} onEnter={Page2.sayHello} />
<Route path="page-3" component={Page3}>
<Route path="section-1/:text" component={Page3Section1} />
<Route path="section-2/:text" component={Page3Section2} />
</Route>
<Route path="form" component={Form} />
</Route>
<Route path='*' component={NotFound} />
</div>
);
ReactDOM.render(<Router history={hashHistory} routes={routes} />, document.getElementById('root'));
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, IndexRoute, IndexRedirect, withRouter, hashHistory, Link} from 'react-router';
class NavLink extends Component {
render () {
// onlyActiveOnIndex={false} - ссылка всегда выделена при переходе на дочерние адреса
// onlyActiveOnIndex={true} - с сылки снимается выделение при переходе на дочерине адреса
return (
<Link to={this.props.to} onlyActiveOnIndex={this.props.onlyActiveOnIndex} activeClassName="active">{this.props.children}</Link>
);
}
}
class App extends Component {
render () {
return (
<div id="main-menu">
<style>{'.active {font-weight: bold;}'}</style>
<ul>
<li style={{display: 'inline-block'}}><Link to="/page-1" activeClassName="active" onlyActiveOnIndex={true}>Страница 1</Link></li>{' '}
<li style={{display: 'inline-block'}}><Link to="/page-2" activeClassName="active" onlyActiveOnIndex={true}>Страница 2</Link></li>{' '}
<li style={{display: 'inline-block'}}><NavLink to="/page-3" onlyActiveOnIndex={true}>Страница 3</NavLink></li>
<li style={{display: 'inline-block'}}><NavLink to="/form" onlyActiveOnIndex={true}>Форма</NavLink></li>
</ul>
{this.props.children}
</div>
);
}
}
class Page1 extends Component {render () {return (<p>Содержимое страницы 1</p>);}}
class Page2 extends Component {
constructor (props) {
super(props);
}
render () {
return (<p>Содержимое страницы 2</p>);
}
componentDidMount () {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
}
routerWillLeave() {
let answer = window.confirm('Вы уверены?');
if (!answer) {return false;}
}
}
Page2 = withRouter(Page2);
class Page3 extends Component {
render () {
return (
<div>
<p>Содержимое страницы 3</p>
<ul>
<li style={{display: 'inline-block'}}><Link to="/page-3/section-1/text_for_section_1">Раздел 1</Link></li>{' '}
<li style={{display: 'inline-block'}}><Link to="/page-3/section-2/text_for_section_2">Раздел 2</Link></li>
</ul>
{this.props.children}
</div>
);
}
}
class Page3Section1 extends Component {render () {return (<p>Секция 1: {this.props.params.text}</p>);}}
class Page3Section2 extends Component {
render () {
if (this.props.params.text.replace(/_/g, ' ') === 'text for section 2') {
return (<p>Секция 2: {this.props.params.text}</p>);
} else {
return (<p>Секция 2: текст по умолчанию</p>);
}
}
}
class Form extends Component {
render () {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input ref="inp" type="text" value="text_for_section_1" onChange={this.handleOnChange.bind(this)} />
<input type="submit" value="Отправить" />
</form>
);
}
handleSubmit (event) {
event.preventDefault();
this.context.router.push(`/page-3/section-1/${this.refs.inp.value}`); // Redirect
}
handleOnChange () {}
}
Form.contextTypes = {
router: PropTypes.object.isRequired
};
class NotFound extends Component {render () {return (<p>Страница не найдена. <Link to="/page-1">Вернуться на главную.</Link></p>);}}
function sayGoodbye () {console.log('Goodbye');}
Page2.sayHello = function () {console.log('Hello');}
let routes = (
<div>
{/* полностью перерисовать компонент App и всё его содержимое при абсолютно любом переходе */}
<Route path="/" component={App}>
{/*<IndexRoute component={Page1} />*/}
<IndexRedirect to="page-2" />
<Route path="page-1" component={Page1} onLeave={sayGoodbye} />
<Route path="page-2" component={Page2} onEnter={Page2.sayHello} />
<Route path="page-3" component={Page3}>
<Route path="section-1/:text" component={Page3Section1} />
<Route path="section-2/:text" component={Page3Section2} />
</Route>
<Route path="form" component={Form} />
</Route>
<Route path='*' component={NotFound} />
</div>
);
ReactDOM.render(<Router history={hashHistory} routes={routes} />, document.getElementById('root'));
Simple React Router My CheatSheet
// Simple React Router
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
constructor (props) {
super(props);
this.state = {
route: window.location.hash.substr(1)
};
}
render () {
let Page;
switch (this.state.route) {
case '/page-1': Page = <p>Содержимое страницы 1</p>; break;
case '/page-2': Page = <p>Содержимое страницы 2</p>; break;
case '/page-3': Page = <p>Содержимое страницы 3</p>; break;
default: Page = <p>Содержимое страницы 1</p>;
}
return (
<div id="main-menu">
<ul>
<li style={{display: 'inline-block'}}><a href="#/page-1">Страница 1</a></li>{' '}
<li style={{display: 'inline-block'}}><a href="#/page-2">Страница 2</a></li>{' '}
<li style={{display: 'inline-block'}}><a href="#/page-3">Страница 3</a></li>
</ul>
{Page}
</div>
);
}
componentDidMount () {
window.addEventListener('hashchange', () => {
this.setState({route: window.location.hash.substr(1)});
});
}
}
ReactDOM.render(<App />, document.getElementById('root'));
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
constructor (props) {
super(props);
this.state = {
route: window.location.hash.substr(1)
};
}
render () {
let Page;
switch (this.state.route) {
case '/page-1': Page = <p>Содержимое страницы 1</p>; break;
case '/page-2': Page = <p>Содержимое страницы 2</p>; break;
case '/page-3': Page = <p>Содержимое страницы 3</p>; break;
default: Page = <p>Содержимое страницы 1</p>;
}
return (
<div id="main-menu">
<ul>
<li style={{display: 'inline-block'}}><a href="#/page-1">Страница 1</a></li>{' '}
<li style={{display: 'inline-block'}}><a href="#/page-2">Страница 2</a></li>{' '}
<li style={{display: 'inline-block'}}><a href="#/page-3">Страница 3</a></li>
</ul>
{Page}
</div>
);
}
componentDidMount () {
window.addEventListener('hashchange', () => {
this.setState({route: window.location.hash.substr(1)});
});
}
}
ReactDOM.render(<App />, document.getElementById('root'));
пятница, 26 августа 2016 г.
React-Redux My CheatSheet
// React Redux
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers, applyMiddleware, bindActionCreators} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import {Provider, connect} from 'react-redux';
function counterUpReducer (state = 0, action) {
switch (action.type) {
case 'COUNTER_UP': return state + action.value;
default: return state;
}
}
function counterUpAction (value) {
return {
type: 'COUNTER_UP'
, value: value
};
}
function counterDownReducer (state = 0, action) {
switch (action.type) {
case 'COUNTER_DOWN': return state + action.value;
default: return state;
}
}
function counterDownAction (value) {
return {
type: 'COUNTER_DOWN'
, value: value
};
}
const reducers = combineReducers({
counterUp: counterUpReducer
, counterDown: counterDownReducer
});
const initialState = {
counterUp: 0
, counterDown: 0
};
const store = createStore(reducers, initialState, applyMiddleware(logger(), thunk));
store.subscribe(() => {console.log(store.getState());});
class App extends Component {
render () {
return (
<CounterBlock />
);
}
}
class CounterBlock extends Component {
render () {
return (
<Counter />
);
}
}
class Counter extends Component {
constructor (props) {
super(props);
this.state = store.getState();
}
render () {
return (
<div id="counter">
<span id="counter-up">{this.props.counterUp}</span>
{' '}
<span id="up" onClick={this.props.handleUpClick.bind(this)}>Вверх</span>
<br /><br />
<span id="counter-down">{this.props.counterDown}</span>
{' '}
<span id="down" onClick={this.props.handleDownClick.bind(this)}>Вниз</span>
</div>
);
}
}
function mapStateToProps (state) {console.log(state.counterUp);
return {
counterUp: state.counterUp
, counterDown: state.counterDown
};
}
function mapDispatchToProps (dispatch) {
return {
handleUpClick: function () {
dispatch(counterUpAction(1));
this.setState(Object.assign({}, this.state, {counterUp: store.getState().counterUp}));
}
, handleDownClick: function () {
dispatch(counterDownAction(-1));
this.setState(Object.assign({}, this.state, {counterDown: store.getState().counterDown}));
}
};
}
Counter = connect(mapStateToProps, mapDispatchToProps)(Counter);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers, applyMiddleware, bindActionCreators} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import {Provider, connect} from 'react-redux';
function counterUpReducer (state = 0, action) {
switch (action.type) {
case 'COUNTER_UP': return state + action.value;
default: return state;
}
}
function counterUpAction (value) {
return {
type: 'COUNTER_UP'
, value: value
};
}
function counterDownReducer (state = 0, action) {
switch (action.type) {
case 'COUNTER_DOWN': return state + action.value;
default: return state;
}
}
function counterDownAction (value) {
return {
type: 'COUNTER_DOWN'
, value: value
};
}
const reducers = combineReducers({
counterUp: counterUpReducer
, counterDown: counterDownReducer
});
const initialState = {
counterUp: 0
, counterDown: 0
};
const store = createStore(reducers, initialState, applyMiddleware(logger(), thunk));
store.subscribe(() => {console.log(store.getState());});
class App extends Component {
render () {
return (
<CounterBlock />
);
}
}
class CounterBlock extends Component {
render () {
return (
<Counter />
);
}
}
class Counter extends Component {
constructor (props) {
super(props);
this.state = store.getState();
}
render () {
return (
<div id="counter">
<span id="counter-up">{this.props.counterUp}</span>
{' '}
<span id="up" onClick={this.props.handleUpClick.bind(this)}>Вверх</span>
<br /><br />
<span id="counter-down">{this.props.counterDown}</span>
{' '}
<span id="down" onClick={this.props.handleDownClick.bind(this)}>Вниз</span>
</div>
);
}
}
function mapStateToProps (state) {console.log(state.counterUp);
return {
counterUp: state.counterUp
, counterDown: state.counterDown
};
}
function mapDispatchToProps (dispatch) {
return {
handleUpClick: function () {
dispatch(counterUpAction(1));
this.setState(Object.assign({}, this.state, {counterUp: store.getState().counterUp}));
}
, handleDownClick: function () {
dispatch(counterDownAction(-1));
this.setState(Object.assign({}, this.state, {counterDown: store.getState().counterDown}));
}
};
}
Counter = connect(mapStateToProps, mapDispatchToProps)(Counter);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
Подписаться на:
Сообщения (Atom)