前後端分離的概念近幾年來開始盛行與倡導,蠻多大公司的專案要找人也都會分前端工程師與後端工程師兩種。 這種分離可以將一個大型專案拆分成前後端兩塊,可以讓開發者的工作職責更為明確,減少相依性。
過往 JAVA 上 JSP 的開發就是典型的前後端不分,HTML 與 JAVA 混在一起,背後本質上其實是 Servlet,debug 不易,而且速度上會比起靜態資源的載入更緩慢,在大併發的情況下效能會下降。
在這樣的氛圍下似乎前後端分離才是王道,但是如果專案只是單單一個小專案,或者頁面不多的情況下,真的有需要做成前後端這樣複雜的架構嗎?? 個人認為還是得看實際需求決定,沒有絕對正確與否。
本次目標
這次主要想實現的功能是:
- 後端 (kotlin) 放在 Tomcat,前端 (html) 則是 Nginx。
- 當前端 Html 頁面留給 Nginx 處理,剩下的 API 會由 Nginx 轉至後端 Tomcat 後回傳 Nginx,Nginx 再去處理回復,這一段就是用到 Nginx 反向代理的功能
- HTTPS 要用的 SSL 憑證留給 Nginx 中設定,Tomcat 只要處理 Http 請求就好。
這裡其實會有一個問題是,關於 Tomcat 的 Session 保存問題,如果一個 Nginx 會反向代理給多個 Tomcat 的話,保存登入資料狀態等問題就會出現,不過這裡我想先做單一 Nginx 串 Tomcat 就好。
SSL 的憑證準備
憑證的部分會需要 Domain,但是在測試的情況下不可能去買什麼網域。 因此如果是 Windows 的話我們可以直接開 host 檔案,加入自訂 localhost 對應到一個自訂的網域就好了。然後再拿這個網域去做自我憑證簽署。
host 檔案的位置如下:
C:\WINDOWS\system32\drivers\etc\hosts
加入妳要做測試的 Domain,之後要拿這個 Domain 做自簽憑證
填寫範例:
127.0.0.1 enix.com
產生憑證檔案
這裡要利用 OpenSSL 來產生憑證檔案。 如果有裝 Git 的話,應該也會有 Git Bash 可以用,其自帶了 OpenSSL 所以可以直接在 Git Bash 中使用。
請先在你要儲存的位置中開啟 GitBash。 再來以下是 Console 中需要做的指令順序:
- 首先產生一個自己的根憑證出來
# 產生 RSA 加密的 Key,長度設定 4096
openssl genrsa -out ca.key 4096
# 使用上方的 Key 產生根憑證
# Generate self signed root CA cert
openssl req -new -x509 -days 365 -sha256 -key ca.key -out ca.crt
- 產生指令一打完就會要求輸入以下的資訊:Domain 請填剛剛在 host 中設定的值,
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:Taipei
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Enix
Organizational Unit Name (eg, section) []:Enix
Common Name (e.g. server FQDN or YOUR name) []:enix.com
Email Address []:enix@gmail.com
到這裡你應該會看到產生了兩個檔案:ca.key、ca.srt
- 再來才是實際產生要在我們的 Local Server 上用的憑證檔案,也就是 Nginx 上要放的憑證。
# Generate server cert to be signed
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
輸入完一樣會要你打一些資訊,照舊即可。
到這裡你應該會看到除了剛剛的兩個檔案又多產生了兩個新檔案:server.key、server.srt
- 最後我們把剛剛產生的 4 個檔案來進行憑證簽署
openssl x509 -req -CAcreateserial -days 30 -sha256 -CA ca.crt -CAkey ca.key -in server.csr -out server.crt
完成後就可以看到多產生了兩個檔案:server.crt、ca.srl 總共六個檔案被產出了,如下圖
Nginx 需要的 SSL 憑證檔案有兩個:
- server.key 是 Server 的私鑰
- server.crt 是 Server 的憑證
之後把這兩個檔案放入 Nginx 設定中就完成了,這會在後面說明。
後端 Tomcat 測試用部分
Tomcat 的部分並非此次實驗重點,我們可以簡單一點使用 Springboot 內嵌 tomcat 打包 jar 檔來測試就好,甚至是直接在 IDE 中啟動也可以。
簡單用 Spring Initializer 創建一個 SpringBoot 應用程式並在 local 起起來,以下是目錄結構:
接著來創建一個取出 Session 的方法,用來測試是否對不同的使用者都有正確的 Session 存取。
這裡可以看到這個 API 的 URL 是 localhost:8080/hello
- 8080 port 的設定在 resources/application.xml 中可以設定改變
package com.enix.controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpSession
@RestController
@RequestMapping("/")
class HelloController {
@RequestMapping("/hello")
@ResponseBody
fun sayHello(session: HttpSession): String {
var count = (session?.getAttribute("count") ?: 0) as Int
session.setAttribute("count", ++count)
return count.toString()
}
}
Nginx 上的設定:反向代理、SSL
首先到官方網站 nginx: download 中選擇 Stable version 的 Windows 版本後解壓縮。
目錄中可以看到 config/nginx.conf 檔案就是主要設定檔,以下將圍繞這個檔案做設定。
先說明我們要設定的項目功能有什麼:
- 如果 url 中是 api 開頭,就反向代理到後端 tomcat 上
- 如果 url 是其他非 api 開頭則可以直接讀取 Nginx 上的頁面
- Nginx 的 SSL 設定
我們先把剛剛的憑證檔案放到 Nginx 的目錄下面:config/ssl 這個目錄預設是沒有的請自己建立。
我們以 nginx.conf 中預設設定為基礎來打開註解或增加設定來達到我們上方所敘述的效果。
以下是需要設定的項目細節: 這裡我們不另外設定將 http 強轉到 https
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
listen 80;
server_name localhost;
server {
location / {
root html;
index index.html index.htm;
}
# 預設的錯誤轉導頁
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# 使用 =代表精準匹配
# 使用~代表使用正規表示法來做匹配,會區分大小寫,若要不區分大小寫需使用~*,
# 由於第一個匹配到規則的配置會立即採用,所以採用此種方式的配置其順序很重要
#
# api 開頭的位置轉到後端去
location /api/ {
# 反向代理到同一台主機的 8080 Port
# 也就是 Tomcat 開的 port
proxy_pass http://localhost:8080/;
# 把 IP、Protocol 等 header 都一起送給反向代理的 server
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
}
# 這裡請打開整個註解
# HTTPS server
server {
listen 443 ssl;
server_name localhost;
# 指定好你的自簽憑證位置
ssl_certificate ssl/server.crt;
ssl_certificate_key ssl/server.key;
location / {
root html;
index index.html index.htm;
}
location /api/ {
# 反向代理到後端 http
proxy_pass http://localhost:8080/backend/;
# 如果 Session 維持失敗,可以考慮開啟
# proxy_cookie_path /api/ /backend/;
# 把 IP、Protocol 等 header 都一起送給反向代理的 server
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
}
}
當以上設定完成以後,我們先於 CMD 中用以下指令測試設定是否都正確:
nginx -t
沒有問題就啟動 Nginx 吧。
start nginx
最後打開瀏覽器輸入你自訂的 Domain 網址 URL 測試:
https://enix.com/api/hello
前端 Ngnix 的預設頁面:
後端 Tomcat 的計數器 API:
發現 Session 可以正常運作,SSL 也正常發揮功能,後端的計數器可以正常增加。
CentOS補充
以上的範例說明都是在 Windows 下的,如果要在 Linux 上做到反向代理還有一些關於 SELinux 的小細節要注意:
SELinux 會把這一個反向代理功能給擋掉,沒有先做設定的話 Nginx 的 Error.log 會看到類似以下內容:
2022/08/16 21:18:30 [crit] 111087#0: *1 connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream, client: 192.168.0.*, server: localhost, request: "GET /api/poweredby.png HTTP/1.1", upstream: "http://127.0.0.1:8080/poweredby.png", host: "192.168.0.*", referrer: "http://192.168.0.*/**/"
這時候請輸入以下指令並重啟系統即可:
# 永久開啟 -P
setsebool httpd_can_network_connect on -P
# 檢查 httpd_can_network_connect 是開的
getsebool -a | grep httpd
結語
這次簡單的實作了一個前後端分離的架構,Html 放 Nginx,API 部分留給 Tomcat,並可以維持住 Session 的存取,當然這裡面還有很多議題可以討論,像是多台 Tomcat 的情況處理等等,不過我想重點應該還是都有涵蓋到。
如果有任何問題或疑惑歡迎留言在下方,感謝各位的閱讀。