前言
Nginx(發(fā)音同“engine X”)是異步框架的網(wǎng)頁(yè)服務(wù)器,也可以用作反向代理、負(fù)載平衡器和HTTP緩存。
本文將講述如何使用 Nginx 在 Web 前后端分離開(kāi)發(fā)中實(shí)現(xiàn)路由的轉(zhuǎn)發(fā)。
Web 開(kāi)發(fā)通常使用的是前后端分離的開(kāi)發(fā)模式,即前端和后端分別進(jìn)行開(kāi)發(fā),前端通過(guò) Ajax 請(qǐng)求后端的接口,將獲取數(shù)據(jù)將數(shù)據(jù)渲染到頁(yè)面上。前端開(kāi)發(fā)會(huì)使用腳手架搭建前端開(kāi)發(fā)環(huán)境,其底層通常會(huì)啟動(dòng)一個(gè)本地服務(wù)器,通常使用的是 nodejs 的 Express 框架。而后端則是提供接口,一般是放在線上的一個(gè)開(kāi)發(fā)用的域名下。
這在開(kāi)發(fā)過(guò)程中會(huì)導(dǎo)致 跨域 問(wèn)題,即在一個(gè)域名下的網(wǎng)頁(yè),是無(wú)法通過(guò) Ajax 請(qǐng)求另一個(gè)(不同源)域名下的接口 API 的。這是瀏覽器的同源策略,是瀏覽器的一個(gè)非常重要的安全策略。
解決這個(gè)問(wèn)題的其中一個(gè)方案是使用 代理。具體來(lái)說(shuō),就是在本地啟動(dòng)一個(gè)服務(wù)器(如 localhost:4000),發(fā)送給該服務(wù)器的請(qǐng)求會(huì)根據(jù)請(qǐng)求路由(比如判斷 url 是否有前綴 /api)進(jìn)行轉(zhuǎn)發(fā),分別轉(zhuǎn)發(fā)到前端開(kāi)發(fā)的服務(wù)器(如 localhost:3000),以及后端服務(wù)器(比如 dev.yoursite.com)。這樣通過(guò)一個(gè)代理服務(wù)器,因?yàn)檎?qǐng)求的 api 都是同一個(gè)域名下的,自然就不會(huì)造成跨域問(wèn)題,從而導(dǎo)致請(qǐng)求失敗。
下面我們就來(lái)講解如何使用 Nginx 來(lái)實(shí)現(xiàn)反向代理。
簡(jiǎn)單認(rèn)識(shí) Nginx 配置文件
安裝好 Nginx 后,我們需要確定下 Nginx 的默認(rèn)配置文件的位置。執(zhí)行命令 nginx -t,該命令會(huì)檢測(cè) nginx 的默認(rèn)配置文件語(yǔ)法是否正確,并進(jìn)行測(cè)試,最后輸出結(jié)果。我們可以從輸出中得到默認(rèn)配置文件所在的位置。
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
還有另外一種方法可以得到默認(rèn)配置文件的位置,那就是執(zhí)行 nginx -h。該命令會(huì)輸出 nginx 的簡(jiǎn)易幫助文檔,其中的 -c filename 的配置項(xiàng)說(shuō)明也指出了默認(rèn)配置項(xiàng)的路徑。
-c filename : set configuration file (default: /etc/nginx/nginx.conf)
通過(guò)這個(gè)文檔,我們也可以知道,使用 -c 配置項(xiàng)可以自定義配置文件。如果不指定文件,使用默認(rèn)配置文件。
下面我們來(lái)修改一下 Nginx 到這個(gè)默認(rèn)配置文件 nginx.config 來(lái)首先代理功能。
nginx.config 文件的 http 后面的代碼塊中,應(yīng)該會(huì)有類似下面這行的代碼:
include /etc/nginx/conf.d/*.conf;
這行代碼的作用是將 /etc/nginx/conf.d 目錄下的后綴為 .conf 的文件內(nèi)容嵌入到引入位置中,作為配置的一部分執(zhí)行。
如果你是在 macOS 安裝的 Nginx,可能會(huì)有點(diǎn)不同。我使用 brew 安裝的 Nginx 為為 include servers/*;,對(duì)應(yīng)嵌入的是 servers 目錄下的所有文件。
為什么會(huì)用到這種嵌入的語(yǔ)法呢?因?yàn)檫@樣我們就可以將不同項(xiàng)目需要用到的配置放到不同的配置文件里,好處就是可以快速地找到對(duì)應(yīng)項(xiàng)目要修改的配置文件,不用擔(dān)心不小心修改了其他項(xiàng)目的配置。另外如果直接在 nginx.conf 上修改,使其變得臃腫。這符合設(shè)計(jì)模式的 單一職責(zé)原則。
此外,你可以會(huì)奇怪conf.d 目錄的命名為什么要加上 .d ?如果你使用 Linux 過(guò)一段時(shí)間,你會(huì)發(fā)現(xiàn)某些目錄或文件的末尾會(huì)加上一個(gè) d,比如 httpd、crond、vsftpd 等。其實(shí)這是為了說(shuō)明這些文件都屬于是 daemon(服務(wù))。這里的服務(wù)指的是系統(tǒng)的服務(wù),主要分為系統(tǒng)本身需要的服務(wù),以及負(fù)責(zé)網(wǎng)絡(luò)的服務(wù)。我們的 conf.d 就是屬于后者。
編寫 Nginx 配置文件
我們?cè)?conf.d 目錄下創(chuàng)建名為 demo.conf 的文件,寫入以下內(nèi)容,然后啟動(dòng) Nginx。
server {
listen 5000;
server_name localhost;
location / {
proxy_pass http://localhost:3000;
}
location /api/ {
proxy_pass http://localhost:4000;
}
}
該配置啟用了 localhost:5000 的服務(wù)器,將 localhost:5000 下開(kāi)頭為 /api/ url 請(qǐng)求代理到了 localhost:4000(后端接口服務(wù)器)。其他請(qǐng)求則是代理到 localhost:3000(前端)。下面我們具體解析一下配置文件里面內(nèi)容的作用。
listen 設(shè)置了服務(wù)器的端口號(hào),server_name 則設(shè)置了主機(jī)名。
location
location 表示進(jìn)行路由的匹配,如果匹配則執(zhí)行對(duì)應(yīng)代碼塊里的操作。location 可以使用 前綴匹配 以及 正則匹配(需要以 ~* 或 ~ 開(kāi)頭)。我們這里的配置使用的是前綴匹配。
這里有個(gè)點(diǎn)需要注意一下,Nginx 的路由匹配和一般的按順序匹配第一個(gè)的路由匹配方案(比如后端的 gin、前端的 vue-router 的路由匹配方案)不同,nginx 匹配路由的方式為:
- 首先進(jìn)行前綴匹配,遍歷所有的前綴匹配,從中選擇前綴匹配最長(zhǎng)的;
- 然后會(huì)進(jìn)行正則匹配,在所有正則匹配中,從前往后選擇第一個(gè)符合的;
- 如果能找到匹配的正則匹配,使用其對(duì)應(yīng)的配置;如果沒(méi)有,則使用之前找到的那個(gè)最長(zhǎng)的前綴匹配對(duì)應(yīng)的配置。
所以,當(dāng)請(qǐng)求為 localhost:5000/api/xx 時(shí), / 和 /api/ 都能夠前綴匹配。根據(jù)規(guī)則,雖然位置更靠前的 / 也符合前綴匹配,但 /api 更長(zhǎng),所以最終匹配的是 /api。
proxy_pass
確定好匹配的 location 后,我們?cè)倏纯?proxy_pass 又做了什么操作。proxy_pass 用于將請(qǐng)求路由映射到指定的協(xié)議和地址。本質(zhì)是將發(fā)送給 Nginx 的請(qǐng)求處理并發(fā)送到另一個(gè)服務(wù)器,然后將返回的數(shù)據(jù)作為 Nginx的返回?cái)?shù)據(jù)返回。
proxy_pass 后如果使用的是 URI(端口后面至少有一個(gè) /),那么 Nginx 就會(huì) 替換 掉 location 匹配的那部分字符。
listen 5000;
server_name localhost;
location /name/ {
proxy_pass http://127.0.0.1/remote/;
}
# localhost:5000/name/fstar
# 會(huì)被映射請(qǐng)求為
# 127.0.0.1/remote/fstar
可以看到,/name/ 的部分在映射時(shí)被移除(或者說(shuō)是替換)了。
proxy_pass 后如果使用的是不是 URI(端口后沒(méi)有任何東西),Nginx 會(huì)將源請(qǐng)求完全映射到代理服務(wù)上:
listen 5000;
server_name localhost;
location /some/path/ {
proxy_pass http://127.0.0.1;
}
# localhost:5000/some/path/x/y
# 會(huì)被映射請(qǐng)求為
# 127.0.0.1/some/path/x/y
這里的 /some/path 并沒(méi)有被移除。
我們的 demo.conf 文件的 proxy_pass 使用的不是 URI,所以是將路由完全映射到另一個(gè)服務(wù)。
思考題
請(qǐng)問(wèn),下面有兩段配置(區(qū)別是 proxy_pass 結(jié)尾是否有 /)?如果請(qǐng)求 /kite/api/xx,分別會(huì)映射為什么?
location /kite/api/ {
proxy_pass http://localhost:5000;
}
location /kite/api/ {
proxy_pass http://localhost:5000/;
}
前面我們講 proxy_pass 的時(shí)候說(shuō)過(guò),proxy_pass 后面如果不是 URI,會(huì)正常轉(zhuǎn)發(fā);如果是 URI,就移除 location 匹配的前綴再進(jìn)行轉(zhuǎn)發(fā),體現(xiàn)的是替換路由的效果。上面這兩個(gè)配置的區(qū)別就在于末尾的這個(gè) /,有 / 是 URI,沒(méi)有的不是 URI,從而導(dǎo)致完全不一樣的結(jié)果,依次分別為:
http://localhost:5000/kite/api/xx
http://localhost:5000/xx
所以,在寫 Nginx 配置的時(shí)候,一定要注意端口后面的 / 是否有必要保留。因?yàn)樗挠袩o(wú)會(huì)導(dǎo)致兩種截然不同的效果。
參考文章
- Nginx 官方文檔
- stackoverflow - How do I rewrite URLs in a proxy response in NGINX
總結(jié)
到此這篇關(guān)于利用Nginx代理如何解決前端跨域問(wèn)題的文章就介紹到這了,更多相關(guān)Nginx代理解決前端跨域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!