Мокеев Евгений, Гоголев Сергей
Вычисление числа Фибоначчи
– CPU bound операция
Подсчёт количества строк в файле
– I/O bound операция
Вычисление SHA-1 хеша файла в Git
– CPU bound операция
Чтение HTTP запроса | I/O |
Парсинг HTTP запроса | CPU |
Запрос в БД | I/O |
Запрос к API | I/O |
Генерация HTML | CPU |
Отправка HTML | I/O |
Одновременно выполняется только одна операция
✗ Поднятие потока не бесплатная операция
✗ Переключение между ними не бесплатное
✗ Есть лимиты на количество
✗ Каждый требует дополнительной памяти !
Apache использует multithreading. На каждый коннект к нему поднимается thread
Nginx использует паттерн Reactor
Ryan Dahl
epoll
kqueue
I/O Completion Port API
data = request('http://example.com/api');
db.insert(data);
Для каждого запроса ресурса необходимо указать handler
request('https://...', function (err, data) {
db.insert(data, function (err, result) {
// Do something else
});
}
1. Callbacks come last
2. Error comes first
JSON.parse(json);
function parseJSON(json, callback) {
try {
callback(null, JSON.parse(json));
} catch(err) {
callback(err);
}
};
parseJSON('{"foo": "bar"}', function(err, data) {
if (!err) console.log(data); //{ foo: 'bar' }
});
✓ Анонимные функции и замыкания
✓ Готов к EventLoop (DOM events, timers)
✓ Большое коммьюнити !
Готовый набор модулей для работы с фаловой системой – fs, для запросов – http, логирования – console
Node.js
apt-get install nodejs
brew install node
Скачать с nodejs.org
$ node
> 2 + 2
4
Посмотреть документацию
Node.js 4 LTS , Node.js 5Философия Unix
Small is beautiful.
Make each program do one thing well.
// hyp.js
function square(n) {
return n * n;
}
function calculateHypo(a, b) {
return Math.sqrt(square(a) + square(b));
}
// hyp.js
function square(n) {
return n * n;
}
module.exports.calculate = function (a, b) {
return Math.sqrt(square(a) + square(b));
}
// app.js
const hyp = require('./hyp.js')
hyp.calculate(3, 4); // 5
// hyp.js
function square(n) {
return n * n;
}
global.calculateHypo = function (a, b) {
return Math.sqrt(square(a) + square(b));
}
// app.js
require('./hyp.js')
calculateHypo(3, 4); // 5
// evil.js
require('shelljs/global');
rm('-rf', '/home/gogoleff/');
mkdir('-p', '/games');
// hyp.js
function square(n) {
return n * n;
}
module.exports = function calculateHypo(a, b) {
return Math.sqrt(square(a) + square(b));
}
// app.js
const calculateHypo = require('./hyp.js');
calculateHypo(3, 4); // 5
module.exports = 42;
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log('Hello ' + this.name);
};
module.exports = User;
module.exports = new User();
module.exports.calculateHypo = function (a, b) {}
exports.calculateHypo = function (a, b) {}
exports = function calculateHypo(a, b) {}
// app.js
const calculateHypo = require('hyp.js');
calculateHypo(3, 4);
// calculateHypo is not a function
exports ⇢ module.exports
const url = require('url');
url.parse('https://yandex.ru');
{ protocol: 'https:', host: 'yandex.ru', port: null, path: '/' }
const lodash = require('lodash');
lodash.shuffle([1, 2, 3, 4]);
// [4, 1, 3, 2]
lodash.uniq([2, 1, 2]);
// [2, 1]
Node Package Manager
Устанавливается вместе с Node.js
Умеет искать, устанавливать и удалять модули
Модули хранятся глобальном хранилище *
registry.npmjs.org/lodash* существуют приватные хранилища
Создаёт файл-манифест package.json описывающий модуль
Модуль + манифест = пакет
Файл содержит название и версию нашего модуля, его зависимости, ...
Demo
Ищет пакет в хранилище по имени
Выводит информацию о пакете по имени
Demo
Устанавливает пакет в качестве зависимости в директорию node_modules
Если у зависимости есть подзависимости – установятся в node_modules у зависимости
Зависимость зафиксируется в package.json
Demo
Устанавливает определённую версию пакета
Устанавливает пакет в качестве зависимости, которая не требуется для работы модуля. Например, для тестирования
Demo
Semantic Versions
2.7.0
major – ломается обратная совместимость
minor – сохраняется обратная совместимость
patch – исправления ошибок
semver.org
let counter = 1;
module.exports = function() {
return counter++;
}
const counter = require('./counter');
const anotherCounter = require('./counter');
console.log(counter()) // 1
console.log(counter()) // 2
console.log(anotherCounter()) // ?
console.log(anotherCounter()) // 3
Модули импортируются один раз, и после первого require, экспорт кешируется
Результат хранится в свойстве require.cache
Demo
Если есть встроенный модуль с таким именем, экспортируется он – require('url')
Если имя начинается с ./, / или . ./, экспортируется по указанному пути – require('./index.js')
В противном случае пакет ищется в node_modules начиная с текущей директории и поднимаясь вверх – require('mathjs')
/home/gogoleff/hypotenuse/node_modules
/home/gogoleff/node_modules
/home/node_modules
Полный алгоритмПоднять HTTP-сервер, который будет обслуживать запросы пользователей
Поднять HTTP-клиент, который сам будет ходить в другие сервисы
Взаимодействие двух программ по TCP
Межпроцессорное взаимодействие через UNIX domain socket
Взаимодействия по протоколу HTTP
const http = require('http');
const server = new http.Server();
server.listen(8080);
server.on('request', function (req, res) {
res.end('Hello, User!');
});
server.on('request', function (req, res) {
console.log(req.method); // GET
});
req.headers; // {'accept-encoding': 'gzip'}
req.url; // /favicon.ico
server.on('request', function (req, res) {
console.log(res.statusCode); // 200
});
res.setHeader('content-type', 'text/html');
res.write('Hello!');
res.end();
Demo
const http = require('http');
const req = http.request({
hostname: 'localhost',
port: 8080
});
req.end();
req.on('response', function (response) {
let str = '';
response.on('data', function (chunk) {
str += chunk; // res.write
});
response.on('end', function () {
console.log(str);
});
});
Demo
require('url');
require('https');
require('querystring');
querystring.parse('foo=bar&arr=a&arr=b');
// { foo: 'bar', arr: ['a', 'b'] }
require('dns');
dns.lookup('nodejs.org', function (err, address) {
console.log(address); // 104.20.22.46
});
req.on('response', function (response) {
let str = '';
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
console.log(str);
});
});
var EventEmitter = require('events').EventEmitter;
var ee = new EventEmitter();
ee.on('log', console.log);
ee.emit('log', 'Hello!'); // Logged 'Hello!'
ee.emit('unknown event'); // Nothing
ee.emit('error'); // Uncaught, unspecified "error" event.
req.on('response', function (response) {
let str = '';
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
console.log(str);
});
});
npm install request
const request = require('request');
request('https://registry.npmjs.org/',
function (err, res, body) {
if (!err && res.statusCode === 200) {
const registry = JSON.parse(body);
console.log(registry.doc_count);
// 271820
}
}
);
Отправка файлов
HTTP-аутентификация
Проксирование
const fs = require('fs');
fs.readFile(__filename, function (err, data) {
console.log(data);
});
__filename – имя текушего файла
Buffer 63 6f 6e 73 74 20 66 73 20 3d 20 72 65 ...
Экспортирует класс для работы
с бинарными данными
Можно рассматривать как массив целых чисел, ограниченных диапазоном 0-255
Каждое число представляет байт
const letterB = new Buffer([98]);
console.log(letterB.toString()); // b
console.log(letterB.toString('utf-8')); // b
const msg = new Buffer([0x2f, 0x04, 0x3d, 0x04,
0x34, 0x04, 0x35, 0x04, 0x3a, 0x04, 0x41, 0x04]);
msg.toString();
// \u0004=\u00044\u00045\u0004:\u0004A\u0004
letterB.toString('ucs2');
// 'Яндекс'
fs.readFile(__filename, function (err, data) {
console.log(data.toString());
});
fs.readFile(__filename, 'utf-8', function (err, data) {
console.log(data);
});
const fs = require('fs'); fs.readFile(__filename, function (err, data) { console.log(data.toString()); });
const data = fs.readFileSync(__filename, 'utf-8');
fs.appendFile();
fs.writeFile();
fs.unlink();
fs.mkdir();
fs.stat(__filename, function (stats) {
console.log(stat.isDirectory()); // false
});
fs.watch();
fs.watch(__filename, function (event, filename) {
console.log(event); // change or rename
});
fs.watch(__dirname, function (event, filename) {
console.log(event); // change or rename
});
fs.readFile(__filename, 'utf-8', function (err, data) {
console.log(data);
});
Данные предварительно сохраняются в Buffer
Только когда весь файл прочитан, данные передаются в обработчик
Данные готовы для обработки, как только будет прочитан первый chunk
✓ Экономия ресурсов
✓ Экономия времени
//gzip.js
const fs = require('fs');
const zlib = require('zlib');
const filename = process.argv[2];
fs.readFile(filename, function(err, buffer) {
zlib.gzip(buffer, function(err, buffer) {
fs.writeFile(filename + '.gz', buffer, function(err) {
console.log('Success!');
});
});
});
$ node gzip bigfile.doc
0 1 2
Файл считывается в Buffer,
который хранится в памяти
Buffer в V8 не может быть больше 0x3FFFFFFF bytes ~ 1 Gb
File size is greater than possible Buffer: 0x3FFFFFFF bytes
Если приложение обрабатывает одновременно несколько пользователей?
fs
.createReadStream(filename)
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream(filename + '.gz'))
.on('finish', function() {
console.log('Success!);
});
Readable для чтения
Writable для записи
Duplex для чтения и записи
Transform Duplex но с преобразованием/p>
fs.ReadStream
fs.createReadStream(filename);
http.IncomingMessage
require('http')
.request(options)
.on('response', function (res) {
res.on('data', function (chunk) {});
res.on('end', function () {});
});
on('data') при получении чанка данных
on('end') при закрытии стрима
on('close') для записи
on('error') в случае ошибки !
process.stdin
.on('data', function(chunk) {
process.stdout.write('Hello, ' +
chunk.toString());
});
process.stdout
fs.WriteStream
fs.createWriteStream(filename);
http.ServerResponse
server.on('request', function (req, res) {
res.write('Hello, ');
res.write('World!');
res.end();
});
.write() отправляет порцию данных в поток
.end() завершает запись в поток
on('error') в случае ошибки передачи данных
$ cat index.js | grep "function" | wc -l
2
process.stdin
.on('data', function(chunk) {
process.stdout.write('Hello, ' +
chunk.toString());
});
process.stdin.pipe(process.stdout);
//gzip.js
const fs = require('fs');
const zlib = require('zlib');
const filename = process.argv[2];
fs.readFile(filename, function(err, buffer) {
zlib.gzip(buffer, function(err, buffer) {
fs.writeFile(filename + '.gz', buffer, function(err) {
console.log('Success!');
});
});
});
fs
.createReadStream(filename)
.pipe(zlib.createGzip()) // Duplex, Transform
.pipe(fs.createWriteStream(filename + '.gz'))
.on('finish', function() {
console.log('Success!);
});
Пример в 2-nodejs/examples
Время на решение
7 дней