---
# System prepended metadata

title: nodeJS

---

# nodeJS
## 特點
> - 優點
>   - 與 js 與法完全相同, 前端好上手
>   - 效能還行(chrome V8 引擎)
> - 缺點
>   - 庫不多
> - 用處
>   - 小型服務器, 中間層
>     - 中間層用處: 增加安全性, 提升性能(存放緩存等雜事), 前後端交互方便
>   - 工具: 測試, 構建(grunt, gulp, webPack...), 抓取
> - nodeJS 還是單線程, 單進程, 
>   不過 nodeJS 有使用非阻塞異步交互 ( 非堵塞IO ) 來增加性能
>   - 不搞多進程線程的原因在於 nodeJS 強項並非性能, 想要性能請用 java C,
>     nodeJS 強項在於簡單, 多進程線程太複雜了, 要用找別人( 真有個性== )
>     - java 擅長處理網絡, 安全性
>     - nodeJS 擅長處理中間層, 緩存, API
>     - python 擅長人工智能
>     - c 的性能完勝

## 運行
> - `$ node 檔名`


## 簡單導覽模塊

### http
> - 服務器有很多種, 例如 apache, nginx, iis, node,...
> - 瀏覽器也很多種, 例如 chrome, IE, ff, opera,...
> - 兩者都必須遵循 http 規範, 所以如果我要用 nodeJS 搭建服務器, 我也要遵守http
> - 但如果要所有人都完全了解 http, 那太麻煩了, 所以 node 有提供相應的API
>   <img src='https://i.imgur.com/X2Q2yBx.png' style='width:200px;'/>

> - http 模塊
>   - HTTP
>   - HTTP/2
>   - HTTPS
#### 最基本的串接服務器
```javascript=
const http = require('http'); // 引入模塊

let server = http.createServer((request, response)=> { // 創建服務器
  console.log('有人來了')
  response.write('haha'); // 輸出
  response.end(); // 結束輸出
});

server.listen(5566); // 監聽

/* shell

$ node 1.js
有人來了
有人來了
^C

*/
```
> - url 輸入 `http://localhost:5566/`
> - 畫面輸出 haha
> - Q. 為什麼輸出了兩次有人來了?
```javascript=
const http = require('http');

let server = http.createServer((request, response)=> {
  console.log(request.url); // 看看請求了啥~
  response.write('haha');
  response.end();
});

server.listen(5566);

/* shell
 
 $ node 1.js
 /             # 我 url 輸入的請求
 /favicon.ico  # favicon.ico 請求~ 一般新的瀏覽器都會自己求求看小圖標, 舊 IE 就不會
 ^C
 
 */
```
> #### `require(module)`
> - 引入模塊, 就是 python 的 `from xxx`

> #### `createServer(cb(request, response){})`
> - http 模塊裡的方法, 顧名思義, 創服務器, 返回一個服務器對象
> - 參數是一個 callback, 有人訪問就會調用
>   - 該 callback 有兩個形參 `request` `response`, 必須站在服務器的角度來看
>     - `request` : 請求, (輸入)
>     - `response` : 發送, (輸出)
>       - `response.write(content)`
>         - 發送的內容
>       - `response.end()`
>         - 告訴瀏覽器我送完了, 否則瀏覽器會一直傻等
>   - 這個 callback 再往內寫一層調用邏輯:
>     http 是應用層協議, 基於 tcp => socket
>     ```javascript
>     socket.on('data', ()=>{
>       req = 解析請求(head, body, 加密,...);
>       res = 另一個 socket
>       
>       cb(req, res);
>     })
>     ```


> #### `server.listen([port[, host[, backlog]]][, callback])`
> - 監聽
> - 一個服務器可能同時有多個程式在運行, 客戶端訪問服務器時, 
>   需要告訴服務器端口號, 否則誰知道你找誰, 而 `listen(port)` 
>   就是設置這個程序的端口號, 等客戶端來找你
>   - 設置端口號要避開重複的, 否則大家電話號碼都設110, 誰知道怎麼報警
>   - 默認端口是跟著協議的, 
>     不過當然你爽怎麼設就怎麼設, 不一定要跟默認走, 只是別人會找不到你而已
>     - http: 80
>     - ftp: 21
>     - mysql: 3306
>     - ...
> - host 指定網卡

> #### 問題:
> - 訪問 5566 端口, 不論指定哪個文件, 都是返回haha

#### 版本二
> - 拿到指定文件, 並返回相應內容
```javascript=
let http = require(`http`);

let server = http.createServer((req, res)=> {
  console.log(req.url);
  
  // 訪問不同, 就給相應的內容
  switch (req.url){
    case `/a`:
      res.write(`aa`);
      break
    case `/b`:
      res.write(`bb`);
      break
    default:
      res.write(`cc`);
  }
  res.end();
});
server.listen(5566);
```
> #### 問題
> - 不可能全部文件都寫在一個 js 上
> - 如果有一萬個文件, 我switch 要判斷一萬次? 
>   而且一萬的文件的內容全部寫在 每個相應 res.write() 裡面, 這js文件大小可想而知
> - 甚至是圖片檔 (二進制) 怎麼直接 write()?
>   - 延伸: JS 很難自己處理二進制, 他都直接交給底層接口處理

> #### 解決
> - 解藕
>   - 服務器就是純粹做轉發的工作 
>   - 檔案存在硬碟中
> - 檔案最好放在一個對外的資料夾, 接收到請求時, 一律往該資料夾找, 
>   因為不可能整個電腦的資料都公開給大家找, 
>   例如 xamp 就放在 /www 裡, xampp 就放在 /htdocs 裡

#### 版本三
```shell
$ tree
.
├── 1.js
└── www           # 創一個資料夾, 接到訪問就來這找
    └── 1.html
```
```javascript=

const http = require(`http`);
const fs = require(`fs`);

let server = http.createServer((req, res)=> {

  // 一收到訪問, 就往 www 找
    // 注意 req.url 本來最前面就會帶一個 / 了, 所以別在加
    // fs.readFile(`www/1.html`, cb)
  fs.readFile(`www${req.url}`, (err, data)=> {
    if (err) {
      console.log(0);
      res.writeHeader(404);   // 
      res.write(`Not Found`);
    } else {
      console.log(1);
      res.write(data);
    }
    res.end();
  });
  console.log(2);
  // res.end();      //=> 千萬別寫這, 一定掛
});
server.listen(5566);


/* shell

  2
  1

  重要: 
    - 先 2 再 1
    - 因為 readFile 是異步, 所以別把 end() 放在同步寫, 否則一定報錯
    - Error [ERR_STREAM_WRITE_AFTER_END]: write after end

*/
```
> #### writeHeader()
> - 改報文頭的
>   - 修改前
>     <img src='https://i.imgur.com/stmqfoi.png' style='width:300px;'>
>   - 修改後
>     <img src='https://i.imgur.com/tmFKTxM.png' style='width:300px;'>

### https
> - https 需要申請憑證(證書), 
>   免費證書: 在中國可以考慮阿里雲的, 在中國以外可以考慮[letsencrypt](https://letsencrypt.org/)


#### 客戶端
```javascript=
$.ajax({
    url: 'https://localhost:8080/'
})
```

#### 服務器
```javascript=
let https = require(`https`);
let fs = require(`fs`);


// https 需要這個證書
let options = {
    key: fs.readFileSync(`agent2-key.pem`),
    cert: fs.readFileSync(`agent2-cert.pem`)
}

https.createServer(options, (res, req)=>{}).listion(8080);
```

### Assertion Testing
`assert(Boolean 表達式, msg)`
> - 如果表達式不為 true, 報錯, 輸出 msg
> - 我 google 翻譯, 他叫斷言
```javascript=
const assert = require('assert');

console.log((function (x,y) {
	assert(arguments.length == 2, '請傳兩個參數'); 
    // 如果表達式執行結果為false, 輸出後面那句話
	return x+y
// })())   // AssertionError: 請傳兩個參數
})(11,22)) // 33
```

### Buffer & FileSystem
> #### 預備知識
> - 服務器幾乎99.9都是異步, 否則他服務你一個人就啥都不用幹了
> - 檔案操作非常慢, 所以也都是異步, 否則開個檔案電腦就卡住了
>   - 雖然 nodeJS 也有提供同步版(sync), 只是很少用
#### Buffer
> - 幫助 nodeJS 處理各種二進制的一種數據結構
> - 不過為了方便, 顯示時是以十六進制的方式顯示

#### fs.readFile()
`fs.readFile(path[, options], cb(err, data){})`
> - `path`: 檔案
> - `cb` 
>   - `err` : 錯誤訊息
>   - `data` : 正確數據
>     - 用 Buffer 數據處理
>     - 對於服務器來說, 都必須用同一套標準來處理文件, 
>       如果用文本處理而文件是圖檔 (二進制文件) 呢? 
>       所以通通都用 Buffer(二進制) 來處理

```shell
$ tree
.
├── 1.js
└── 1.txt
```
```txt
123
```
```javascript=
let fs = require('fs');

fs.readFile('1.txt', (err, data)=>{
  // console.log(err);
    // 正確時:
      // null    
    // 錯誤時:
      /*
         [Error: ENOENT: no such file or directory, open '2.txt'] {
          errno: -2,
          code: 'ENOENT',
          syscall: 'open',
          path: '2.txt'
         }
      */
    
  // console.log(data);
    // 正確時: <Buffer 31 32 33 0a> //=> Buffer!!! (註)
    // 錯誤時: undefined
  
  if (err) {
    console.log(err);
  } else {
    console.log(data.toString()); // 123 //=> 必須確認不是二進制數據才轉
  }
})
```
```javascript=
// 31 32 33 由來

// 字符編碼
'123'.charCodeAt(0);  // 49
'123'.charCodeAt(1);  // 50
'123'.charCodeAt(2);  // 51

// 49 59 51 的十六進制就是 31 32 33
// 所以打印顯示 Buffer 是顯示十六進制
```

#### fs.writeFile()
`fs.writeFile(file, data[, options], cb(err){})`
> - 寫入檔案
> - 如有該檔案, 會覆蓋檔案
```shell
$ tree
.
└── 1.js
```
```javascript=
let fs = require('fs');

fs.writeFile('1.txt', '123', err=> {
  if (err) {
    console.log(err);
  } else {
    console.log('success');
  }
})
```
```shell
$ node 1.js 
success

$ tree
.
├── 1.js
└── 1.txt

$ cat 1.txt
123
```

#### fs.appendFile()
`fs.appendFile(path, data[, options], callback)`
> - 基本上跟 `writeFile()` 用法一樣
> - 差別在於他是接著從後面繼續寫, `writeFile()` 是直接重寫, 覆蓋原檔

### Crypto
> - 簽名, 不是加密
> - 提供各種 hash 算法, 例如 md5, sha1,..
>   - 單向散列的: 只能把值加密, 不能把加密的解密
>   - Q. 那網路上那些解密怎麼做到的?
>     - 暴力破解的, 把加密結果都記起來, 解密時輸入加密後的值時, 並不是解密,  
>       而是做一個查詢的動作, 畢竟加密結果都是一樣的, 查一下就知道了
> - Q. 最安全的加密是?
>   - 如果有辦法做到每次加密都不同, 密鑰長度>內容長度, 那就很安全
>     問題是這流通困難, 所以不存在
>   - 目前相對安全的加密方法為: RSA
>   
> - Q. 前端加密方法?
>   - HTTPS
>   - 安裝插件(一般都是銀行之類的比較需要這麼麻煩, 其他HTTPS就行了)

#### crypto.createHash()
`crypto.createHash(method)`
> - 創建一個加密的對象, 參數傳加密的方法
> #### crypto.createHash().update()
> - `crypto.createHash(method).update(str)`
> - 新增要加密的字串
> #### crypto.createHash().digest()
> - `crypto.createHash(method).digest(顯示方法)`
> - 拿到加密後的值
> - 參數為顯示方法, 可選的, 沒傳默認 `<Buffer>` 顯示

```javascript=
const crypto = require('crypto');

// let obj = crypto.createHash(`md5`);
let obj = crypto.createHash(`sha1`);


// 可以一次寫
// obj.update('123456');

// 也可以慢慢更新
obj.update('123');
obj.update('4');
obj.update('56');

console.log(obj.digest()); 
  // <Buffer e1 0a dc 39 49 ba 59 ab be 56 e0 57 f2 0f 88 3e>
  // Buffer 數據類型
console.log(obj.digest(`hex`));
  // e10adc3949ba59abbe56e057f20f883e //=> 轉 16 進制顯示
```
#### 雙層加密
> - 既然他都把值記下來了, 那雙層加密就是把加密後的值再加密一次
> - 如果數據庫夠大, 可能重複加密很多次還是會被破
> - 可以打亂原本的重複加密規則, 例如加密後拼接一組字串(私鑰),再拿去加密
>   如果沒人知道你的這組字串, 那就很難破解
>   這也是為什麼忘記密碼都要創新的, 因為存在數據庫的是雙層加密又混私鑰的字串, 還原你的密碼太麻煩了, 叫你弄新的比較快
```javascript=
const crypto = require('crypto');


function md5(str) {
  let obj = crypto.createHash(`md5`);
  obj.update(str);
  return obj.digest(`hex`);
}

console.log(md5(md5(`123456`))); 
  // 14e1b600b1fd579f47433b88e8d85291 
  //=> 基本上即使是雙層, 還是有可能被紀錄而查到
  //=> 解決辦法就是多一層自己知道的字串, 如果這串沒有被人知道, 那就安全

console.log(md5(md5(`123456`)+`apple5566nodie`));
  // 97cb9c4848c8b5b7d4ddb921c6e9f400 
  //=> 這個就很難解了
```
> <img src='https://i.imgur.com/JMOzsbi.png' style='width: 300px'/> <img src='https://i.imgur.com/IdC4Rpe.png' style='width: 300px'/>



### OS
```javascript=
let os = require('os');
console.log(os.cpus());
```
```shell
$ node 1.js
[{...},{...},{...},{...}]

$ # 我的電腦四核的, 所以有四組
```

### Path
```javascript=
let path = require('path');

let p = '/aaa/bbb/ccc/d.jpg'

console.log(path.dirname(p));  // /aaa/bbb/ccc
console.log(path.basename(p)); // d.jpg
console.log(path.extname(p));  // .jpg
```
```javascript=
const path = require('path');


// 組路徑
// 這兩個組起來都一樣
console.log(path.join(__dirname, './a/c'));
    // /Users/.../01_webpack/a/c

console.log(path.resolve('./a/c'))
    // /Users/.../01_webpack/a/c

```



### Events
> - 後端沒有前端那些事件, 例如點擊啦之類的
> - 但後端有後端的對列, 例如事件到達觸發
> - 最初為了解決異步問題, 但現在異步漸漸被 async/await 取代
> - 與一般函數最大區別在於Events 可以解藕, 
>   因為Events 是以事件名作為依據而非函數, 而寫完就函數就綁死在那了, 無法輕易變動, 不過這在大項目才比較有感覺, 菜雞如我無法感受

#### EventEmitter
> - 事件發射器
> - 用 new 來實例
> - Events 模塊最常用的就是這個而已

#### on
`emit(事件名,  cb)`
> - 監聽, 接收
> - 事件名隨便取

#### emit
`emit(事件名, 調用cb的實參)`
> - 發送, 觸發

```javascript=
const Event = require('events').EventEmitter;


let eve = new Event();

// 監聽
eve.on('apple', (x,y)=> {
  console.log(x,y);
})
eve.on('apple', (a, b)=>{
    console.log(a+b);
})

// 觸發
eve.emit('apple', 1,2); 
/* 
 * 1 2
 * 3
*/


// 與一般function 的差別?
  // Event 可以解藕, 這個 apple 變量已經綁著這個 function 了
  // 上面那個Event實例的監聽可以隨便綁各種函數，想怎麼綁就怎麼綁
function apple(x,y) {
  console.log(x,y);
}

apple(1,2); // 1 2
```


### Query String
> - 查詢字串
> - url 後面那個參數就是queryString
>   https://tw.search.yahoo.com/search?fr=yfp-search-sb&p=js 的 fr=yfp-search-sb&p=js
```javascript=
let qs = require(`querystring`);
let obj = qs.parse(`fr=yfp-search-sb&p=js`);
console.log(obj); 
  // [Object: null prototype] { fr: 'yfp-search-sb', p: 'js' }
```


### url
> - 就整個 url

#### url.parse()
`url.parse(url, Boolean)`
> - `Boolean` : 
>   - 如果 true, 表示 query 也要解析, false 就表示不用
>   - 預設為 false
```javascript=
let u = require(`url`);
let obj = u.parse(`https://tw.search.yahoo.com/search?fr=yfp-search-sb&p=js`, true);
console.log(obj);

/* parse 二參為 false

Url {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'tw.search.yahoo.com',
  port: null,
  hostname: 'tw.search.yahoo.com',
  hash: null,
  search: '?fr=yfp-search-sb&p=js',
  query: 'fr=yfp-search-sb&p=js',
  pathname: '/search',
  path: '/search?fr=yfp-search-sb&p=js',
  href: 'https://tw.search.yahoo.com/search?fr=yfp-search-sb&p=js'
  }

*/ 

/* parse 二參為 true
 
 Url {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'tw.search.yahoo.com',
  port: null,
  hostname: 'tw.search.yahoo.com',
  hash: null,
  search: '?fr=yfp-search-sb&p=js',
  query: [Object: null prototype] { fr: 'yfp-search-sb', p: 'js' },
  pathname: '/search',
  path: '/search?fr=yfp-search-sb&p=js',
  href: 'https://tw.search.yahoo.com/search?fr=yfp-search-sb&p=js'
  }
 
*/
```


### DNS
<img src='https://i.imgur.com/VOGjcCn.jpg' style='width: 300px;'>

```shell
$ ping google.com
PING google.com (172.217.24.14): 56 data bytes
64 bytes from 172.217.24.14: icmp_seq=0 ttl=51 time=28.735 ms
...

$ # google 的 172.217.24.14 => 域名
```
#### dns.resolve()
`dns.resolve(url, cb)`
> - 解析指定網址的 dns
> - 網路操作慢, 所以經常使用cb
>   - cb 兩參數, 錯誤訊息(err)與域名(res)

```javascript=
let d = require(`dns`);

// d.resolve(`google.com`, (err, res)=>{
d.resolve(`5566nodiehaha.com`, (err, res)=>{
  if (err) {
    console.log(err);
  } else {
    console.log(res);
  }
})

// [ '172.217.24.14' ]
// Error: queryA ENOTFOUND 5566nodiehaha.com

// 有些網站有好幾個域名, 例如百度
  // [ '39.156.69.79', '220.181.38.148' ]
```

### 其他常見模塊
> - C
>   - C++ Addons
>   - C/C++ Addons with N-API
>   - 用 c 寫一些插件給 nodeJS 使用
>   - 移植可能會有問題, 因為各個平台的 c 語言編譯器可能不同
> - 多進程
>   - Child Processes
>   - Cluster
>   - Process
> - 網路
>   - tcp => nodeJS 使用 Net 模塊來使用 tcp
>   - udp => nodeJS 使用 UDP/Datagram 模塊 來使用 udp
> - 域名
>   - DNS
>   - Domain
> - 流操作 stream
>   - 連續數據都是流: 影音流, 網路流, 文件流, 語音流
> - TLS/[SSL](https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A)
>   - 加密, 安全
>   - https 就是 http 基於 SSL
>   - 現在常用的加密協議都是依據 SSL 實現的
> - ZLIB: gz壓縮 




## 數據交互
> - Web 服務器主要工作
>   - 返回文件
>   - 數據交互(GET POST)
>     - GET
>       - 數據在 head 裡 (url裡)
>       - <=32k
>     - POST
>       - 數據在 body 裡
>       - <= 1G
>   - 數據庫

### 表單
> #### GET
> - 寫一個表單, 開啟服務器, 表單 action 記得前面要傳對端口
```shell
$ tree
.
├── 1.js
└── www
    └── 1.html
```
```htmlmixed=
<head>
  <meta charset='utf-8'>
</head>
<body>
  <form action='http://localhost:5566/apple' method='GET'>
    <input name='a'>
    <input name='b'>
    <input type='submit'>
  </form>
</body>
```
```javascript=
let http = require(`http`);
let url = require(`url`);

let server = http.createServer((req, res)=>{
  // console.log(req.url);
    // /apple?a=5566&b=nodie //=> GET 數據就在 url 上
    
  // console.log(url.parse(req.url));
    /*
       Url {
         ...,
         query: 'a=5566&b=nodie',
         pathname: '/apple', 
         ...
       }
    */
    
    
  let {query, pathname} = url.parse(req.url, true);
  console.log(query);    
  console.log(pathname);
  res.end();
})

server.listen(5566);

// 輸入表單後 chorme 刷新的網址  
  // http://localhost:5566/apple?a=5566&b=nodie

/* shell

  [Object: null prototype] { a: '5566', b: 'nodie' }
  /apple
  
*/
```

> #### POST
> - 大部分的大數據都是切成小包小包的
>   - 有些服務器一次只跟一個客戶端連線, 
>     如果客戶端需要傳100G的檔案, 那這個服務器就都不用服務別人了
>   - 容錯率提高, 如果傳錯了, 就只要補傳那個錯的小包即可
> - 也就是說後端收到數據的時候不會一次收到, 而是小包小包收
 
> #### Post 筆記
> - `http.createServer(cb(req,res) {})`
>   - `req.on('data', cb(data){})`
>     - 每次收到數據時觸發
>    - 收到的數據可以不用特別驗證, 因為 http 是基於 tcp
>   - `req.on('end', cb(){})`
>     - 收完所有數據後觸發
```htmlmixed=
<body>
  <form action='http://localhost:5566/apple' method='POST'>
    <input name='a'>
    <input name='b'>
    <input type='submit'>
  </form>
</body>
```
```javascript=
let http = require(`http`);
let querystring = require(`querystring`)

let server = http.createServer((req, res)=>{
  let str = '';
  
  // NodeJS 的事件跟瀏覽器的事件當然不一樣, 後端不用點擊交互等
  // on 就跟 addEventListener 一樣是註冊監聽事件用的
  
  // data 事件是每次收到數據都會觸發
    // 不用特別驗證這個data, 畢竟http是基於 tcp, 掉包機會比較小
  req.on(`data`, data=> {
    str+=data;
  })
  
  // 數據全部收完後觸發
  req.on(`end`, ()=>{
    console.log(str);
    let d = querystring.parse(str);
    console.log(d);
  })

  res.end();
})

// 輸入表單後 chrome 的 url => http://localhost:5566/apple
  // POST 數據本來就不在 url 裡

/* shell

a=5566&b=nodie
[Object: null prototype] { a: '5566', b: 'nodie' }

*/
```

### 接口
<img src='https://i.imgur.com/bqhxQu5.jpg' style='width: 400px;'>

> - 數據庫是後端最重要的資產, 不可能讓前端直接看到
>   所以必須要有通過一個第三方來通信, 這個街口可以用各式語言來完成
> - 接口與前端通信必須討論一套規則, 前後端要傳什麼格式給對方等問題, 例如
>   ```txt
>   // 註冊
>   前端 => 後端 /reg?user=xxx&pass=ooo
>   後端 => 前端 {"error": 1, "msg": "xxxx"} 或 
>               {"error": 0, "msg": "success"}
>   
>   // 登入
>   前端 => 後端 /login?user=xxx&pass=ooo
>   後端 => 前端 {"error": 1, "msg": "xxxx"} 或 
>               {"error": 0, "msg": "success"}
>   ```
> - 安全性:
>   - 一切來自前端的都必須校驗
>   - 前後端都必須對數據校驗
>     - 前端為了用戶體驗, 避免格式不符就送, 浪費網路
>     - 後端為了安全性


```shell
$ tree
.
├── 1.js
└── www
    ├── 1.html
    └── 2.html
```


`$ vi 1.js`
```javascript=
// 服務器

let http = require(`http`);
let querystring = require(`querystring`)
let url = require(`url`);
let fs = require(`fs`);

let users = {} // 測試用而臨時存放數據的地方

let server = http.createServer((req, res)=>{

    // 獲取URL數據
    let {query, pathname} = url.parse(req.url, true);

    // 獲取 body 數據
    let str = '';
    req.on(`data`, data=> {
        str+=data;
    })
    
    req.on(`end`, ()=>{
        // 解析 body 數據
        let post = querystring.parse(str);
        // console.log(query, pathname, post);

        // 根據上面假設的討論, 註冊與登入時, 傳過來的參數會有 user 跟 pass
        let {user, pass} = query;
        
        // 判斷客戶端想要幹嘛
        switch (pathname) {
        
            // 想註冊, 驗證數據有沒有問題
                // 必須假設前端送來的資料都有問題
            case '/reg':
                if (!user) {
                    // 根據上面假設討論的回傳數據
                    res.write(`{"error": 1, "msg": "Give Me UserName, Please"}`)
                } else if (!pass) {
                    res.write(`{"error": 1, "msg": "Give Me Password, Please"}`)
                } else if (!/^\w{6,10}$/.test(user)){
                    res.write(`{"error": 1, "msg": "UserName Not as required"}`)
                } else if (/['"]/.test(pass)){
                    res.write(`{"error": 1, "msg": "PassName Not as required"}`)
                } else if (users[user]) {
                    res.write(`{"error": 1, "msg": "UserName has been registered"}`)
                } else {
                    users[user] = pass;
                    res.write(`{"error": 0, "msg": "Success"}`)
                }
                res.end();

                break;
                
            // 想登入, 驗證數據
            case '/login':
                if (!user) {
                    res.write(`{"error": 1, "msg": "Give Me UserName, Please"}`)
                } else if (!pass) {
                    res.write(`{"error": 1, "msg": "Give Me Password, Please"}`)
                } else if (!/^\w{6,10}$/.test(user)){
                    res.write(`{"error": 1, "msg": "UserName Not as required"}`)
                } else if (/['"]/.test(pass)){
                    res.write(`{"error": 1, "msg": "PassName Not as required"}`)
                } else if (!users[user]) {
                    res.write(`{"error": 1, "msg": "UserName has not been registered"}`)
                } else if (users[user] != pass) {
                    res.write(`{"error": 1, "msg": "UserName or Password is incorrect"}`)
                } else {
                    res.write(`{"error": 0, "msg": "Success"}`)
                }
                res.end();
                break;
                
            // 其他, 表示要拿資料
            default:
                // 送到對外的資料夾找
                fs.readFile(`www${pathname}`, (err, data)=>{
                    if (err) {
                        res.writeHeader(404);
                        res.write(`not Found`);
                    } else {
                        res.write(data);
                    }
                    res.end();
                })
        }
    })
})

server.listen(5566);
```

```htmlmixed=
<!-- 表單測試 -->
<form action='http://localhost:5566/reg' method='GET'>
<!-- <form action='http://localhost:5566/login' method='GET'> -->
    <input name='user'>
    <input name='pass'>
    <input type='submit'>
</form>
```


```htmlmixed=
<!-- AJAX 測試 -->
<head>
    <meta charset='utf-8'>
    <script>
    window.onload = function () {
        let btn1 = document.querySelector(`#btn1`);
        let btn2 = document.querySelector(`#btn2`);

        btn1.onclick = function () {
            let u = document.querySelector(`#user`).value;
            let p = document.querySelector(`#pass`).value;

            // 1. 創建 XHR 對象
            let xhr = new XMLHttpRequest();
            
            // 2. 寫資料
            xhr.open(`GET`, `/reg?user=${u}&pass=${p}`, true);
            
            // 3. 傳送
            xhr.send();
            
            
            xhr.onreadystatechange = function () {
            
                // 傳完之後
                if (xhr.readyState === 4) {
                    // console.log(1)
                    
                    // 4. 判斷是否『 通信 』成功
                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                        // console.log(xhr.responseText);
                        
                        // 5. 判斷 註冊/登入 成功與否
                            // 5.1 處理回傳的字串
                        let data = JSON.parse(xhr.responseText);
                        
                        // 5.2 依據假設回傳的數據
                        //     會有 error 的對象, 1 代表數據有問題, 
                        
                        // 1 Boolean 為 ture, 進去表示數據有問題
                        if (data.error) { 
                            console.log(`Error: `+ data.msg);
                        } else {
                            console.log(`Success: ` + data.msg);
                        }
                    } else {
                        console.log(`error`);
                    }
                }
            }
        }
        
        // 登入一模一樣, 差別在 url 變成 login 而已
        btn2.onclick = function () {
            let u = document.querySelector(`#user`).value;
            let p = document.querySelector(`#pass`).value;



            let xhr = new XMLHttpRequest();
            xhr.open(`GET`, `/login?user=${u}&pass=${p}`, true);
            xhr.send();
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                        console.log(xhr.responseText);
                        let data = JSON.parse(xhr.responseText);
                        if (data.error) {
                            console.log(`Error: `+ data.msg);
                        } else {
                            console.log(`Success: ` + data.msg);
                        }
                    } else {
                        console.log(`error`);
                    }
                }
            }
        }
    }
    </script>
</head>
<body>
    <input id='user' name='user'><br/>
    <input id='pass' name='pass'><br/>
    <input type='button' value='註冊' id='btn1'>
    <input type='button' value='登入' id='btn2'>
</body>
```

### 接口風格
> - 定義接口可以隨便定, 不過很多人會依照某些風格來制定
> - 叫流行的例如 [RESTful](https://zh.wikipedia.org/wiki/表现层状态转换)




## Post 的 multipart 上傳
> - 表單的三種 POST
>   - application/x-www-form-urlencoded : 一堆 &&&
>   - multipart/form-data; boundary=----WebKitFormBoundaryWBQBnwHG6EAMjPQA
>     - `enctype='multipart/form-data'`
>   - text/plain : 沒啥用

#### multipart/form-data

> - 上面用 str 拼接的問題
>   1. 如果遇到不是用 urlencoded 會有問題
>   2. 如果遇到傳二進制檔案, 更有問題
>   - 因為multipart/form-data傳進來的數據都是二進制, 直接轉string圖檔直接亂碼

```shell
$ tree
.
├── 1.js
├── 1.txt
└── post.html
```
```txt
123456789
```

```htmlmixed=
<!-- <form action='http://localhost:5566/upload' method='POST'> -->
<form action='http://localhost:5566/upload' method='POST' enctype='multipart/form-data'>
    <input name='a' />
    <input name='b' />
    <input type='file' name='fl'/>
    <input type='submit'/>
</form>
```
```javascript=
let http = require(`http`);


let s = new http.createServer((req, res)=> {
    console.log(req.url);
    let str = '';  // 用字符串拼接, 
                   
    req.on(`data`, data=>{
        // console.log(`data: ${data}`);
        str += data;
    })

    req.on(`end`, ()=> {
        console.log(str);
        res.end()
    })

});

s.listen(5566);
```

```shell
$ # 1. 用 urlencoded 傳簡單的文檔
/upload
a=aaa&b=bbb&fl=1.txt         # => 1. 沒資料, 只有名字, 有名字沒啥用啊

$ # 2. 用 urlencoded 傳圖檔
/upload
a=aaa&b=bbb&fl=photo.jpeg      # => 2. 沒資料, 只有名字, 有名字沒啥用啊

$ # 3. 用 multipart/form-data 傳簡單的文檔
/upload
------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ
Content-Disposition: form-data; name="a"

aaa
------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ
Content-Disposition: form-data; name="b"

bbb
------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ
Content-Disposition: form-data; name="fl"; filename="1.txt"
Content-Type: text/plain

123456789             # => 3. 有資料, 可是多很多HTTP 規定的東西

------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ--

$ # 4. 用 multipart/form-data 傳圖檔
/upload
------WebKitFormBoundary9UAJnYPs6PKANNWJ
Content-Disposition: form-data; name="a"

aaa
------WebKitFormBoundary9UAJnYPs6PKANNWJ
Content-Disposition: form-data; name="b"

bbb
------WebKitFormBoundary9UAJnYPs6PKANNWJ
Content-Disposition: form-data; name="fl"; filename="photo.jpeg"
Content-Type: image/jpeg

����JFIF���	( %!1!%)+...383-7(-.+

....一堆亂碼, 略         # 4. 直接發生慘案

------WebKitFormBoundary9UAJnYPs6PKANNWJ--
```


### Buffer
> - nodeJS 用來專門存放二進制數據的地方, 類似陣列的地方
> - https://www.runoob.com/nodejs/nodejs-buffer.html
> - ps. [官方](https://nodejs.org/zh-cn/docs/guides/buffer-constructor-deprecation/)說不要用 `new Buffer()`, 要用 `Buffer.alloc()` 或 `Buffer.form()` 來代替
>   - `new Buffer(number)` => `Buffer.alloc(number)`
>   - `new Buffer(string[, encoding])` => `Buffer.from(string[, encoding])`
>   - `new Buffer(...arguments)` => `Buffer.from(...arguments)`

#### Buffer.concat([])
`Class Method: Buffer.concat(list[, totalLength])`
> - 合併 Buffer 數據
> - 參數是一個 list
```javascript=
let a = new Buffer(`a`); // Buffer.from(`a`);
let b = new Buffer(`b`);

let c = Buffer.concat([a,b]);
console.log(a,b,c); // <Buffer 61> <Buffer 62> <Buffer 61 62>
```


> - 解析 multipart/form-data 數據
```txt
# 原本數據

------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ   # 分割符
Content-Disposition: form-data; name="a"   # 數據描述

aaa                                        # 數據
------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ   # 分割符
Content-Disposition: form-data; name="b"   # 數據描述

bbb                                        # 數據值
------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ   # 分割符
Content-Disposition: form-data; name="fl"; filename="1.txt"   # 數據描述
Content-Type: text/plain           # 數據描述

123456789                          # 文件內容

------WebKitFormBoundaryeQhHBmWUW3ZBgVrZ--   # 分割符


# 改造1.   ---------------------------------------------

<分割符>
<數據描述>

<數據>
<分割符>
<數據描述>

<數據>
<分割符>
<數據描述>
<數據描述>

<文件內容>
<分割符>--



# 改造2.  ---------------------------------------------
  # 事實上每行尾都有 /r/n 來換行

<分割符>\r\n
<數據描述>\r\n
\r\n
<數據>\r\n
<分割符>\r\n
<數據描述>\r\n
\r\n
<數據>\r\n
<分割符>\r\n
<數據描述>\r\n
<數據描述>\r\n
\r\n
<文件內容>\r\n
<分割符>--\r\n


# 改造3.   ---------------------------------------------


<分割符>\r\n<數據描述>\r\n\r\n<數據>\r\n
<分割符>\r\n<數據描述>\r\n\r\n<數據>\r\n
<分割符>\r\n<數據描述>\r\n<數據描述>\r\n\r\n<文件內容>\r\n
<分割符>--\r\n
```
```
# 根據 Buffer 的合併數據 Buffer.concat 的方法, 
  想用 Buffer 處理, 使用 Array 可能會很方便

# 1. 用 <分割符> 來切割

[
    空, 
    \r\n<數據描述>\r\n\r\n<數據>\r\n, 
    \r\n<數據描述>\r\n\r\n<數據>\r\n, 
    \r\n<數據描述>\r\n<數據描述>\r\n\r\n<文件內容>\r\n, 
    --\r\n
]

  # 特點: 
  # - 文件數據有兩項描述, 與普通數據有別
  # - 數據內容的開始在『 第一個 』 \r\n 後面
  #   -  但不是所有 \r\n 數據後面都是數據的開始
  #   -  如果我文件空兩行, 數據就兩個 \r\n 了
         1
    
    
         2  #=> 1\r\n\r\n2
      
         
  # - 頭尾兩項看起來沒啥用到
  # - 每項頭尾都有 \r\n 包著

    
      
    
# 2. 清掉沒用到的東西

[
    <數據描述>\r\n\r\n<數據>, 
    <數據描述>\r\n\r\n<數據>, 
    <數據描述>\r\n<數據描述>\r\n\r\n<文件內容>, 
]

# 3. 用 \r\n\r\n 把數據切開, 會得到兩種陣列
     
     - 普通數據 [<數據描述>, <數據>]
     - 文件數據 [<數據描述>\r\n<數據描述>, <文件內容>]
```


#### `Buffer.prototype.indexOf()`
`buf.indexOf(value[, byteOffset][, encoding])`
> - 查找
> - `return` : 
>    - `value in buf` : 找到的位置
>    - `-1`  : 找不到
> - `value` : `<str>` | `<Buffer>` | `<int>` | `<Uint8Array>`
> - `byteOffset` : `<int>`, 起始索引
```javascript=
let a = Buffer.from(`aa-o-bb-o-cc`);


// let b = a.indexOf(Buffer.from(`-o-`));
let b = a.indexOf(`-o-`);
    // 直接給字串也行, 因為他會自己轉 Buffer

console.log(b); // 2
```

#### `Buffer.prototype.slice(start, end-1)`
> - 擷取
```javascript=
let a = Buffer.from(`aa-o-bb-o-cc`);
let c = a.slice(3,5);  // 擷取 [3] 跟 [4]


console.log(a); // <Buffer 61 61 2d 6f 2d 62 62 2d 6f 2d 63 63>
console.log(c); // <Buffer 6f 2d>
console.log(c.toString()); // o-
```


#### `Buffer.prototype.split() ?`
```javascript=
let a = Buffer.from(`aa-o-bb-o-cc`);
let c = a.splic(`-o-`);
    // a.splic is not a function
```
> - 還沒有這個方法, 官方文檔都沒找到, 只能自己寫
```javascript=
let a = Buffer.from(`aa-o-bb-o-cc`);

Buffer.prototype.split = Buffer.prototype.split || function (x) {

    let a = [];
    let n = 0;
    let cur = 0;

    // 如果有找到 n 就存找到的位置
    while ((n = this.indexOf(x, cur)) !== -1) {
        a.push(this.slice(cur, n)) // 把找到前的東西切出來丟進陣列
        cur = n + x.length;  // 指針要改成分割元素的後面那個
    }

    a.push(this.slice(cur)); // 最後有一段會切不到, 指針切到底就是最後一段
    return a
};



let c = a.split(`-o-`);
console.log(c); // [ <Buffer 61 61>, <Buffer 62 62>, <Buffer 63 63> ]
console.log(c.map(buffer=>buffer.toString())); // [ 'aa', 'bb', 'cc' ]
```


### 處理 multipart/form-data 數據

#### `req.headers`
> - 獲取傳進來的 headers

```javascript=
const http = require(`http`);

let s = new http.createServer((req, res)=> {
    console.log(req.url);       // 看客戶端 url 傳啥
    console.log(req.headers);   // 看客戶端表頭傳啥
    let arr = [];
    req.on(`data`, data=>{
        console.log(data.toString()); // 看客戶端數據(body)傳啥
        arr.push(data);
    })

    req.on(`end`, ()=> {
        let data = Buffer.concat(arr)
        res.end()
    })
});

s.listen(5566);
```
```shell
$ # 沒有傳資料時 #=> 沒有 Content-type
/upload
{
  host: 'localhost:5566',
  connection: 'keep-alive',
  'upgrade-insecure-requests': '1',
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
  'sec-fetch-user': '?1',
  accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  'sec-fetch-site': 'none',
  'sec-fetch-mode': 'navigate',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7'
}

$ # 傳 urlencoded
    # 'content-type': 'application/x-www-form-urlencoded'
/upload
{
  host: 'localhost:5566',
  connection: 'keep-alive',
  'content-length': '20',
  'cache-control': 'max-age=0',
  'upgrade-insecure-requests': '1',
  origin: 'null',
  'content-type': 'application/x-www-form-urlencoded',  #=> HERE
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
  'sec-fetch-user': '?1',
  accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  'sec-fetch-site': 'cross-site',
  'sec-fetch-mode': 'navigate',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7'
}
a=aaa&b=bbb&fl=1.txt

$ # 傳 multipart/form-data
    # 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryEfBUaWBDtW6ESf6K'
    # 他會傳 ---WebKitFormBoundaryEfBUaWBDtW6ESf6K' 分隔福進來
/upload
{
  host: 'localhost:5566',
  connection: 'keep-alive',
  'content-length': '369',
  'cache-control': 'max-age=0',
  'upgrade-insecure-requests': '1',
  origin: 'null',
  'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryEfBUaWBDtW6ESf6K',   #=> HERE
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
  'sec-fetch-user': '?1',
  accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  'sec-fetch-site': 'cross-site',
  'sec-fetch-mode': 'navigate',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7'
}
------WebKitFormBoundaryEfBUaWBDtW6ESf6K
Content-Disposition: form-data; name="a"

aaa
------WebKitFormBoundaryEfBUaWBDtW6ESf6K
Content-Disposition: form-data; name="b"

bbb
------WebKitFormBoundaryEfBUaWBDtW6ESf6K
Content-Disposition: form-data; name="fl"; filename="1.txt"
Content-Type: text/plain

123456789

------WebKitFormBoundaryEfBUaWBDtW6ESf6K--

```

### 整理思緒
> #### 觀察 multipart 的 content-type
```
// Header
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryEfBUaWBDtW6ESf6K',


// Content
------WebKitFormBoundaryEfBUaWBDtW6ESf6K
...
------WebKitFormBoundaryEfBUaWBDtW6ESf6K--
```
> - multipart 傳來的 header 會有 `content-type` 跟使用的分隔符
> - `EfBUaWBDtW6ESf6K` 是隨機的, 
>   避免文檔內容寫到 ------WebKitFormBoundary 而造成混淆
> - Header 的 boundary 只有四個 ----
>   - 內容的前面兩個是跟最後兩個 \-\- 對應的

> #### 觀察兩種數據陣列
```
// 普通數據
Content-Disposition: form-data; name="a"

// 文件數據
Content-Disposition: form-data; name="fl"; filename="1.txt"\r\n
Content-Type: text/plain
```
> - 描述用 `; ` 分隔
> - 普通數據的描述只有一行
> - 文件數據的描述有多行(\r\n)

> #### 處理數據
> - 邏輯就是假設全部都是二進制, 把數據全部丟到 Buffer 裡, 
>   然後利用上面那些規則挑出真正的二進制, 並且利用那些規則來拿到相應的數據
> - 去 headers 拿邊界
> - 用邊界切數據
> - 拿掉前後項
> - 拿到前後 \r\n
> - 用 \r\n\r\n 切割數據, 拿到數據描述與數據
> - 利用普通數據的數據描述只有一行 ( 沒有\r\n ) 來判斷哪些是普通數據
>   普通數據的數據描述可以直接跟數據內容組起來
> - 二進制數據的數據描述也可以切一切取出來
> - 二進制數據的數據內容直接丟給 `fs` 來處理

```shell
$ tree
.
├── 1.js            # 服務器
├── 1.txt           # 想要上傳的文件
├── common.js       # 放剛剛寫的 split
├── post.html       # 表單上傳文件
└── upload          # 要把上傳的文件存放的地方
    └── 1.txt       # 執行後成功存放的文件
```

```javascript=
Buffer.prototype.split = Buffer.prototype.split || function (x) {

  let a = [];
  let n = 0;
  let cur = 0;


  while ((n = this.indexOf(x, cur)) !== -1) {
      a.push(this.slice(cur, n))
      cur = n + x.length;
  }

  a.push(this.slice(cur));
  return a
};
```

```javascript=
const http = require(`http`);
const common = require(`./common.js`)
const fs = require(`fs`)


let s = new http.createServer((req, res)=> {

    // 1. 把數據全部存到 arr 裡, 不要輕易轉 str, 避免悲劇
    let arr = [];
    req.on(`data`, data=>{
        arr.push(data);
    })

    req.on(`end`, ()=> {
    
        // 1.1 假設所有數據都是二進制, 把數據全部丟進 Buffer 裡
        let data = Buffer.concat(arr)

        // 之後整理完數據要存放用的
        let post = {};
        let files = {};

        // 2. 拿到邊界
        if (req.headers[`content-type`]) {
            let ct = req.headers[`content-type`];
            // console.log(ct);	
              // multipart/form-data; boundary=----WebKitFormBoundaryamBq0Ond7btQNd5P
              
            // headers 數據前面只有四個 -, 補兩個進來
            let Boundry = `--` + ct.split(`; `)[1].split(`=`)[1];
            // console.log(Boundry);
              // ------WebKitFormBoundaryamBq0Ond7btQNd5P

            // 3. 用邊界分割數據
            // console.log(data);
              // <Buffer 2d 2d 2d 2d 2d 2d ... 319 more bytes>
            
            let arr = data.split(Boundry) // 記得在原型寫一個 split
            // console.log(arr);
              /*
                 [
                    <Buffer >,
                    <Buffer 0d 0a 43 6f 6e 74 ... 1 more byte>,
                    <Buffer 0d 0a 43 6f 6e 74 ... 1 more byte>,
                    <Buffer 0d 0a 43 6f 6e 74 ... 53 more bytes>,
                    <Buffer 2d 2d 0d 0a>
                  ]
               
               */

            // 4. 拿掉前後項
            arr.shift();
            arr.pop();


            // 5. 拿掉前後 \r\n
            arr = arr.map(buffer=>buffer.slice(2,buffer.length-2));
            // console.log(arr.map(b=>b.toString()));
              /*
                 [
                  'Content-Disposition: form-data; name="a"\r\n\r\naaa',
                  'Content-Disposition: form-data; name="b"\r\n\r\nbbb',
                  'Content-Disposition: form-data; name="fl"; filename="1.txt"\r\n' +
                    'Content-Type: text/plain\r\n' +
                    '\r\n' +
                    '123456789\n'
                  ]
               */


            // 6. 用 \r\n\r\n 切割出數據描述與數據

            arr.forEach(b=>{
                let n = b.indexOf(`\r\n\r\n`);
                let discription = b.slice(0, n);
                let content = b.slice(n+4);

                // console.log(discription.toString(), `||` ,content.toString())
                  /*
                     Content-Disposition: form-data; name="a" || aaa
                     Content-Disposition: form-data; name="b" || bbb
                     Content-Disposition: form-data; name="fl"; filename="1.txt"
                     Content-Type: text/plain || 123456789
                   */

                // 7. 數據分類
                //    -  利用普通數據的描述只有一行的特性, 可以區分哪些不是二進制
                //    -  二進制文件的描述也可以直接轉, 只有內容需要保持 Buffer 
                
                // 7.1 描述直接轉字串
                discription = discription.toString();

                // 7.2 判斷是普通數據還是二進制數據
                if (discription.indexOf(`\r\n`)==-1) {
                    
                    // 普通數據隨便轉
                    content = content.toString();
                    // console.log(discription);
                        // Content-Disposition: form-data; name="a"
                        
                    // 8. 數據描述處理
                    //    -  數據描述是 `; ` 來區隔
                    
                    // 8.1 拿到每個 name
                    let name = discription.split(`; `)[1].split(`=`)[1];
                    // console.log(name) //=> "a"
                    
                    // 8.2 把雙引號拿掉
                    name = name.substring(1,name.length-1);
                    // console.log(name);
                    
                    
                    // 9. 組合數據
                    post[name] = content;
                } else {
                    // console.log(discription);
                    /*
                     Content-Disposition: form-data; name="fl"; filename="1.txt"
                     Content-Type: text/plain
                    */
                    
                    // 8.3 處理非普通數據描述
                    //     -  非普通數據的描述有多行, 利用 \r\n 可以切出每行
                    let [line1, line2] = discription.split(`\r\n`);
                    
                    // - 處理每個數據的相應
                    let [, name, filename] = line1.split(`; `);
                    name = name.split(`=`)[1];
                    name = name.substring(1, name.length-1);

                    filename = filename.split(`=`)[1];
                    filename = filename.substring(1, filename.length-1);


                    type = line2.split(`: `)[1];
                    // console.log(name, filename, type);
                      // fl 1.txt text/plain 
                    // console.log(content);
                      // <Buffer 31 32 33 34 35 36 37 38 39 0a>

                    // 10. 把二進制數據直接用 fs 處理
                    fs.writeFile(`upload/` + filename, content, err=>{})


                }

            })
            console.log(post);

        }

        res.end()
    })

});


s.listen(5566);
```


### uuid /guid
> - https://www.npmjs.com/package/uuid
> - 可以產生一串亂代碼


#### 安裝

> - `$ npm init`
> - `$ npm install uuid -D`

> - `-D == --save-dev`
>   - 寫入 devDependencies 
>   - 開發環境
>   - vs `-S == --save`
>     - 寫入 dependencies
>     - 生產環境
>     

> - 更新 npm `npm install -g npm` , MAC 可能會遇到權限問題, 前面加個 sudo 就搞定了

```shell
$ tree
.
├── 1.js
├── 1.txt
├── common.js
├── node_modules            
│  └── uuid               # 安裝的 uuid
│       ├── AUTHORS
│       ├── CHANGELOG.md
│       ├── LICENSE.md
│       ├── README.md
│       ├── bin
│       │   └── uuid
│       ├── index.js
│       ├── lib
│       │   ├── bytesToUuid.js
│       │   ├── md5-browser.js
│       │   ├── md5.js
│       │   ├── rng-browser.js
│       │   ├── rng.js
│       │   ├── sha1-browser.js
│       │   ├── sha1.js
│       │   └── v35.js
│       ├── package.json
│       ├── v1.js
│       ├── v3.js
│       ├── v4.js
│       └── v5.js
├── package-lock.json
├── package.json       # npm init
├── post.html
└── upload
    └── 1.txt
    
$ cat package.json 
{
  "name": "02",
  "version": "1.0.0",
  "description": "",
  "main": "1.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "uuid": "^3.3.3"      # uuid 環境
  }
}
```

#### 使用
```javascript=
const uuid = require(`uuid/v4`);

console.log(uuid());
console.log(uuid().replace(/\-/g, '')); // 清 - 可以省空間
```
```shell
$ node test.js
713bf640-cc45-4739-8a8f-ca7e0e13e823
03722a972409407aa5737c89366e2b05


$ node test.js
08de6563-977c-4ce1-9e58-f4dba31d0164
5498330f4ef34d4aaffecf5f30f5b673


$ # 每次執行都會產生一組亂碼
```


### 使用 uuid 當檔名
> - 隨機產生一串字串
> - uuid 是有協議的 https://tools.ietf.org/html/rfc4122
```javascript=
const uuid = require(`uuid/v4`); // 引入 uuid, v4 是第四版, 他還有 135版
...
...

if (discription.indexOf(`\r\n`)==-1) {
    let name = discription.split(`; `)[1].split(`=`)[1];
    name = name.substring(1,name.length-1);
    content = content.toString();
    post[name] = content;
} else {
    let [line1, line2] = discription.split(`\r\n`);
    let [, name, filename] = line1.split(`; `);
    name = name.split(`=`)[1];
    name = name.substring(1, name.length-1);

    filename = filename.split(`=`)[1];
    filename = filename.substring(1, filename.length-1);
    type = line2.split(`: `)[1];
    console.log(name, filename, type);


    // 使用 uuid 當檔名
    let path = `upload/${uuid().replace(/\-/g, '')}`

    fs.writeFile(path, content, err=>{
        if (err) {
            console.log('')
        } else {
            // 把數據存起來
            files[name] = {path, filename,type}; // 註
            console.log(files);
            console.log(`成功`);
        }
    })
}
```
```javascript=
// 註
let a = 1, b = 2
let o = {}

o[`haha`] = {a,b}

o // {haha: {…}}
    // haha: {a: 1, b: 2}
    // __proto__: Object
```
```shell
$ node 1.js
fl photo.jpeg image/jpeg
{
  fl: {
    path: 'upload/ec661cc44feb4f5cbdc72f280493635f',
    filename: 'photo.jpeg',
    type: 'image/jpeg'
  }
}
成功

$ tree

...
└── upload
    └── ec661cc44feb4f5cbdc72f280493635f
```
> - 副檔名改一下就可以用了
> - 副檔名是給人看的, 機器間交流是不需要副檔名的
> <img src='https://i.imgur.com/9qN4e1W.png' style='width: 300px;'/><img src='https://i.imgur.com/NVCH6zc.png' style='width: 300px;'/>

> - 服務器如何判斷文件類型
>   - 實用性很低, 基本上存起來就好了
>   - 判斷方法
>     - 簡易判斷: 副檔名
>     - 嚴謹判斷: 分析文件結構, 每個檔案的結構都拿出來做 if 判斷, 非常麻煩

> #### 問題
> - 等到所有數據都傳完才處理 => 在 end() 的 cb 處理
>   - 應該要收到一些解析一些
> - fs.readFile & fs.writeFile 的問題
```javascript
const http = require(`http`);
const fs = require(`fs`);

let s = http.createServer((req, res)=>{

  // readFile 會把所有數據都存到內存後才回調
  // - 佔用內存
  //   - 內存都快炸了, CPU 還是空的
  // - 資源利用不充分
  //   - 一下磁盤忙得要死, 網絡閒得要死
  //   - 一下網絡忙得要死, 磁盤閒得要死
  fs.readFile(`www${req.url}`, (err, data)=>{
    res.write(data);
  })
});
s.listen(5566);
```
> - 解決: 流 (讀一點數據, 發ㄧ點數據) 

## 流
> - 連續的數據都是流
> - 所有的流都有 `on`

> - 操作方法:
>   - 讀取流: `rs`  `req`
>   - 寫入流: `ws`  `res`
>   - 讀寫流: 壓縮 加密

> - 利用 `pipe` 來接通流 
>   - 想像流是兩個地點, pipe 是水管, 
>   - 水往低處流, 簡單說的話就是會自己調配

> - 生產者與消費者模型
>   <img src='https://i.imgur.com/CCVL0Nf.jpg' style='width: 250px'>
>   - 簡單的說, 當能夠寫時才會繼續讀, 
>   - 因為大部分都是讀比寫快
>   - 讀寫各自有一塊緩沖區(數組), 當寫的緩衝區滿了時, 會觸發事件, 告訴讀別再送了
>     此時讀的地方就會暫時先不讀
>   - 等到寫的陣列清空後, 會再告訴讀可以繼續了
>   - 不過當然也有可能寫比讀快, 那就不會觸發爆滿事件而一直讀一直寫

### 讀取流
> - `fs.createReadStream(<file>)`
>    - 返回讀到的數據
> - `http.createSever((req,res)=>{}) ` 的 `request` 其實也是讀取流
>    - 因為 req 也是流, 所以他有 `on`

> #### `fs.createReadStream.on('error', err=>{})`
> - 讀取錯誤觸發 cb

### 寫入流
> - `fs.createWriteStream`
> - `http.createServer((req,res)=>{})` 的 `response` 其實也是寫入流
>    - 因為 res 也是流, 所以他有 `on`

> #### `fs.createWriteStream.on('finish', ()=>{})`
> - 寫入完成後觸發
> - 寫入完成的名字不是 `end` !

#### 文件流
```shell
$ tree
.
├── 1.png
├── test.js
└── upload
```
```javascript=
let fs = require(`fs`);  

let rs = fs.createReadStream(`1.png`);
// let rs = fs.createReadStream(`3.png`); //=> 讀取失敗測試
let ws = fs.createWriteStream(`2.png`);

rs.pipe(ws); // 流有方向性, 先讀後寫


rs.on(`data`, data=>{
  console.log(`讀取資料`)
})

rs.on(`end`, ()=>{    // rs.on(`end`) 讀取完還會有一些還沒寫完
  console.log(`讀取完成`)
})


// 錯誤處理
rs.on(`error`, err=>{
  console.log(`讀取失敗`)
})

ws.on(`finish`, ()=>{     // ws 結束的名字不是 end!!!
  console.log(`寫入完成`)
})

```
```shell
$ node test.js
讀取資料 or 讀取失敗
讀取完成
寫入完成 

$ tree
.
├── 1.png
├── 2.png
├── test.js
└── upload
```

#### 網路流
```javascript=
let fs = require(`fs`);
let http = require(`http`);



let s = http.createServer((req, res)=>{
  let rs = fs.createReadStream(`www${req.url}`); // 讀取資料

  rs.pipe(res); // 把資料流出去


  rs.on(`error`, err=>{  // 除錯
      res.writeHead(404);
      res.write(`Not Found`);
      res.end();
  })
})

s.listen(5566);
```


### 讀寫流
> - 壓縮, 加密, ...
>   - ps. 音樂影片圖片類檔案的壓縮率不高, 所以有些人傾向不壓縮這些東西
```shell
$ tree -sh --size
.
├── [280k]  JQuery.js
└── [ 259]  gzip.js
```
```javascript=
const fs = require(`fs`);
const zlib = require(`zlib`);

let rs = fs.createReadStream(`JQuery.js`);
let ws = fs.createWriteStream(`JQuery.js.gz`);
let gz = zlib.createGzip();  // 創建一個 gz 壓縮對象

rs.pipe(gz).pipe(ws);   // 讀寫流: 一邊在讀一邊在寫

ws.on(`finish`, ()=>{
  console.log(`壓縮完畢`)
})
```
```shell
$ node gzip.js   
壓縮完畢

$ tree -sh --size
.
├── [280k]  JQuery.js
├── [ 83k]  JQuery.js.gz   #=> 壓縮成功
└── [ 259]  gzip.js

$ # 解壓正常~

$ tree -sh --size
.
├── [280k]  JQuery\ 2.js     #=> 正常解壓
├── [280k]  JQuery.js
├── [ 83k]  JQuery.js.gz
└── [ 259]  gzip.js
```
> - gz 是主流壓縮方法之一

#### 網路傳輸壓縮
```shell
$ tree
.
├── server.js
└── www
    ├── JQuery.js
    └── temp.html
```
```javascript=
const http = require(`http`);
const fs = require(`fs`);
const zlib = require(`zlib`);


http.createServer((req, res)=>{
    let rs = fs.createReadStream(`www${req.url}`);
    let gz = zlib.createGzip();

    // rs.pipe(res);  //=> 比較直接流與壓縮後流的差別

    res.setHeader(`Content-Encoding`, `gzip`);
    //=> 必須讓瀏覽器知道傳過去的是壓縮檔
    rs.pipe(gz).pipe(res);

    rs.on(`error`, err=>{
        console.log(err);
        res.setHeader(`Content-Encoding`, ``); 
            // NOT FOUND 不是壓縮檔, 所以必須把頭改回來, 
            // 否則瀏覽器用 gzip 去解析這兩個字根本看不懂而無法顯示
        res.writeHeader(404);
        res.write(`NOT Found`);
        res.end();
    })

}).listen(5566);
```
```htmlmixed=
<script src='JQuery.js'></script>
123
```
> - 壓縮前後
>   ![](https://i.imgur.com/ZJUiA1a.png)
>   ![](https://i.imgur.com/2KIdybm.png)
>   - html 非常微小的變大, 我猜是表頭多丟了幾個字( `setHeader()` )
> - 如果沒有在表頭告訴瀏覽器編碼方法, 瀏覽器根本不認識這檔案,
>   然後就會跳出下載, 叫使用者自己打開來看
>   <img src='https://i.imgur.com/Sc9UQPZ.png' style='width: 200px'>
> - 參考 Yahoo 的 Response Header
>   - `content-encoding: gzip`
>   - 設置後, 主流的瀏覽器就會自己解壓了
>   ![](https://i.imgur.com/EMyMcKm.png)

> #### `request.setHeader(name, value)`
> - 設定 header
> - `setHeader` 是設定 header 對象裡的屬性, 這只是設置
>   vs. `writeHeader`: write 寫完就發出去了
>   - 如果寫了 writeHeader 後再寫 setHeader, 程序會掛掉
```javascript=
const http = require(`http`);


http.createServer((req, res)=>{
    res.writeHeader(999);
    res.setHeader(`content-type`, `text/html`);
    res.write(5566);

    res.end();
	

}).listen(5566);
```
```shell
$ node server.js
Error: Cannot set headers after they are sent to the client
```



## 緩存
> - 瀏覽器的緩存一般都非常大, Chrome 之所以這麼快, 就是因為他用空間換時間
> - 有沒有可能本地的緩存修改時間比瀏覽器的新?
>   - 正常情況下不會, 但如果使用者自己去改過那個檔案, 那就有可能
> - 強制不使用緩存的絕招? 
>   - 改變 url
>   - 服務器配置
>   - 事實上標準的瀏覽器每次都會訪問服務器一次, 雖然很多瀏覽器都不是標準的
### 觀察 XAMPP 傳輸
```txt
// x 為重複 key, v 為多出來的 key

// 第一次請求

  // - REQUEST
x  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
x  Accept-Encoding: gzip, deflate
x  Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7
x  Connection: keep-alive
x  Host: 192.168.64.2
x  Referer: http://192.168.64.2/test/
x  Upgrade-Insecure-Requests: 1
x  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36

  // - RESPONSE

  Status Code: 200 OK

x  Accept-Ranges: bytes
v  Connection: Keep-Alive
x  Content-Length: 4
x  Content-Type: text/plain
x  Date: Thu, 09 Jan 2020 01:59:06 GMT
x  ETag: "4-59bab53846ab9"
v  Keep-Alive: timeout=5, max=100
x  Last-Modified: Thu, 09 Jan 2020 01:57:06 GMT   #=> 主要重點
x  Server: Apache/2.4.41 (Unix) OpenSSL/1.1.1d PHP/7.4.1 mod_perl/2.0.8-dev Perl/v5.16.3




// 第二次請求


  // - REQUEST
x  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
x  Accept-Encoding: gzip, deflate
x  Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7
v  Cache-Control: max-age=0
x  Connection: keep-alive
x  Host: 192.168.64.2
v  If-Modified-Since: Thu, 09 Jan 2020 01:57:06 GMT  #=> 主要重點
v  If-None-Match: "4-59bab53846ab9"
x  Referer: http://192.168.64.2/test/
x  Upgrade-Insecure-Requests: 1
x  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36


  // - RESPONSE

  Status Code: 304 Not Modified   #=> 主要重點

x  Accept-Ranges: bytes
x  Content-Length: 4
x  Content-Type: text/plain
x  Date: Thu, 09 Jan 2020 01:59:47 GMT
x  ETag: "4-59bab53846ab9"
x  Last-Modified: Thu, 09 Jan 2020 01:57:06 GMT    #=> 主要重點
x  Server: Apache/2.4.41 (Unix) OpenSSL/1.1.1d PHP/7.4.1 mod_perl/2.0.8-dev Perl/v5.16.3
```
> - 第一次請求: S > C => `Last-Modified: Thu, 09 Jan 2020 01:57:06 GMT`
>   - 第一次請求時, 服務器會發資料日期給客戶端
> - 第二次請求: 
>   - C > S => `If-Modified-Since: Thu, 09 Jan 2020 01:57:06 GMT`
>   - S > C => `Status Code: 304 Not Modified`
>   - 第二次請求時, 客戶端會告訴服務器自己手上的版本
>   - 服務器查證一下, 版本相同304, 版本不同 202
> - 所以緩存最重要的就是文件的最新修改日期

### fs.stat()
`fs.stat(path[, options], cb(err, stat)=>{})`
> - 查看檔案資訊
```javascript=
const fs = require(`fs`);


fs.stat(`./www/temp.html`, (err, stat)=>{
    if (err) {
        console.log(err);
    } else {
        console.log(stat);
        console.log(stat.mtime); // 2020-01-08T16:13:14.858Z
        console.log(typeof stat.mtime); // object
        console.log(stat.mtime.constructor); // [Function: Date]
          // 返回的是一個 Date 的實例
    }
})

```
```shell
$ tree
.
├── test.js
└── www
    └── temp.html
    
$ node test.js
Stats {
  dev: 16777224,   # 設備號
  mode: 33188,     # 文件模式
  nlink: 1,        # 連接數, linux 跟 os 可以 ssh 連接, 所以可能不是2
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 830972,
  size: 38,
  blocks: 8,
  atimeMs: 1578500585213.3013,
  mtimeMs: 1578499994858.087,
  ctimeMs: 1578500058928.0815,
  birthtimeMs: 1578499994853.8013,
  atime: 2020-01-08T16:23:05.213Z,
  mtime: 2020-01-08T16:13:14.858Z,      # 修改時間
  ctime: 2020-01-08T16:14:18.928Z,      # 建檔時間
  birthtime: 2020-01-08T16:13:14.854Z   # 檔案生日==? 應該跟 ctime 同意思
}
...
```
### Date.prototype.toUTCString()
> - 把日期對象轉 string
> - `Date.prototype.toGMTString()` 是一樣的東西, MDN 說別用這個
```javascript=
let oDate = new Date();
console.log(oDate.toUTCString()); // Thu, 09 Jan 2020 02:53:40 GMT
console.log(oDate.toGMTString()); // Thu, 09 Jan 2020 02:53:40 GMT
```

### 緩存實現過程
> - 利用 `fs.stat` 拿到檔案最新修改時間
> - 利用 `req.headers` 查看瀏覽器是否有 `if-modified-since` 
>   - 沒有: 表示瀏覽器沒有存過這個檔案在緩存中 => 發一份新的 202
>   - 有: 表示瀏覽器以前有存過, => 比較本機與瀏覽器的版本新舊
>     - 本機比較新: 發一份新的 202
>     - 本機沒有比較新: 304 叫瀏覽器讀取自己內存就好了
> - 兩者差距
>   ![](https://i.imgur.com/dO7cTcN.png)
>   ![](https://i.imgur.com/y0VNbR3.png)
```javascript=
const fs = require(`fs`);
const http = require(`http`);
const url = require(`url`);

http.createServer((req, res)=>{
    let pathname = url.parse(req.url)[`pathname`];
    // console.log(url.parse(req.url));

    // 1. 拿到文件的修改日期
    fs.stat(`www${pathname}`, (err, stat)=>{
        if (err) { // 錯誤處理
            // console.log(err);
            res.writeHeader(404);
            res.write(`Not Found`);
            res.end();
        } else {

            // 2. 判斷瀏覽器有沒有拿過這個檔案
            if (req.headers[`if-modified-since`]) {
            
                // 2.2 有拿過就拿他的修改時間來跟服務器的修改時間對比
                // console.log(req.headers[`if-modified-since`]);
                let cliDate = new Date(req.headers[`if-modified-since`]);
                cliDate = Math.floor(cliDate.getTime() / 1000); // 盡量避免小數
                console.log(cliDate);

                let serDate = Math.floor(stat.mtime.getTime() / 1000);
                console.log(serDate);

                // 2.2.1 比對誰新
                if (serDate > cliDate) {
                    // 2.2.2 服務器比較新就發一份
                    sendFile();
                    console.log(1);
                } else {
                    // 2.2.3 服務器沒有比較新就 304
                    console.log(2);
                    res.writeHeader(304);
                    res.write(`Not Modified`);
                    res.end();
                }

            } else {
                // 2.1 沒有就是第一次訪問這個文件, 發一次
                console.log(0);
                sendFile();
            }

            function sendFile() {
                let rs = fs.createReadStream(`www${pathname}`);

                // 每次傳檔案都要傳檔案最新修改時間
                res.setHeader(`last-modified`, stat.mtime.toUTCString());

                rs.pipe(res);

                // stat 那邊錯誤處理只表示他那時讀得到
                // 不代表檔案都沒錯, 也可能在這期間又發生啥事, 有處理有保佑
                rs.on(`error`, err=>{  
                    res.writeHeader(404);
                    res.wtite(`Not Found`);
                    res.end();
                })
            }
        }
    })
}).listen(5566);
```

```shell
$ node server.js
0             #=> 第一次請求, 
1578543584    #=> 相同版本請求, 304
1578543584
2
1578543584    #=> 修改檔案後請求, 重傳
1578543819
1
1578543819    #=> 修改檔案再次請求, 304
1578543819
2
```
```txt
// - 第一次請求文件

  // REQUEST
  ...

  // RESPONSE
  Status Code: 200 OK
  ...
  last-modified: Thu, 09 Jan 2020 04:19:44 GMT
  ...
  
// - 第二次請求文件
  // REQUEST
  ...
  If-Modified-Since: Thu, 09 Jan 2020 04:19:44 GMT
  ...
  
  // RESPONSE
  Status Code: 304 Not Modified
  last-modified: Thu, 09 Jan 2020 04:19:44 GMT
  
  
// - 修改文件後請求
  // REQUEST
  ...
  // If-Modified-Since: Thu, 09 Jan 2020 04:19:44 GMT
  ...
  
  // RESPONSE
  Status Code: 200 OK
  ...
  // last-modified: Thu, 09 Jan 2020 04:23:39 GMT
  ...
```
### 緩存策略
> - 必須告訴瀏覽器哪些東西可以緩存, 哪些不行
> - `cache-control`: 控制緩存內容
> - `expires`: 可以緩存, 但有時效




## 多進程
> - 多線程: 性能高, 非常複雜
> - 多進程: 性能略低, 相對簡單
>   - 相較於單進程, 更充分利用 CPU 資源
>   - 主進程: 負責生成子進程, 千萬別幹太多事, 主進程掛了全部就掛了
>   - 子進程: 負責做事

> - nodeJS 默認單進程單線程
> - 一般情況, 主進程會等待子進程結束才結束

### 進程
> - 普通程序無法創建進程, 只有系統進程才能創建進程, 進程是分裂出來的
> - 只有主進程能分裂
> - 分裂出來的兩個進程共享同一套 code

### cluster.fork()
> - 分裂進程
```javascript=
const cluster = require(`cluster`);

cluster.fork();  // 分裂進程

console.log(1);
```
```shell
$ node 1.js
1
cluster.fork is not a function # 因為只有主進程能分裂, 子進程執行到分裂方法就掛了
```

### cluster.isMaster
> - 判斷是否為主進程
> - 返回 true 就是主進程
```javascript=
const cluster = require(`cluster`);

if (cluster.isMaster){
	cluster.fork();
}
console.log(1);

// shell
1
1

```

> #### 進程開太多沒有意義, 視核心數極限 
### os.cpus()

> - 返回 cpu 資訊 , 用陣列裝
```javascript=
const cluster = require(`cluster`);
const os = require(`os`);
// console.log(os.cpus())
	// [{...},{...},{...},{...}] => 四核

if (cluster.isMaster) {
	for (let i = 0; i < os.cpus().length; i++) {
		cluster.fork();
	}
	console.log(`主進程`);
} else {
	console.log(`子進程`)
}

/* shell
主進程
子進程
子進程
子進程
子進程

*/
```
> #### 父子進程共享[handle](https://zh.wikipedia.org/wiki/%E5%8F%A5%E6%9F%84)
>   - 一個端口只能有一個程序
>   - 可是父子進程可以共享同個端口
```javascript=
const http = require(`http`);

http.createServer().listen(5566);
http.createServer().listen(5566);

/* shell
	Error: address already in use :::5566
*/
// 端口只能單一程序
```
```javascript=
const cluster = require(`cluster`);
const os = require(`os`);
const http = require(`http`);

if (cluster.isMaster) {
    for (let i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
    console.log(`主進程`);
} else {
    http.createServer().listen(5566);
    console.log(`5566`);
}

/* shell
  主進程
  5566
  5566
  5566
  5566
*/
// 父子進程可以共享同個端口~
```

### process.pid
> - 進程號碼
```javascript=
const cluster = require(`cluster`);
const os = require(`os`);
const http = require(`http`);
const process = require(`process`);

if (cluster.isMaster){
    for (let i=0; i<os.cpus().length; i++) {
        cluster.fork()
    }
    console.log(`主進程`)
} else {

    http.createServer((req,res)=>{
        console.log(process.pid);     // 進程號碼~~~
        res.write(`123`);
        res.end();
    }).listen(5566);
    console.log(`子進程`);
}



/* shell
 * 
 * 頁面刷幾次都是 9230
 * 因為多核進程運作是第一個滿了才會啟用第二個, 不是分工
 * 因為切換進程需要消耗不少資源
 * 資源的調度是由系統完成的
 * 
 * 
node 1.js
主進程
子進程
子進程
子進程
子進程
9230
9230
9230
9230
9230
9230
*/
```

























