Mais conteúdo relacionado Semelhante a Real World Lessons on the Pain Points of Node.js Applications (20) Real World Lessons on the Pain Points of Node.js Applications1. Real World Lessons on the Pain Points of
Node.js Applications
@Ben_Hall
Ben@BenHall.me.uk
OcelotUproar.com / Katacoda.com
3. Agenda
• Creating strong foundation
– Node v5, NPM, Security
• Error Handling
• Async/Promises
• Deploying / Scaling
• Performance
• Debugging
8. https://github.com/nodejs/node/wiki/
API-changes-between-v0.10-and-v4
• The domain module has been scheduled for
deprecation, awaiting an alternative for those
who absolutely need domains.
• fs.exists() is now deprecated. It is suggested to
use either fs.access() or fs.stat(). Please read the
documentation carefully.
• Updated setImmediate() to process the full queue
each turn of the event loop, instead of one per
queue.
9. https://nodejs.org/en/blog/release/v5
.0.0/
• Breaking changes.
• When parsing HTTP, don't add duplicates of the
following headers
• HTTP methods and header names must now conform
to the RFC 2616 "token" rule
• zlib: Decompression now throws on truncated input
• buffer: Removed both 'raw' and 'raws' encoding types
from Buffer, these have been deprecated for a long
time.
10. Docker to test deployment
• Didn’t need to install anything on host
> docker run -it -v $(pwd):/src -p 3000 node:5
root@container:> npm install
root@container:> npm start
15. Angular 1.2 => 1.3
> angular.element(document)
[#document]
> angular.element(document)
TypeError: undefined is not a function
28. Cover Your Bases
• Barry Dorrans, Troy Hunt, Niall Merrigan
• Troy Hunt and Barry Dorrans sessions
• A security testers toolkit - Niall Merrigan
• https://vimeo.com/131641274
• Going beyond OWASP - Barry Dorrans
• https://vimeo.com/131642364
29. Child Process Exec
child_process.exec(req.query.url, function (err, data) {
console.log(data);
});
https://localhost:49155/api/openUrlInDefaultBrowser?url=c:/windows/sy
stem32/calc.exe
Thanks TrendMicro Antivirus on Windows!
https://code.google.com/p/google-security-research/issues/detail?id=693
30. Cross-Site Request Forgery
var csrf = require('csurf');
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });
app.get('/form', csrfProtection, function(req, res) {
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
https://blog.risingstack.com/node-js-security-checklist/
31. Rate Limiting
var ratelimit = require('koa-ratelimit');
var ipBasedRatelimit = ratelimit({
db: redis.createClient(),
duration: 60000,
max: 10,
id: function (context) {
return context.ip;
}
});
app.post('/login', ipBasedRatelimit, handleLogin);
https://blog.risingstack.com/node-js-security-checklist/
32. Security Audit NPM Packages
> npm install nsp
> nsp check
(+) 18 vulnerabilities found
33. https://nodesecurity.io/
• Root Path Disclosure (2x)
• Regular Expression Denial of Service (10x)
• Incorrect Handling of Non-Boolean Comparisons
During Minification
• Denial-of-Service Extended Event Loop Blocking
• Denial-of-Service Memory Exhaustion
• Symlink Arbitrary File Overwrite
• Remote Memory Disclosure (2x)
34. NPM Credentials Leaks
• https://github.com/ChALkeR/notes/blob/mast
er/Do-not-underestimate-credentials-
leaks.md
51. Not part of the Node
Makes integration more difficult.
Makes swapping code in / out more
painful.
53. “The goal isn’t about removing
levels of indentation but rather
writing modular code that is easy to
reason about”
Strongloop Blog
http://strongloop.com/strongblog/node-js-callback-hell-promises-generators/
63. “You can't get into callback hell if
you don't go there.”
Isaac Schlueter
73. var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello worldn");
}).listen(8000);
}
74. > NODE_DEBUG=cluster node server.js
23521,Master Worker 23524 online
23521,Master Worker 23526 online
23521,Master Worker 23523 online
23521,Master Worker 23528 online
81. Deploying Node App via Docker
> cat Dockerfile
FROM node:5-onbuild
EXPOSE 3000
> docker build –t my-node-app .
> docker run –p 3000:3000 my-node-app
85. > docker run -d
-p 80:80
-v /var/run/docker.sock:/tmp/docker.sock:ro
jwilder/nginx-proxy
> docker run --name web
-e VIRTUAL_HOST=www.katacoda.com
my-node-app
88. var check_docker = function(cb) {
docker.ping(function(err) { handle_error('docker', err, cb);});
};
var check_redis = function(cb) {
redis.status(function(err, connected) {
if(err === null && connected === "ready") {
cb();
} else {
handle_error('redis', {msg: 'Not Connected', err: err}, cb);
}
})
};
var check_pg = function(cb) {
pg.status(function(err) { handle_error('postgres', err, cb);});
};
94. var Ocelite = require('ocelite');
var db = new Ocelite();
db.init('data.db', ['user'], function() {
db.save('user', {name: 'Barbara Fusinska', twitter: 'basiafusinska'}, ['twitter'],
function() {
db.get('user', 'twitter', 'basiafusinska', function(err, obj) {
console.log(obj);
});
});
});
95. > npm install v8-profiler
const profiler = require('v8-profiler')
const fs = require('fs')
var profilerRunning = false
function toggleProfiling () {
if (profilerRunning) {
const profile = profiler.stopProfiling()
console.log('stopped profiling')
profile.export()
.pipe(fs.createWriteStream('./myapp-'+Date.now()+'.cpuprofile'))
.once('error', profiler.deleteAllProfiles)
.once('finish', profiler.deleteAllProfiles)
profilerRunning = false
return
}
profiler.startProfiling()
profilerRunning = true
console.log('started profiling')
}
process.on('SIGUSR2', toggleProfiling)
> kill -SIGUSR2 <pid>
103. Summary
• Update to Node.js v5
• Start using ES6
• Security
• Manage your errors
• Forgot making promises
• Scale using Docker
Notas do Editor var d = require('domain').create();
d.on('error', function(err){
// handle the error safely
console.log(err);
});
// catch the uncaught errors in this
// asynchronous or synchronous code block
d.run(function(){
// the asynchronous or synchronous code
// that we want to catch thrown errors on
var err = new Error('example');
throw err;
});
var util = require('util');
function UserNameAlreadyExistsError(err) {
Error.call(this);
this.name = 'UserNameAlreadyExistsError';
if(typeof err === 'string') {
this.message = err;
} else {
this.message = err.message;
}
this.detail = err.details;
}
util.inherits(UserNameAlreadyExistsError, Error);
module.exports = UserNameAlreadyExistsError; try {
var files = yield readdir(dir)
} catch (er) {
console.error('something happened whilst reading the directory')
}
getTweetsFor("domenic") // promise-returning function
.then(function (tweets) {
var shortUrls = parseTweetsForUrls(tweets);
var mostRecentShortUrl = shortUrls[0];
return expandUrlUsingTwitterApi(mostRecentShortUrl); // promise-returning function
})
.then(httpGet) // promise-returning function
.then(
function (responseBody) {
console.log("Most recent link text:", responseBody);
},
function (error) {
console.error("Error with the twitterverse:", error);
}
); var repo_url = $(site).find('li[data-tab="repo"]').find('a').attr('href');
var activity_url = $(site).find('li[data-tab="activity"]').find('a').attr('href');
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $(p).find('.repolist-name a').attr('href'), title: $(p).find('.repolist-name').text().clean(), stars: parseInt($(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $(p).find('.language').text()};
if(repo_list.length === (ix + 1)) {
request.get("https://github.com" + activity_url, function(err, activity_html) {
var $_activity = cheerio.load(activity_html);
var repo_list = $_activity('.alert');
var results = [];
repo_list.each(function(ix, p) {
var repo = {title: $(p).find('.title').text().clean(), actioned_at: $(p).find('.js-relative-date').attr('datetime')};
results.push(repo);
if(repo_list.length === (ix + 1)) {
data.repositories = results.repo;
data.public_activity = results.activity;
callback({parser: 'github', id: data.username, profile: data});
}
});
});
}
});
}); var repo_url = $(site).find('li[data-tab="repo"]').find('a').attr('href');
var activity_url = $(site).find('li[data-tab="activity"]').find('a').attr('href');
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $(p).find('.repolist-name a').attr('href'), title: $(p).find('.repolist-name').text().clean(), stars: parseInt($(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $(p).find('.language').text()};
if(repo_list.length === (ix + 1)) {
request.get("https://github.com" + activity_url, function(err, activity_html) {
var $_activity = cheerio.load(activity_html);
var repo_list = $_activity('.alert');
var results = [];
repo_list.each(function(ix, p) {
var repo = {title: $(p).find('.title').text().clean(), actioned_at: $(p).find('.js-relative-date').attr('datetime')};
results.push(repo);
if(repo_list.length === (ix + 1)) {
data.repositories = results.repo;
data.public_activity = results.activity;
callback({parser: 'github', id: data.username, profile: data});
}
});
});
}
});
}); var repo_url = $(site).find('li[data-tab="repo"]').find('a').attr('href');
var activity_url = $(site).find('li[data-tab="activity"]').find('a').attr('href');
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $(p).find('.repolist-name a').attr('href'), title: $(p).find('.repolist-name').text().clean(), stars: parseInt($(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $(p).find('.language').text()};
if(repo_list.length === (ix + 1)) {
request.get("https://github.com" + activity_url, function(err, activity_html) {
var $_activity = cheerio.load(activity_html);
var repo_list = $_activity('.alert');
var results = [];
repo_list.each(function(ix, p) {
var repo = {title: $(p).find('.title').text().clean(), actioned_at: $(p).find('.js-relative-date').attr('datetime')};
results.push(repo);
if(repo_list.length === (ix + 1)) {
data.repositories = results.repo;
data.public_activity = results.activity;
callback({parser: 'github', id: data.username, profile: data});
}
});
});
}
});
}); var repo_url = $(site).find('li[data-tab="repo"]').find('a').attr('href');
var activity_url = $(site).find('li[data-tab="activity"]').find('a').attr('href');
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $(p).find('.repolist-name a').attr('href'), title: $(p).find('.repolist-name').text().clean(), stars: parseInt($(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $(p).find('.language').text()};
if(repo_list.length === (ix + 1)) {
request.get("https://github.com" + activity_url, function(err, activity_html) {
var $_activity = cheerio.load(activity_html);
var repo_list = $_activity('.alert');
var results = [];
repo_list.each(function(ix, p) {
var repo = {title: $(p).find('.title').text().clean(), actioned_at: $(p).find('.js-relative-date').attr('datetime')};
results.push(repo);
if(repo_list.length === (ix + 1)) {
data.repositories = results.repo;
data.public_activity = results.activity;
callback({parser: 'github', id: data.username, profile: data});
}
});
});
}
});
}); var repo_url = $(site).find('li[data-tab="repo"]').find('a').attr('href');
var activity_url = $(site).find('li[data-tab="activity"]').find('a').attr('href');
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $(p).find('.repolist-name a').attr('href'), title: $(p).find('.repolist-name').text().clean(), stars: parseInt($(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $(p).find('.language').text()};
if(repo_list.length === (ix + 1)) {
request.get("https://github.com" + activity_url, function(err, activity_html) {
var $_activity = cheerio.load(activity_html);
var repo_list = $_activity('.alert');
var results = [];
repo_list.each(function(ix, p) {
var repo = {title: $(p).find('.title').text().clean(), actioned_at: $(p).find('.js-relative-date').attr('datetime')};
results.push(repo);
if(repo_list.length === (ix + 1)) {
data.repositories = results.repo;
data.public_activity = results.activity;
callback({parser: 'github', id: data.username, profile: data});
}
});
});
}
});
}); var parse_repositories = function(repo_url, callback) {
request.get("http://github.com" + repo_url, function(rr, repo_html) {
var $_repo = cheerio.load(repo_html);
var repo_list = $_repo('.repolist li');
var results = [];
repo_list.each(function(ix, p) {
var source = true;
if($_repo(p).hasClass('fork'))
source = false;
var repo = { link: "http://github.com" + $_repo(p).find('.repolist-name a').attr('href'), title: $_repo(p).find('.repolist-name').text().clean(), stars: parseInt($_repo(p).find('.stargazers a').text().clean(), 10), is_source: source, language: $_repo(p).find('.language').text()};
if(repo.title !== '') {
results.push(repo);
}
if(repo_list.length === (ix + 1)) {
callback(null, results);
}
});
if(repo_list.length === 0) {
callback(null, []);
}
});
};
run(function* () {
console.log("Starting")
var file = yield readFile("./async.js”)
console.log(file.toString())
})
app.post('/users', function *(request) {
var user = new User(request.params);
if (yield user.save()) {
return JSON.stringify(user);
} else {
return 422;
}
});
Story Story