intro to node.js from the perspective of a perl hacker
DESCRIPTION
A brief talk about Node.js given to Kitchener/Waterloo Perl Mongers, September 2011.TRANSCRIPT
Node.jsCoding for the server in Javascript
(from the perspective of a Perl hacker)
Why use Node?
1: Performance
Node.js vs Perl
Qpsmtpd vs Haraka
matt@Valour ~/Perl/node$ time /usr/libexec/postfix/smtp-source -l 5000 -m 50000 -s 100 -d -f matt@local -t [email protected] -c localhost:252550000
real 1m28.451suser 0m1.789ssys 0m6.357smatt@Valour ~/Perl/node$ time /usr/libexec/postfix/smtp-source -l 5000 -m 50000 -s 100 -d -f matt@local -t [email protected] -c localhost:252550000
real 0m12.020suser 0m1.730ssys 0m6.351s
2: Non-Blocking IO
What’s your CPU doing?
Core: ~1 instruction per cycle
L1 cache: 3 cycles
L2 cache: 14 cycles
RAM: ~250 cycles
Disk seek: 40 million cycles
Network: 240 million cycles
Source: http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait
Of course Perl has AIO
POE
AnyEvent
EV
Danga::Socket
The problem: CPAN modules aren’t AsyncTypical example: ORMs
3: The Language…
OK so it’s Javascript
It’s really not that bad, if you structure it well
Node.js has clean modules with no global namespace
I’m not missing all the $@%*& bullshit
exports.hook_rcpt = function (next, connection) { connection.relaying = true; next(OK);}
Classes are Functions
function Connection(client, server) { this.client = client; this.server = server; …}
var conn = new Connection(client, server);
Inheritance is weird, but works
var net = require('net');var util = require('util');
function Socket(options) { if (!(this instanceof Socket)) return new Socket(options); net.Socket.call(this, options); // like SUPER this.current_data = ''; this.on('data', this.process_data); this.on('end', this.process_end);}
util.inherits(Socket, net.Socket);
It’s just as messy as Perl
Stomp on inherited class “hash” entries
Parameter passing isn’t checked in any way
Has stuff you shouldn’t use and consider deprecated
It’s nicer than Perl…
No global variables (perlvar) nonsense
Regexps return an array of matches (0 = whole match, 1 = capture 1, 2 = capture 2, etc)
Most methods don’t mutate, which feels surprisingly nice
Less freaky syntax hidden corners
But it also sucks HARD
No lexical scope. Just global (module level) and function level.
for (i=0; i<10; i++) {
var j=i;
setTimeout(function () { console.log(j) }, 1000);
}
prints ten “9”s.
for (i=0; i<10; i++) { setTimeout((function (j) { return function () { console.log(j) } })(i), 1000);}
Outer function captures j (as JShas function variable scope)
Returns inner function whichuses j as a closure variable
We execute the outer functionpassing in i (which becomes j)
But it also sucks HARD
The “this” variable doesn’t stick around:
MyObject.prototype.do_something_later = function () {
setTimeout(function () { this._do_something() }, 1000);}
MyObject.prototype.do_something_later = function () { var self = this;
setTimeout(function () { self._do_something() }, 1000);}
Async can make things hard
What I’ve typically done before: if ($user->requires_moderation()) { # makes a DB call
… # moderate user
}
What you have to do in Async style: user.requires_moderation(function (err, flag) {
if (err) { … }
if (flag) {
// moderate user
}
});
Npm vs CPAN
Node Package Manager
(Obviously) a lot less stuff than CPAN
But still a surprising amount: 3831 packages as of writing
By default everything installs locally – no more screwing up apps by updating some library
For binary tools this still works. Binaries are symlinks to the install directory
Uploading is also much easier. “npm publish”. Done.
Debugging
Basic debugger built in, pretty shit really
Node-inspector – plugs into WebKit’s Javascript debugger
Profiling
Frankly, totally busted
Node-inspector has something but I can’t make it work
V8’s profiler is based on periodic snapshots, not a full profile
Other nice parts
Methods can be (re)defined on the fly – makes per-object methods trivial
Try/catch and switch part of core language
Named methods/functions are just variablesfunction blah () { } === var blah = function () { }
blah // variable containing the function
blah() // call the function
object.blah() === object[“blah”]()
It’s much more pure OO than Perlnot very many global functions
string.length vs length($string)
Node.js API
Timers, current process, Events, binary buffers, Streams, Crypto, Filesystem, Networking (IPv6 compatible), HTTP Server and client, Child processes, OS info
What’s Missing?Cmdline arg parsing
pack/unpack, flock, seek
Decent date/time manipulation
QuotedPrintable
Encode equivalent (iconv on NPM, but that lacks lots)
Test harness
Example of filesystem access
Hard way:
fs.open(path, ‘r’, function (err, fd) { if (err) { ... } var buf = new Buffer(1024); fs.read(fd, buf, 0, 1024, null, function (err, bytesRead, buf) { ... });});
Easy way:
var rs = fs.createReadStream(path);rs.on(‘error’, function (err) { ... });rs.on(‘data’, function (buf) { ... });
Note: no line-by-line reading built in
IPC coming in node 0.6var cp = require('child_process');
// Note: not what we know as fork() in POSIX/Perlvar n = cp.fork(__dirname + '/sub.js');
n.on('message', function(m) { console.log('PARENT got message:', m);});
n.send({ hello: 'world' }); // uses JSON internally
// in sub.js:process.on('message', function(m) { console.log('CHILD got message:', m);});
process.send({ foo: 'bar' });
IPC with hook.io
Available via NPM
Does transparent message passing between multiple listeners
Kind of like gearman for node, but maybe more feature complete
Using multiple CPUs
By default there’s no threading/forking
Needs a module – “cluster” from npm
var net = require(‘net’);var server = net.createServer(function (client) { // handle client connection});
server.listen(2525);
var cluster = require(‘cluster’);var net = require(‘net’);var server = net.createServer(function (client) { // handle client connection});var c = cluster(server);c.listen(2525);
Links:
http://nodejs.org/http://npmjs.org/https://github.com/baudehlo/Haraka
Thank You