- The best overview of the Node.js architecture I've come across: https://youtu.be/PNa9OMajw9w
- Another great resource: https://nodesource.com/blog/understanding-the-nodejs-event-loop/
- https://github.com/nodejs/node/blob/master/lib/timers.js
- https://github.com/nodejs/node/blob/master/src/node.cc
- https://github.com/nodejs/node/blob/master/src/timer_wrap.cc
Timers
- A JS hashmap of linked lists of timers, keyed on the timer offset in ms. Timers are added FIFO.
- In libuv, each linked list gets a timer handle.
- The timer handle's callback will call a JS callback, listOnTimeout(), which fires all the due timers in the linked list corresponding to the handle, then calculates the new timeout time for the corresponding handle.
setImmediate()
- A JS queue of immediate callbacks & a processImmediate() function that calls all the immediate callbacks.
- A check handle, immediate_check_handle, belonging to the process & created when the process object is initialized.
- A boolean in the process object that toggles using the immediate handle, with a setter callback that toggles the active state of the check handle.
- When the check handle is made active, its C++ callback is checkImmediate(), which in turn makes a callback to the JS function processImmediate().
libuv fs methods: uv_fs_open, uv_fs_close, etc. These take as input a callback. If the callback is NULL, they will execute synchronously in the libuv threadpool.
ASYNC_CALL & SYNC_CALL in node_file.cc: these will create a uv_fs_t and call the respective uv_fs_ methods on it, with or without a callback function.
C++ methods in node_file.cc:, Open, FStat, Read, Close.
JS methods in fs.js: readFile, readFileSync, etc. These will call the C++ methods. The async methods pass in a callback, which gets passed down to the libuv calls.
Reading a file:
- Open: returns a file descriptor (integer that represents this opened file entry from the kernel)
- FStat: populates an array with file information corresponding to the file descriptor
- CreateBuffer
- ReadSync: populates buffer
- Close: closes file descriptor
Maintains an array of arrays of listener functions, keyed by event name. (The 2nd array is only created if needed, i.e., if there is more than 1 listener for a particular event. Otherwise the listener is stored in the top-level array.)
on(type, listener): really _addListener(). Adds the listener to the top-level array if it's the first listener for that event, otherwise add it to the end of the array of listeners for that type.
emit(type, arguments): Calls each of the listeners registered to that event. Listener calls are achieved via optimized EmitNone, EmitOne, EmitTwo, EmitThree, and EmitMany methods.
- https://github.com/nodejs/node/blob/master/lib/_http_server.js
- https://github.com/nodejs/node/blob/master/src/tcp_wrap.cc
- https://nodejs.org/api/http.html#http_http_createserver_requestlistener
_http_server.js/createServer(requestListener)
- requestListener is a function which takes as input a request and a response.
- Creates a new server, registers the requestListener to the "request" event, and registers an internal connectionListener to the "connection" event.
_http_server.js/connectionListener(socket)
- Set up the socket, allocate & re-initialize a new HTTPParser, establish a 1:1 relationship between socket & parser.
- Reinitialize() is a C++ function that calls http_parser_init() in deps/http_parser/http_parser.cc. It will init a new HTTPPARSER resource.
- Set parser.onIncoming() to be the parserOnIncoming() function.
_http_server.js/parserOnIncoming(server, socket, state, req, keepAlive)
- Called after headers are read on a new message.
- Creates a response object. Emits the "request" event and sends in the request & response object as arguments.
server.listen(port, callback) -> net.js/Server.prototype.listen() -> net.js/listenInCluster(server, address, port, addressType, backlog, fd, exclusive) -> net.js/setupListenHandle(address, port, addressType, backlog, fd)
net.js/setupListenHandle(address, port, addressType, backlog, fd) Calls createServerHandle to get a TCPWrap handle, sets its .onconnection property to the onconnection function, then associates the handle with the Server object.
net.js/createServerHandle(address, port, addressType, fd) Creates a new TCPWrap from tcp_wrap.cc and binds it to the address & port supplied.
src/tcp_wrap.cc/bind(address, port) Calls uv_tcp_bind to bind the handle associated with the TCPWrap to the address & port supplied.
onconnection(err, clientHandle) Creates a new JS Socket, sets its server to be the JS Server that is associated with the handle whose onconnection function was called, & then emits a "connection" event on the Server.
- _http_common.js: https://github.com/nodejs/node/blob/master/lib/_http_common.js
- node_http_parser.cc: Wrapper around a C HTTP parser.
- http_parser.c: https://github.com/nodejs/node/tree/master/deps/http_parser
In _http_common.js, the function parserOnHeadersComplete will call onIncoming() for a given HTTP parser. The callback in C++ HTTP Parser in node_http_parser.cc will call the JS function parserOnHeadersComplete(). On new connection
- https://github.com/expressjs/express/blob/master/lib/application.js
- https://github.com/expressjs/express/blob/master/lib/express.js
Uses http.CreateServer with the application object as the request listener. The application object is a "function" which contains the route handler logic.
createApplication() creates a new app of type function(req, res, next), then calls app.init(). The function calls app.handle(req, res, next), which in turn calls router.handle(req, res, next).