title | comments | date | type | categories | tags |
---|---|---|---|---|---|
Node.js |
true |
2018-06-01 09:24:07 -0700 |
categories |
Full stack |
Node.js |
-
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
-
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
-
- 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
- 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
-
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 CommonJS -
Implementation:
-
module
is is glolal object in Node.js, have propertyexports
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
var 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. 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
]/* --- 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:
const circle = require('./circle'); circle.area(3) circle.circunference(3)
-
-
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 file -
download
require.js
&main.js
in same project folder as other js files -
only add one
<script>
tag to HTML is OK, as follows-
<script data-main="scripts/main" src="scripts/require.js"></script>
-
-
-
Request Method
-
request.method
: GET, POST, PUT, DELETE-
function (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)
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
-
// 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
-
//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
-
// 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:
-
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
-
var 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]
-
let 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]
-
let 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: 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
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
: make one object inherit from the other oneutil.inherits(constructor, superConstructor)
- 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