Real-life Node.js

August Lilleaas

Hva er poenget med Node.js?

Push-meldinger for iOS

Nettlesertesting

Hva er poenget med Node.js?

JavaScript: Ikke så viktig

Jeg lager ikke websider i Node.js

Skreddersydde servere

Løser multitrådet programmering for deg

Node.js har ikke tråder

Bare tøys

Din kode kjører alltid i samme tråd

API-er bruker tråder i bakgrunnen

fs.readFile("/path/to/a", function (err, data) {
    console.log("a");
});
fs.readFile("/path/to/b", function (err, data) {
    console.log("b");
});
console.log("test");

// => test, a, b
// => test, b, a
fs.readFile("/path", func)
childProcess.exec("pygments 'var myCode = {};'", func)
tcpSocket.on("data", func)
httpServer.on("request", func)
udpSocket.on("data", func)

Din kode kjører alltid i samme tråd

API-er bruker tråder i bakgrunnen

teddziuba.com/2011/10/node-js-is-cancer.html

Mangel på programmerer-tråder kan være et problem

uv_work_t* req = new uv_work_t();
req->data = ...;
uv_queue_work(uv_default_loop(), req, OperationFunc, AfterFunc);

codeslinger.posterous.com/if-youre-using-nodejs-youre-doing-life-wrong

“Callback spaghetti is about the last pattern with which you'd ever want to write anything”

Push-meldinger for iOS

HTTPS-server, ActiveMQ, TLS-sockets

var httpsServer = https.createServer({
    key: fs.readFileSync("server.key"),
    cert: fs.readFileSync("server.crt"),
    requestCert: true,
    ca: fs.readFileSync("ca.crt"),
    rejectUnauthorized: true
});
httpsServer.on("request", function (req, res) {
    var data = JSON.parse(req.body);
    var peerCert = req.connection.getPeerCertificate();
    var fingerprint = peerCert.fingerprint;
    mysql.find({
        table: "apps",
        where: ["fingerprint = ?", fingerprint],
        success: function (app) {
            activeMQ.queue(app, data);
            res.writeHead(200); res.end();
        }
    });
});
function onStompMessage(app, data) {
    if (!(app.identifier in apnsSockets)) {
        var socket = new ApnsSocket(app);
        apnsSockets[app.identifier] = socket;
    }

    apnsSockets[app.identifier].write(data);
}
write: function (data) {
    queue.push(data);
    consumeQueue();
}
function consumeQueue() {
    if (queue.length == 0) return;
    if (connectInProgress) return;
    if (!connected) connect(); return;

    var message = queue.shift();
    writeToSocket(message);
    process.nextTick(function () { consumeQueue(); });
}
function connect() {
    connectInProgress = true;
    promise.all([
        pfs.readFile(app.sslPath + "/apns.key"),
        pfs.readFile(app.sslPath + "/apns.crt")
    ]).then(function (key, cert) {
        var socket = tls.connect(2195, "gateway.push.apple.com", {
            key: key, cert: cert, ca: CA_CERTS
        }, function () {
            if (socket.authorized) {
                onSuccessfulConnection(socket);
            }
        });
    });
}
function onSuccessfulConnection(socket) {
    connectionInProgress = false;
    connected = true;

    socket.on("end", function () { connected = false; });
    socket.on("close", onSocketClose);
    socket.on("data", onApnsError);

    connectedSocket = socket;
    consumeQueue();
}

Vi har nå sett et eksempel på multitrådet kode hvor deadlocks og annet snacks er umulig.

var buffer = new Buffer(1 + 4 + 4 + 2 + deviceToken.length + 2 + payload.length);
var i = 0;
// Command
buffer[i++] = 1;
// Message identifier
buffer[i++] = msgId >> 24 & 0xFF;
buffer[i++] = msgId >> 16 & 0xFF;
buffer[i++] = msgId >> 8 & 0xFF;
buffer[i++] = msgId & 0xFF;
// Expiry in epoch seconds (a day from now)
var expiresAt = (now.getTime() / 1000) + 86400;
buffer[i++] = expiresAt >> 24 & 0xFF;
buffer[i++] = expiresAt >> 16 & 0xFF;
buffer[i++] = expiresAt >> 8 & 0xFF;
buffer[i++] = expiresAt & 0xFF;
// Device token
buffer[i++] = deviceToken.length >> 8 & 0xFF;
buffer[i++] = deviceToken.length & 0xFF;
deviceToken.copy(buffer, i, 0, deviceToken.length);
i += deviceToken.length;
// Payload
buffer[i++] = payload.length >> 8 & 0xFF;
buffer[i++] = payload.length & 0xFF;
payload.copy(buffer, i, 0, payload.length);
return buffer;

Nettlesertesting

busterjs.org

HTTP capture server, resource sets

var captureServer = busterCaptureServer.create();
var httpServer = http.createServer();
httpServer.listen(8080);
captureServer.attach(httpServer);
function respond(req, res) {
    if (req.method == "GET" && req.path == capturePath) {
        captureSlave(req, res); return true;
    }
    if (req.method == "GET" && req.path == "/sessions") {
        listSessionsFromRequest(res); return true;
    }
    if (req.method == "GET" && url.pathname == "/sessions/current") {
        showCurrentSession(res); return true;
    }
    if (req.method == "POST" && req.path == "/sessions") {
        createSessionFromRequest(req, res); return true;
    }

    // ...
function captureSlave(req, res) {
    var slave = bSlave.create(this, bayeuxServer, currentSession);
    slave.on("end", function () { unloadSlave(slave); });
    res.writeHead(302, {"Location": slave.url}); res.end();
    bayeux.publish("/capture", slave.serialize());
    slaves.push(slave);
}
function createSessionFromRequest(req, res) {
    var requestBody = "";
    req.setEncoding("utf8");
    req.on("data", function (chunk) { requestBody += chunk; });
    req.on("end", function () {
        createSessionWithRequestBody(requestBody, res);
    });
}
function createSessionWithRequestBody(requestBody, res) {
    try {
        var data = JSON.parse(requestBody);
    } catch(e) {
        res.writeHead(400); res.write("Invalid JSON"); res.end();
        return;
    }

    var session = createSession(data);
    res.writeHead(sessions.length > 1 ? 202 : 201);
    res.write(JSON.stringify(session.serialize()));
    res.end();
}
var resourceSet = busterResources.resourceSet.deserialize({
    resources: [
        {path: "/foo.js", content: "var a = 123;"},
        {path: "/bar.js", content: "var b = 456;"}
    ],
    loadPath: ["/foo.js"]
});
function respond(req, res) {
    if (req.method == "GET" && req.path == capturePath) {
        captureSlave(req, res); return true;
    }
    if (req.method == "GET" && req.path == "/sessions") {
        listSessionsFromRequest(res); return true;
    }

    // ...

    if (resourceMiddleware.respond(req, res)) return true;
var resourceMiddleware = bResources.resourceMiddleware.create();

// ...

resourceMiddleware.mount(
    "/sessions/" + session.id,
    session.resourceSet
);
// Klient
$~> buster test
var rs = bResourceSet.create(); rs.addResource(...);
rs.serialize();
// Server
var rs = bResourceSet.deserialize(reqBody);
middleware.mount("/sessions/" + id);
slave.load("/sessions/" + id)

Med gode abstraksjoner kan man lage fine høynivå webapper med Node.js

Node er ikke som poteten

(Poteten kan brukes til alt)

Takk for meg

august@augustl.com