Event-driven & asynchrous IO & Event Emitter
NOTE :
- Node.js is an application [a process] in OS; but has multiple threads
- Only one JS thread, all others are worker threads in thread pool used by OS
- they share data via message queue
Asynchrous I/O
- Each I/O operation is assigned a
worker thread
in Node.js run time env; - But only one
master thread
(js engine) deal with computation
- Each I/O operation is assigned a
Even Loop
Node.js — V8 — libuv — OS
libuv [cross platform Asynchronous I/O]: handles utilize other threads to complete asynchronous I/O
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
Node.js use cases
- I/O-bound application
- Except the only one JS processing thread, all other threads deal with I/O operation, so that I/O processing can happend simultaneously => save time
- JS processing thread has no CPU-bound computation, only stay in event loop, and keep staring at the top of message queue; When some event done, handle its callbacks
process.nextTick()
vs. setImmediate()
- tick: each iteration of an Event Loop is called a tick.
process.nextTick()
: will execute all callbacks set by it previousely in next ticksetImmediate()
: will execute only one callback in next tick, even set multiple callbacks
CommonJS: require
& exports
Sad History: Before CommonJS, JavaScript doesn’t have module importing feature, so it is limited to small script, cannot be used to develop large project
Current state : Many modules like
buffer
File
Http
Socket
can be imported using CommonJSImplementation:
module
is is glolal object in Node.js, have propertyexports
1
module = { exports: {} }
Before we step into the details, let’s see before CommonJS, how to use import function. The public functions are exposed while the private properties and methods are encapsulated
1
2
3
4
5
6
7
8var revealingModule = (function () {
var privateVar = "Ben Thomas";
function setNameFn( strName ) {
privateVar = strName;
}
return { setName: setNameFn, };
})();
revealingModule.setName( "Paul Adams" );When a module is exported, inside Node.js it will be wrapped as
IIFE
(Immediately Invoked Function Expression)1
2
3
4
5
6
7
8
9
10
11// 1. original code
var greet = function () { console.log('Hello World'); };
module.exports = greet;
// 2. Parsed code by Node.js
(function (exports, require, module, __filename, __dirname) { //add by node.js
var greet = function () { console.log('Hello World'); };
module.exports = greet;
}).apply(); //add by node.js
return module.exports;
How to use:
define module:
module.exports
orexports
[module.exports
===exports
]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* --- circle.js --- */
// 1st way
exports.area = (radius) => Math.pow(radius, 2) * 3.14;
exports.circunference = (radius) => 2 * radius * 3.14;
// 2nd way
const area = (radius) => Math.pow(radius, 2) * 3.14;
const circunference = (radius) => 2 * radius * 3.14;
module.exports = {area: area, circumference: circunference};
// 3rd way
module.exports = {
area: (radius) => Math.pow(radius, 2) * 3.14;
circunference: (radius) => 2 * radius * 3.14;
}use module everywhere:
1
2
3const circle = require('./circle');
circle.area(3)
circle.circunference(3)
AMD: (Asynchronous Module Definition)
Why need AMD?**
- Since Node.js are always running locally inside server, the
require
are instantly completed, i.e. synchronously - Browser need to download js files from server asynchorously. While one file want
require
another, the other one might still not downloaded => ERROR! - Thus, we need another mechnism able to import modules asynchronously : AMD comes in!
- Since Node.js are always running locally inside server, the
How to use it in browser?
[Good Article]: https://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/
use
define()
to declare the module dependency for each filedownload
require.js
&main.js
in same project folder as other js filesonly add one
<script>
tag to HTML is OK, as follows1
<script data-main="scripts/main" src="scripts/require.js"></script>
Web
Request Method
request.method
: GET, POST, PUT, DELETE1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function (req, res) {
switch (req.method)
case 'POST':
update(req, res);
break;
case 'DELETE':
remove(req, res);
break;
case 'PUT':
create(req, res);
break;
case 'GET':
default:
get(req, res);
}
}
Request URL
complete URL like the following: (Hash part will be discarded)
1
http://user:pass@host.com:8080/p/a/t/h?query=string#hash
pathname
=url.parse(req.url).pathname
routers in Node.js:
url:
/[controller]/[action]/a/b/c
handlers for different controllers & actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// routes different urls to their related handlers
var url = require('url')
function (req, res) {
var pathname = url.parse(req.url).pathname;
var paths = pathname.split('/');
var controller = paths[1] || 'index';
var action = paths[2] || 'index';
var args = paths.slice(3);
if (handles[controller] && handles[controller][action]) {
handles[controller][action].apply(null, [req, res].concat(args));
} else {
res.writeHead(500);
res.end('找不到响应控制器'); }
}
// handlers object
handles.index = {};
handles.index.index = function (req, res, foo, bar) {
res.writeHead(200);
res.end(foo);
};
Request Query String
query
=url.parse(req.url).query
url:
/domainname/path?foo=bar&baz=val
1
2
3
4
5
6
7
8
9
10//eg1. '/domainname/path?foo=bar&baz=val'
{
foo: 'bar',
baz: 'val'
}
//eg2. '/domainname/path?foo=bar&foo=val'
{
foo: ['bar', 'val'],
}
Cookie
workflow: [ key point: browser sends cookie for each request]
- server sends cookie to browser
- browser stores it in localstorage
- browser sends cookie every time making request
[For browser:] string:
Cookie: foo=bar; baz=val
=>req.header.cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// browser parse cookie
var parseCookie = function (cookie) {
var cookies = {};
if (!cookie) { return cookies; }
var list = cookie.split(';');
for (var i = 0; i < list.length; i++) {
var pair = list[i].split('=');
cookies[pair[0].trim()] = pair[1];
}
return cookies;
};
// browser adds cookie to `req` object
function (req, res) {
req.cookies = parseCookie(req.headers.cookie);
hande(req, res);
}
[For server:]
Format:
1
Set-Cookie: name=value; Path=/ ; Expires = Sun, 01/21/1987 09:01:01 GMT; Domain = .domain.com;
arguments:
name&value
: the useful stuffdomain
: cookie only sent to the domainpath
: cookie only sent when this domain/pathexpire
/maxAge
: when or how long expiresHttpOnly
: will make this cookie cannot be access bydocument.cookie
secure
: only sent when HTTPS; not sent when HTTP
Serialize Cookie [Server] : convert cookie to string
1
2
3
4
5
6
7
8
9
10var serialize = function (name, val, opt) {
var pairs = [name + '=' + encode(val)]; opt = opt || {};
if (opt.maxAge) pairs.push('Max-Age=' + opt.maxAge);
if (opt.domain) pairs.push('Domain=' + opt.domain);
if (opt.path) pairs.push('Path=' + opt.path);
if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
if (opt.httpOnly) pairs.push('HttpOnly');
if (opt.secure) pairs.push('Secure');
return pairs.join('; ');
};
Session
why need it: cookie with too much data will make delay in network transmission
Solution:
- store data on server side, give a key (sessionID) to cookie to refer its data
- set
expire
to its corresponding cookie
Cache response file [ has nothing to do with cookie ]
- Client:
If-Modified-Since
, Server:Last-Modified
- Client:
If-None-Match
, Server:ETag
- solve problem: even if file modified, but content didn’t modified
- compare the digest of target file
- Server:
Expires
/Cache-Control
- the first 2 ways still need to send request, and wait for response => waste time!
- This approach doesn’t need to send request if doesn’t
expires
- But there is a problem: the time are not the same on two sides,
max-age
ofcache-control
comes in
- how to update cache when server file updated within expired period?
- Problem: browser will use stale file, since it not expire
- We also update the URL, since Cache pairs with URL
- Client:
Authentication / Authorization
- basic authentication
- request with :
useranme
&password
- request with :
- OAuth2.0
- request with
access_token
- request with
- Bearer token: JWT
- request with
Json Web Token
- request with
- basic authentication
Form Data
- content-type:
application/x-www-form-urlencoded
(“foo=bar&baz=val”) - content-type:
application/json;
- content-type:
application/xml;
- content-type:
Simulate a Client(browser) & a Server
server implementation [send response as a server]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let http = require('http')
let fs = require('fs')
let url = require('url')
http.creatServer( function(req, res){
let pathname = url.parse(req.url).pathname
console.log("request for " + pathname + "received.")
fs.readFile(pathname.substr(1), function(err, data){
if(err){
console.log(err)
res.writeHead(404, {'Content-Type': 'text/html'})
}else{
res.writeHead(200, {'Content-Type': 'text/html'})
res.write(data.toString())
}
res.end()
})
}).listen(8080)
console.log("Server running at http://127.0.0.1:8080/")
client implementation [send request as a browser]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let http = require('http')
let options ={
host: 'localhost',
port: '8080',
path: '/index.html'
}
let callback = function(res){
let body = ''
res.on( 'data', function(data){
body += data
})
res.on( 'end', function(){
console.log(body)
})
}
let req = http.request(options, callback)
req.end()
XSS / CSRF
- XSS: cross site script
- inject script from unsafe input, and runs it to get cookie, ie sessionID
- [Solution]: convert to HTML entities:
&
will be&
- CSRF:
- don’t bother to get sessionID
- lure customer to another website, while he is in middle of session of some weak-protection Bank website
- the other website will submit a form to Bank website indicating transfer money
- Bank website server cannot tell from whom that request is. It still believe that is from its customer since he is in the middle of the session
- [Solution]:
- the Bank form add another random hidden input field
- each time receive request, check if the random value equal to sent one
- This way the form cannot be duplicate
Global variables
console
console.log('11')
process
process.exit(0)
process.nextTick(callback)
__filename
: absolute path + name for the current script being executed__dirname
: absolute path for the current script being executed
Util Objects
util
: make one object inherit from the other oneutil.inherits(constructor, superConstructor)
Versions
- semantic versioning
- consists of three numbers, separated by periods, such as 2.3.0
- the middle number has to be incremented, when new functionality is added
- the first number has to be incremented, when compatibility is broken, so that existing code that uses the package might not work with the new version,
- caret character (^)
- any version compatible with the given number may be installed
^2.3.0
would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed