寫在開頭
本文采用圖文解析、結合實戰的方式進行網絡原理解析,幫助大家去掌握一些網絡知識,並瞭解 Caddy
的基本使用(見下圖)。
本人計劃在近幾年將持續輸出深度好文,如果對這類文章感興趣的話,還請您點個 關注
和 贊
支持一下吧!
引言
大家好呀~
本篇文章主要是安利一個對前端更友好的 web
服務器 Caddy
,我們會介紹 Caddy
的安裝使用,並通過圖文來解析其原理。
Caddy
是唯一一個在默認情況下自動使用 HTTPS
的 Web
服務器,可以用來完成跨域請求、反向代理、靜態文件服務器、部署 History SPA
應用、負載均衡等等功能,在可讀性、可維護性和易用性方面都做的很好,對前端更友好!
如果你還是不太理解
Caddy
到底是用來做什麼的,那你可以把它簡單理解爲對前端更友好的nginx
。
反向代理
本文討論的
代理
僅限於HTTP 代理
,不涉及其他協議。
Caddy
是一個簡單好用的 Web
服務器,反向代理
是它的一個核心功能。所以,在介紹 Caddy
之前,我們先介紹一下 反向代理
是什麼,反向代理
可以幫我們做什麼事情。
我們先來了解一下正向代理,正向代理就是在客戶端與服務器之間實現一個代理服務器,客戶端的所有請求先經過代理服務器,由代理服務器再去請求真實服務器,請求成功後再由代理服務器將真實服務器的響應結果發回至客戶端。
正向代理的經典案例就是公司內部的 VPN
代理,企業員工在 遠程開發
時需要先連接 VPN
,再由 VPN
連接至公司服務器。這樣做可以防止一些陌生連接,拒絕除 VPN
外的所有外網連接,只有連接 VPN
才能正常訪問公司服務器。
我們來畫一張圖幫助大家理解什麼是 正向代理
(見下圖)
而反向代理正好相反,反向代理一般是在服務器端,客戶端發起的網絡請求首先被反向代理服務器收到,再由反向代理服務器決定轉發到某個具體的服務。換而言之,反向代理服務器將決定客戶端最終訪問到的目標服務器,常見的反向代理案例有負載均衡、CDN 加速。
我們在實際開發中,可以使用反向代理來 解決前端跨域問題
、部署前端服務
等等,我們本篇教程也是主要介紹這兩個功能的使用。
我們來畫一張圖幫助大家理解什麼是 反向代理
(見下圖)
最後使用一句話概括就是:正向代理隱藏真實客戶端,反向代理隱藏真實服務端。
Caddy 的優勢
我們在實際開發中,可以使用 Caddy
來搭建反向代理服務器,從而完成跨域請求、靜態文件服務器、部署 History SPA
應用、負載均衡等等功能,使用 Caddy
來做這些工作的好處是我們通過幾行配置文件就可以完成這些工作,非常的簡單易用。
在日常開發中我們通常使用 webpack
解決開發環境的跨域和請求轉發問題,webpack
的 proxy
選項可以解決大部分跨域和請求轉發問題,但是對 history
路由的支持性較差,並且組內開發的成員之間的配置可能會導致衝突,造成額外的維護成本。
使用 nginx
可以解決這些問題,但是 nginx
比較複雜,對前端人員並不是特別友好。在學習 nginx
的過程中我們可能會漸行漸遠,忘記了我們的初衷只是爲了解決跨域和請求轉發問題。
Caddy
使用 Go
語言編寫,跨平臺性強,配置文件具有高可讀性,對前端更友好。在可讀性、可維護性和易用性方面的優勢成爲了選擇 Caddy
的理由。
安裝 Caddy
介紹了那麼多,我們差不多可以進入到實戰部分了,先從 安裝
開始吧!
Caddy
目前有 1.0
和 2.0
兩個大版本,本文是針對 2.0
版本的教程,如果需要使用 1.0
版本的話建議查看 Caddy 1.0 官方文檔。
如果想要先了解
Caddy
好不好用,可以先跳過安裝
這一節。
Mac 平臺
Mac 非常適合開發者,歡迎廣大開發者加入 Mac 大家庭。
首先我們需要下載 Caddy,你也可以去 官方地址 下載最新版本。
由於 Caddy
由 go
編寫,go
編譯後的文件可以直接執行,所以我們下載完成後我們直接解壓到自己的目錄,比如 ~/bin/
目錄。然後我們加上一個映射就可以使用啦,我們使用 vi ~/.bash_profile
命令編輯文件,添加下面這行代碼:
export PATH=~/bin
添加了全局映射後,我們使用下面這行命令使我們的改動生效
source ~/.bash_profile
接下來我們輸入 caddy version
來驗證我們的安裝是否生效,如果可以正確輸出 caddy
的版本說明已經安裝成功啦~(見下圖)
Windows 平臺
首先我們需要下載 Caddy,你也可以去 官方地址 下載最新版本。
下載完成後,解壓到你的常用目錄(路徑最好別帶中文),然後我們複製 Caddy
所在目錄的路徑(見下圖)
然後,我們使用 Win + E
喚起文件管理器,然後右鍵點擊我的電腦,點擊 屬性
(見下圖)
然後,我們選擇 高級
,點擊 環境變量
(見下圖)
然後我們在彈出的窗口中,選中 Path
這一欄(見下圖)
然後,我們在彈出的窗口中點擊新建,將我們複製的 Caddy
目錄路徑粘貼進去(見下圖)
最後,我們點擊 確定
,保存設置。我們在命令行中輸入 caddy
,安裝成功啦!(見下圖)
Linux 平臺
首先我們使用 curl
命令下載 Caddy
的安裝包,如下
curl -OL https://github.com/caddyserver/caddy/releases/download/v2.0.0/caddy_2.0.0_linux_amd64.tar.gz
大家根據自己的需要下載對應版本的安裝包。
我們使用 tar zxvf caddy_2.0.0_linux_amd64.tar.gz
解壓文件,解壓後的 caddy
文件是可執行文件,我們再配置相應的映射,將命令映射到全局即可(見下圖)
Caddy
使用教程
在 Caddy
安裝完成後,我們來學習如何使用 Caddy
吧。
使用 Caddy
解決跨域問題
我們先使用 Caddy
來解決一個前端最常見的跨域問題,我們以一個簡單 Demo 爲例。在該案例中,我們使用 fetch
發起一個網絡請求,請求一個網絡資源(見下圖)
從上圖我們可以看出,我們在使用 fetch
發起了一個網絡請求後,將請求的結果打印出來。現在,我們打開瀏覽器,查看請求結果(見下圖)。
從上圖可以看到,我們的請求失敗了,請求失敗的原因是因爲瀏覽器的 同源策略
導致的跨域問題。
同源策略是一個重要的安全策略,它用於限制一個
origin
的文檔如何能與另一個源的資源進行交互,在使用XMLHttpRequest
或fetch
時則會受到同源策略的約束。
我們想要解決這個問題的話,需要服務端返回指定的響應頭(Access-Control-Allow-*
),這些響應頭可以通過瀏覽器的 同源策略
檢測。
如果需要在服務端配置響應頭的話,則需要後端人員配合,由前端推動後端的工作在效率上是不高的,還可能有些後端人員難以配合(可能是異地、第三方接口、不知道跨域是啥…)。
我們現在來使用 Caddy
解決這個問題,我們需要通過簡單的兩步來解決這個跨域問題:
- 配置
Caddyfile
(Caddy
的配置文件),啓動Caddy
; - 配置
hosts
文件;
配置 Caddyfile
Caddyfile
是 Caddy
的配置文件,我們在 Demo 的根目錄
下新建文件 Caddyfile
,添加下面幾行代碼
http://proxy.dev-api-mall.jt-gmall.com {
reverse_proxy http://dev-api-mall.jt-gmall.com {
header_up Host dev-api-mall.jt-gmall.com
header_down Access-Control-Allow-Origin *
header_down Access-Control-Allow-Methods *
header_down Access-Control-Allow-Headers *
}
}
我們對這幾行配置進行簡單的解析(見下圖):
我們來分析一下上面幾行核心配置代碼的含義吧,解析如下:
第 1 行
:攔截對http://proxy.dev-api-mall.jt-gmall.com
這條url
的訪問請求,進行內部邏輯處理;第 2 行
:將攔截的請求
轉發(反向代理)到http://dev-api-mall.jt-gmall.com
(我們的目標地址);第 3 行
:在轉發請求時,添加首部字段Host: dev-api-mall.jt-mall.com
,這一步的目的是爲了讓目標服務器能夠識別請求源;第 4~6 行
:在響應結果時,添加Access-Control-Allow-*: *
等多個首部字段信息,這樣可以通過瀏覽器的同源策略
檢測;
我們通過嵌套結構的幾行代碼就可以將 Caddyfile
配置完成啦!
配置 hosts
文件
在配置好 Caddyfile
後,我們將我們請求的地址修改爲 http://proxy.dev-api-mall.jt-gmall.com
,代碼實現如下:
我們在命令行工具使用 caddy run --watch
命令運行 caddy
(運行 caddy
時請保證 80
端口是空閒的),caddy
運行成功後將會輸出下面的結果(見下圖)
然後我們打開瀏覽器,打開 http://localhost:3000
(Demo
的運行地址),查看控制檯輸出的請求結果(見下圖)
從上圖可以看出,我們的請求失敗了,這是因爲我們在訪問代理地址(http://proxy.dev-api-mall.jt-gmall.com
)時,由於這個域名沒有註冊,將會導致 DNS
解析失敗,最終導致請求失敗。
此時我們只需要配置 hosts
文件,將這條 hostname
的 IP
地址指向本機即可,在 hosts
文件中添加下面這條記錄:
127.0.0.1 proxy.dev-api-mall.jt-gmall.com
hosts
文件是一個操作系統文件,以表的形式存儲了 主機名 和IP
地址,用於查找主機名稱。這條記錄代表的是在訪問
proxy.dev-api-mall.jt-gmall.com
時,將IP
地址解析爲127.0.0.1
(本機)。不同系統的
hosts
文件配置方法在本文的最後一節
。
配置好了 hosts
文件後,我們刷新瀏覽器,可以看到我們的請求結果被打印在控制檯了!(見下圖)
我們從上圖可以看出,我們通過 Caddy
的反向代理功能解決了跨域問題,並且更好的模擬了真實環境的網絡請求。
原理解析
我們來簡單梳理一遍流程,分析一下 Caddy
做了什麼,幫助我們解決了跨域問題。
我們從客戶-服務端的視角來進行解析,我們的瀏覽器就是客戶端,Caddy
同時作爲服務端與客戶端,目標服務器屬於服務端。
瀏覽器 - 客戶端
首先,我們在客戶端(瀏覽器)發起了一個請求,請求的地址是 http://proxy.dev-api-mall.jt-gmall.com/vegetable/list?page=1&pageSize=20
,瀏覽器首先解析出 hostname
的值爲 proxy.dev-api-mall.jt-gmall.com
。
在解析出了 hostname
後,瀏覽器讀取主機的 hosts
文件配置,查詢是否匹配,此時將命中我們在 hosts
文件中設置的 127.0.0.1 proxy.dev-api-mall.jt-gmall.com
規則,將域名解析爲 IP
地址 - 127.0.0.1
,也就是本機地址。
將域名解析完成後,瀏覽器解析到請求的端口爲空,請求協議爲 http
,然後使用 http
的默認端口 80
與 IP
地址創建了網絡套接字 127.0.0.1:80
(如下圖)。
創建好了網絡套接字後,瀏覽器將與目標地址 127.0.0.1:80
(我們運行的 Caddy
服務) 創建 TCP
連接,然後按照 http
協議標準封裝好請求信息,以數據分組(segment
)的形式發送給服務端。
Caddy
- 服務端 + 客戶端
我們的 Caddy
服務(服務端)運行在本地端口 80
上,對應的地址就是 127.0.0.1:80
。所以, Caddy
服務收到了這個 TCP
連接請求,Caddy
將 TCP
的數據分組(segment
)解析後,解析到了 http
請求(見下圖)。
從上面可以看出,我們的請求源是 127.0.0.1:57721
(IP
地址爲我們本機的 IP
,端口爲 瀏覽器
發起請求時使用的的隨機端口 - 瀏覽器
客戶端),目的地址是 127.0.0.1:80
(IP
地址爲我們本機的 IP
,端口爲 Caddy
的運行端口 - Caddy
服務端)。我們的 Host
請求頭爲 proxy.dev-api...
(代理地址),請求來源(發起方)是 http://localhost:3000
(我們的本地服務)。
Caddy
收到了這個 http
請求後,解析到協議爲 HTTP
,Host
爲 proxy.dev-api-mall.jt-gmall.com
,組合起來後匹配到了下面這條配置規則。(見下圖)
從上圖可以看出,Caddy
在匹配到內部規則後,開始處理這條請求。根據配置規則,Caddy
將這條請求轉發到 http://dev-api-mall.jt-gmall.com
。此時,Caddy
先進行 DNS
查詢和端口查詢,組合了 IP
地址與端口後再與該地址建立 TCP
連接,將客戶端的請求原封不動的轉發到指定地址(見下圖)。
從上圖可以看出,這條請求由作爲客戶端的 Caddy
發出。我們的請求源是 10.8.71.38:52170
(IP
地址爲本機的 IP
,端口是 Caddy
使用的隨機端口 - Caddy
客戶端),目的地址是 39.98.164.255:80
(IP
地址爲目標服務器 IP
,端口爲 HTTP
協議默認端口號 - 目標服務器)。我們的 Host
請求頭爲 dev-api...
(我們在 Caddyfile
中指定的 Host
首部),其餘的首部字段及請求信息都由 Caddy
直接轉發到目標服務器。
遠程服務器接收到請求後,處理請求後返回響應結果。(見下圖)
我們從上圖可以看出,這條響應結果的源地址是 39.98.164.255:80
(IP
地址爲請求的服務器 IP
,端口爲請求的服務器端口 80
- 遠程服務器),目的地址是 10.8.71.38:52170
(IP
地址爲我們本機的 IP
,端口是 Caddy
使用的隨機端口 - Caddy
客戶端)。服務器將響應結果發送到 Caddy
客戶端,我們的 Caddy
客戶端接收到響應結果後,由 Caddy
服務器進行處理。
我們的 Caddy
服務器在處理響應結果時,根據 Caddyfile
配置在響應結果中添加 Access-Control-Allow-...
三條首部信息,最後將這條響應結果發送給瀏覽器客戶端。(見下圖)
我們從上圖可以看出,這條響應結果的源地址是 127.0.0.1:80
(IP
地址爲我們本機的 IP
,端口爲 Caddy
的運行端口 - Caddy
服務端),目的地址是 127.0.0.1:57721
(IP
地址爲我們本機的 IP
,端口爲 瀏覽器
發起請求時使用的的隨機端口 - 瀏覽器
客戶端)。
我們可以在響應結果中看到,我們在 Caddyfile
設置的首部信息 Access-Control-Allow-...
被添加在了響應結果中,響應結果中有這三個首部字段就可以通過瀏覽器的 同源策略
限制。我們在響應首部中可以看到兩個 Server
首部,一個是我們本地的 Caddy
服務自動添加,另一個可能是遠程服務器上的 Caddy
服務器所添加的。最後,數據被正常返回,我們在瀏覽器的控制檯也可以看到請求成功啦!(見下圖)
從上圖看出,我們通過 Caddy
的反向代理功能,解決了跨域問題!
我們最後來通過一張圖幫助大家理解上面的流程吧!(見下圖)
圖有點大,建議點擊查看原圖,這樣可以看到更多細節。
使用 Caddy
搭建反向代理服務器
在這一節我們將使用 Caddy
搭建反向代理服務器,Caddy
可以輕鬆地完成這項工作。
使用 Caddy
搭建反向代理服務器的思路和解決跨域問題的思路是差不多的,都是使用 reverse_proxy
屬性。
我們想要實現的效果是,在訪問 http://www.caddy-test.com
域名時,將其反向代理到我們的本地服務 http://localhost:3000
上。
我們先在 http://localhost:3000
服務加上一些樣式,修改後效果如下圖
我們從上圖可以看出,我們的服務允許在本地的 3000
端口上,我們使用 /list
路徑訪問了一個列表頁。
此時我們打開 http://www.caddy-test.com/list
(見下圖)
從上圖可以看出,由於這個域名尚未註冊,所以導致我們的 DNS
查詢失敗啦!
配置 hosts
文件
我們在本地開發時,只需要配置 hosts
文件,將這條 hostname
的 IP
地址指向本機即可,我們在 hosts
文件中添加這條記錄:
127.0.0.1 www.caddy-test.com
不同系統的
hosts
文件配置方法在本文的最後一節
。
這條記錄表示,當匹配到 www.caddy-test.com
域名時,返回 IP
地址 127.0.0.1
(本機 IP
)。我們在配置好了 hosts
文件後,我們再次打開 http://www.caddy-test.com/list
(如下圖)
從上圖可以看出,我們此時的頁面是一片空白。這是因爲在解析了域名和端口後,瀏覽器最終訪問到了 127.0.0.1:80
上的 Caddy
服務(我們在第一節的時候運行了 Caddy
),而 Caddy
服務對這條域名的訪問並沒有做配置,無法做出正確響應。接下來,我們將會進行 Caddyfile
的配置。
擴展閱讀:
如果此時訪問
http://www.caddy-test.com:3000/list
(指定端口)會發現頁面可訪問,也可能返回了Invalid Host header
字符串(這是因爲被webpack
自帶的一些安全策略攔截了正確的響應結果,但是我們已經成功訪問到了服務)。這是因爲在指定了端口後,我們訪問的地址就被解析成了
127.0.0.1:3000
,直接訪問指定端口的服務。這樣的方式既不安全(需要暴露可訪問端口),也不優雅(帶個端口號太難記啦)。
配置 Caddyfile
我們現在需要配置我們的 Caddyfile
,配置如下:
http://www.caddy-test.com {
reverse_proxy localhost:3000 {
header_up Host localhost
}
}
由於我們啓動
Caddy
的命令加上了--watch
,所以此時我們的Caddy
將會檢測到Caddyfile
的變化後自動重啓。
我們現在再打開 http://www.caddy-test.com/list
就可以看到,我們的頁面可以正常訪問啦(見下圖)!
從上圖可以看出,在我們訪問我們配置的 測試域名
時,展示了我們在本地 3000
端口運行的服務所返回的頁面,我們的反向代理配置成功啦!
擴展閱讀:
如果我們的域名不是配置在
hosts
文件中,而是註冊在真實的域名註冊機構
,那我們的Caddy
服務就是“真正的”
反向代理服務器啦!
接下來我們對 Caddyfile
配置文件進行逐行解析(見下圖)。
我們來逐行解析一下:
第 10 行
:攔截對http://www.caddy-test.com
這條url
的訪問請求,進行內部邏輯處理;第 11 行
:將攔截的請求
轉發(反向代理)到localhost:3000
(我們的本地服務);第 12 行
:轉發請求時,帶上首部字段Host: localhost
,這一步的目的是爲了通過webpack
自帶的Host
首部安全檢查;
原理解析
其實反向代理的原理和解決跨域問題的原理是一樣的,只是把遠程服務器地址換成了內網地址,所以我們直接用一張長圖來進行解釋吧(見下圖)。
圖有點大,建議點擊查看原圖,這樣可以看到更多細節。
使用 Caddy
部署 SPA - History
路由模式項目
在介紹完了反向代理後,我們來介紹一下如何使用 Caddy
部署 history
路由模式的單頁應用吧。
目前前端的兩種路由模式主要分爲 hash
和 history
模式兩種。hash
模式是指通過地址欄 URL
中的 #
符號區分路由,而 history
模式就是通過路徑 /xxx
來區分路由。
在單頁(SPA
)應用中使用 history
路由模式需要服務器配置支持,我們在開發過程中可以通過 webpack
來配置 history
路由模式。在我們將應用打包後,我們可以通過 Caddy
配置,使我們的 Caddy
服務器支持 history
路由模式的 SPA
應用。
首先,我們在 SPA
應用中配置 history
路由模式,然後使用打包命令 npm run build
(不同技術棧的打包大同小異)將我們的應用打包,最後項目的目錄層級看起來像是這樣的(見下圖)
我們構建好的代碼在 dist
目錄下,Caddyfile
與 dist
同級,接下來我們配置一下 Caddyfile
,配置如下:
http://localhost:3000 {
file_server
root * ./dist
try_files {path} /index.html
}
配置完成後,我們打開瀏覽器,輸入 http://localhost:3000/list
,會發現我們的頁面成功渲染啦(見下圖)!
我們簡單剖析一下這幾行配置(見下圖)
我們來進行逐行解析一下:
第 16 行
:攔截對http://localhost:3000
這條url
的訪問請求,進行內部邏輯處理(在測試或生產環境時,這裏應該配置一個真實域名);第 17 行
:啓用靜態文件服務器;第 18 行
:靜態文件服務器訪問的根目錄在./dist
- 在dist
文件夾外的內容無法訪問;第 19 行
:這行代碼是處理history
路由模式的關鍵 - 如果URL
匹配不到任何靜態資源,將會返回index.html
(解決404
問題);
從上面可以看出,使用 Caddy
部署 history
路由模式的單頁應用還是比較簡單的。這裏還涉及了一些服務器運維的知識,先不作展開啦,有興趣的童鞋可以自己去了解一下。
使用 Caddy
進行負載均衡
使用 Caddy
進行負載均衡也是建立在反向代理的基礎之上,我們將 Demo
分別在三個端口運行(模擬多個服務器運行的多個實例),最後運行效果如下:
從上圖可以看出,我們啓動了三個同樣的 Demo
服務,使用網站的 title
來進行區分。
使用 Caddy
做負載均衡,只需要將多個服務掛在同一個 reverse_proxy
屬性下即可(見下圖)
在配置完成後,我們打開瀏覽器,輸入 http://www.caddy-test.com
,然後多刷新幾次,看看效果(見下圖):
從上面三張圖可以看出,在不斷刷新的過程中,Caddy
自動將我們的請求隨機分流分配到某個服務上,從而達到負載均衡的效果。
注意,實際生產環境的負載均衡要比文中描述的複雜的多,有需要的童鞋最好自己去了解一下。負載均衡並不是本教程的重點,就不作展開討論了。
不同平臺的 hosts
文件配置
如果你知道 hosts
文件如何配置,那麼你可以跳過本節內容~
Mac
Mac 修改 hosts
文件很簡單,使用 vi
命令即可,如下:
# 可能需要 root 權限
sudo vi /etc/hosts
在命令行輸入命令行,鍵盤 i
可進入編輯模式,編輯完成後使用 Esc
鍵退出編輯模式。
最後,同時按下 shift + :
鍵,輸入 wq!
即可保存更改。
Linux
與 Mac
的方法類似,不做複述。
Windows
首先使用 Win + R
鍵喚起 運行
輸入框(如下圖)
然後我們輸入 C:\Windows\System32\drivers\etc\hosts
後按下 確定
按鈕(見下圖)
點擊 確定
按鈕後,選擇使用 記事本
打開,然後進行修改、保存就可以啦(可能需要管理員權限)。
小結
最後,我們使用 Caddy
完成了跨域請求、反向代理、靜態文件服務器、部署 History SPA
應用、負載均衡多種功能。
從上面的案例中我們可以看出,Caddy
在可讀性、可維護性和易用性方面確實做的不錯,通過簡單的學習就可以上手使用。
如果只是用於本地開發、中小型應用,那麼強烈推薦你使用 Caddy
!
如果想要用於複雜的大型項目,那麼建議你可以先參考下面這些資料,再決定是否使用:
最後一件事
如果您已經看到這裏了,希望您還是點個贊再走吧~
您的點贊是對作者的最大鼓勵,也可以讓更多人看到本篇文章!
如果覺得本文對您有幫助,請幫忙在 github 上點亮 star
鼓勵一下吧!