---
# System prepended metadata

title: Web Server軟體 V.S. NodeJS
tags: [技術, 網路]

---

# Web Server軟體 V.S. NodeJS
※本篇內容僅用以方便我自己記憶用，不一定符合業界習慣且無法保證100%正確。

## 本篇名詞解釋

### Web Server軟體
市面上常見的，用以開啟網頁伺服器連線服務的軟體，如[Apache HTTP Server](https://zh.wikipedia.org/zh-tw/Apache_HTTP_Server)、[Nginx](https://zh.wikipedia.org/zh-tw/Nginx)、[微軟IIS](https://zh.wikipedia.org/zh-tw/%E7%B6%B2%E9%9A%9B%E7%B6%B2%E8%B7%AF%E8%B3%87%E8%A8%8A%E6%9C%8D%E5%8B%99)。

### HTTP Server
定義大致上等同網頁伺服器(Web Server)。
接收來自HTTP protocol(且通常是通過80 port或443 port)的流量並進行處理、給予回應的伺服器。80 port為HTTP protocol常用的連接埠，443則為HTTPS(含確認安全憑證相關程序，為目前主流的網頁通訊協定)的常用連接埠。

### Node.js
JavaScript的框架(Framework)，用來撰寫網路服務相關的功能，模組化、入門難度低，且**可以取代其他Web Server軟體，用來打開HTTP Server**。

<br>

## 實作 & 區別
以下皆以我本次實作經驗為例，作業系統為Ubuntu 22.04.1 LTS，使用Apache2來當HTTP Server，並讓Nodejs的WebSocket模組接管3000 port。

### Apache
**在預設config下，Apache會監聽80 port的流量，建立一個HTTP Server**。啟動Apache之後，一般使用者即可透過在瀏覽器輸入http://{IP-address}:80來存取你的網頁 (或http://{domain-name}:80，如果你有綁定域名的話)。網址後面的:80表示使用者透過80 port連線，由於80和443是最常用的HTTP port number，key網址時通常可以直接省略。

網頁要顯示的內容預設是編寫在/var/www/html底下的index.html中。

申請憑證且設定好SSL相關config後，可透過指令開啟https連線功能 (通常也就是443 port的對外連線，記得也要allow linux防火牆的443流量)。
(相關教學：[Apache 安裝 HTTPS 教學](https://medium.com/feveral%E7%9A%84%E7%A8%8B%E5%BC%8F%E7%AD%86%E8%A8%98/apache-%E5%AE%89%E8%A3%9D-https-%E6%95%99%E5%AD%B8-dd022e2be7b4))
```
# 撰寫SSL相關的config
sudo vim /etc/apache2/sites-enabled/ssl.conf

# 啟用剛剛寫好的ssl.conf
sudo a2enmod ssl

# 重開Apache
sudo service apache2 restart

# 允許443 port通過防火牆
sudo ufw allow 443
```

另外，透過編輯config檔案，可設定proxy，依照不同情況把client的request重新導向到指定的port做處理，或者做自動轉址(如：把 http:// 自動轉址到 https:// ，半強制使用者透過https連線，避免沒有經過SSL憑證驗證的流量)
(相關範例：[如何設定WebSocket轉導與分流](https://www.tpisoftware.com/tpu/articleDetails/947) 、 [How to automatically redirect HTTP to HTTPS on Apache servers?](https://stackoverflow.com/questions/16200501/how-to-automatically-redirect-http-to-https-on-apache-servers))

如果需要開啟其他port，同樣可透過編輯config檔案來達成。
###### > /etc/apache2/sites-enabled/000-default.conf (VirtualHost後面的*:80為port number)
![](https://i.imgur.com/ofsNTa2.png "備援網址 https://images.plurk.com/7puSsfFlZK3hnnwBWgOjwh.png")

###### > 開啟80與443 port之後，透過netstat指令確認這些port的監聽狀況
![](https://i.imgur.com/nhSejgJ.png "備援網址  https://images.plurk.com/4UfcL0lCCytcuaxDbh7NaK.png")


### Node.js
同樣可用來開啟server，監聽的port依照程式碼編寫內容而定，也可以一次開啟並監聽數個port。

如果監聽80 port或者443 port，大致上便是取代了Apache的功能、建立一般的HTTP Server。

以下範例程式是以nodejs的https套件來開啟HTTP server，且這裡指定的port number是3000 port而不是443 port，因為待會還有特殊用途。
(相關教學：[Day7 - Node.js 內建的 Web Server 介紹及使用](https://ithelp.ithome.com.tw/articles/10185302))
```javascript=
//import相關套件
const fs = require('fs')
const https = require('https')
const SocketServer = require('ws').Server
const PORT = 3000 //指定 port

const options = {
    // SSL憑證相關檔案路徑
    key: fs.readFileSync('/privkey.pem'),
    cert: fs.readFileSync('/fullchain.pem'),
}

//建立server
const server = https.createServer(options);
server.listen(PORT, '0.0.0.0', () => {
    console.log(`Listening on ${PORT}`);
});
```

到這邊一般使用者即可透過輸入https://{domain-name}:3000來連上你的網頁，但因為網頁內容是空白的，瀏覽器可能會回傳EMPTY RESPONSE錯誤訊息來告訴你它沒有東西可顯示。

###### > 透過netstat指令，可以看到在執行node之後，3000 port已經被監聽了。
![](https://i.imgur.com/8pWnZqC.png "備援網址 https://images.plurk.com/2vefdiyM0zKGOkOgxAXSPd.png")


承上述程式碼，接下來我將server交給ws套件，它將開啟WebSocket(另一種通訊協定)的相關服務。
```javascript=
//將 express 交給 SocketServer 開啟 WebSocket 的服務
const wss = new SocketServer({ server });
```

從這裡開始，一般使用者就無法再透過https://{domain-name}:3000連上你的網頁，因為3000 port目前已經被WebSocekt server監聽，得透過**wss**://{domain-name}:3000來連線 (但並不是直接輸入在瀏覽器的網址列，必須透過WebSocket協定來連線，這部份有一些線上測試工具可以用)

※因為前面使用的是**https**連線，所以WebSocket會強制規定Client端只能用同樣有通過SSL驗證程序的**wss://** (WebSockets over SSL/TLS)來連線，否則會報錯。如果前面用的是不需要安全憑證的**http**，則可以用**ws://** 開頭的網址進行連線。

WebSocket協定可以做到讓使用者與伺服器即時互動，甚至再透過伺服器broadcast來達到使用者與使用者即時互動的效果。以這支程式為例，呈現的就是簡易的即時聊天室功能。
(參考範例：[[WebSocket] 實作一個簡單的聊天室 (+ Node.js)](https://eudora.cc/posts/220105/))

![](https://images.plurk.com/7xdAaOz9JFkdwm9QK0R4iY.gif "備援網址 https://images.plurk.com/7xdAaOz9JFkdwm9QK0R4iY.gif")
```javascript=
//當有 client 連線成功時
wss.on('connection', ws => {
  ws.send("This website is for test purpose, please do not leave any personal information here.");
  console.log('Client connected')
  // 當收到client消息時
  ws.on('message', data => {
    // 收回來是 Buffer 格式、需轉成字串
    data = data.toString()
    console.log(data) // 可在 terminal 看收到的訊息

    /// 發送消息給client 
    ws.send(data)

    /// 發送給所有client： 
    let clients = wss.clients  //取得所有連接中的 client
    clients.forEach(client => {
        client.send(data)  // 發送至每個 client
    })
  })
  // 當連線關閉
  ws.on('close', () => {
    console.log('Close connected')
  })
})
```


### 流程圖
![](https://i.imgur.com/p66GUMr.png "備援網址 https://images.plurk.com/MoHijVSk6Dh49SSOyL04Y.png")
