Nginx與前端開發

Nginx與Node.js

“Nginx是一款輕量級的HTTP服務器,採用事件驅動的異步非阻塞處理方式框架,這讓其具有極好的IO性能,時常用於服務端的反向代理和負載均衡。”

作爲前端開發,即使沒用過Nginx,但一定聽說過上面這句話。這句經典的話,基本構成了所有人對Nginx的第一印象。

Nginx發佈於2004年,經過初期幾年的沉澱之後,迅速躥升爲“網紅”,成爲了當年互聯網技術圈最火的詞彙和技術。然而經過多年的發展,到現在,當年的網紅早已“過氣”。因爲如今基本上所有的大型網站都搭建在Nginx之上,Nginx不再是一個什麼新詞,而是互聯網網站搭建的必選技術之一。看到這裏,“HTTP服務器”、“事件驅動”、“異步非阻塞”以及Nginx的網紅經歷,是不是讓前端童鞋們想到了Nodejs?

在工作上,由於工作平臺和語言的原因,對於大部分前端童鞋,更傾向於用Nodejs來搭建服務器,進而實現一些需求,對Nginx有天然的抗拒感。的確,Nginx中的絕大部分功能,如果單純的使用Node.js也可以滿足和實現。但實際上,Nginx和Node.js並不衝突,都有自己擅長的領域:Nginx更擅長於底層服務器端資源的處理(靜態資源處理轉發、反向代理,負載均衡等),Node.js更擅長於上層具體業務邏輯的處理。兩者可以實現完美組合,助力前端開發。

首章最後要說幾句。本文的目的是通過對Nginx的簡單介紹,來讓前端童靴瞭解其實通過Nginx可以強有力地助力前端開發:完全可以把之前Node.js的一些工作放到Nginx上,而不是痛苦地在npm中找包或者造輪子。但實際上,Nginx種看似簡單的配置,實則學問深深。在Nginx實現一個同樣的功能,不同的配置編寫寫法,效率上可能差上好幾倍。而這些完全是在建立在對Nginx原理的深入理解和常年的配置運維經驗上,哪怕是你們公司的後端都可能對Nginx的瞭解並不深入。如果真的想深入學習Nginx,還是找專業的SA或者PE請教吧。

反向代理

什麼是反向代理? 互聯網應用基本都基於CS基本結構,即client端和server端。代理其實就是在client端和真正的server端之前增加一層提供特定服務的服務器,即代理服務器。

  1. 正向代理 反向代理不好理解,正向代理大家總有用過,翻牆工具其實就是一個正向代理工具。它會把 們訪問牆外服務器server的網頁請求,代理到一個可以訪問該網站的代理服務器proxy,這個代理服務器proxy把牆外服務器server上的網頁內容獲取,再轉發給客戶。具體的流程如下圖。

1.jpg

概括說:就是客戶端和代理服務器可以直接互相訪問,屬於一個LAN(局域網);代理對用戶是**非透明**的,即用戶需要自己操作或者感知得到自己的請求被髮送到代理服務器;代理服務器通過**代理用戶端的請求**來向域外服務器請求響應內容。
  1. 反向代理 反向代理則正好相反,先看流程圖圖。

2.jpg

在**反向代理**中(事實上,這種情況基本發生在所有的大型網站的頁面請求中),客戶端發送的請求,想要訪問server服務器上的內容。但將被髮送到一個代理服務器proxy,這個代理服務器將把請求代理到和自己屬於同一個LAN下的內部服務器上,而用戶真正想獲得的內容就儲存在這些內部服務器上。看到區別了嗎,這裏proxy服務器代理的並不是客戶,而是服務器,即向外部客戶端提供了一個統一的代理入口,客戶端的請求,都先經過這個proxy服務器,至於在內網真正訪問哪臺服務器內容,由這個proxy去控制。一般代理是指代理客戶端,而這裏代理的對象是服務器,這就是“反向”這個詞的意思。Nginx就是來充當這個proxy的作用。 概括說:就是代理服務器和真正server服務器可以直接互相訪問,屬於一個LAN(服務器內網);代理對用戶是**透明**的,即無感知。不論加不加這個反向代理,用戶都是通過相同的請求進行的,且不需要任何額外的操作;代理服務器通過**代理內部服務器**接受域外客戶端的請求,並將請求發送到對應的內部服務器上。
  1. 爲什麼要Nginx反向代理 使用反向代理最主要的兩個原因: 1)安全及權限。可以看出,使用反向代理後,用戶端將無法直接通過請求訪問真正的內容服務器,而必須首先通過Nginx。可以通過在Nginx層上將危險或者沒有權限的請求內容過濾掉,從而保證了服務器的安全。 2)負載均衡。例如一個網站的內容被部署在若干臺服務器上,可以把這些機子看成一個集羣,那麼Nginx可以將接收到的客戶端請求“均勻地”分配到這個集羣中所有的服務器上(內部模塊提供了多種負載均衡算法),從而實現服務器壓力的負載均衡。此外,nginx還帶有健康檢查功能(服務器心跳檢查),會定期輪詢向集羣裏的所有服務器發送健康檢查請求,來檢查集羣中是否有服務器處於異常狀態,一旦發現某臺服務器異常,那麼在以後代理進來的客戶端請求都不會被髮送到該服務器上(直到後面的健康檢查發現該服務器恢復正常),從而保證客戶端訪問的穩定性。

前端可以用Nginx做些什麼

下面的內容建立在對Nginx配置有基本認知的情況下。如果沒有的話,請先從網上查閱資料(例如基本配置)做簡單瞭解。如果你想本地安裝Nginx,強烈建議採用源碼編譯安裝,這樣後續添加模塊更爲方便。

  1. 快速實現簡單的訪問限制 經常會遇到希望網站讓某些特定用戶的羣體(比如只讓公司內網)訪問,或者控制某個uri不讓人訪問。Nginx配置如下: location / { deny 192.168.1.100; allow 192.168.1.10/200; allow 10.110.50.16; deny all; } 複製代碼 其實deny和allow是ngx_http_access_module模塊(已內置)中的語法。採用的是從上到下匹配方式,匹配到就跳出不再繼續匹配。上述配置的意思就是,首先禁止192.168.1.100訪問,然後允許192.168.1.10-200 ip段內的訪問(排除192.168.1.100),同時允許10.110.50.16這個單獨ip的訪問,剩下未匹配到的全部禁止訪問。實際生產中,經常和ngx_http_geo_module模塊(可以更好地管理ip地址表,已內置)配合使用。
  2. 解決跨域 在衆多的解決跨域方式中, 都不可避免的都需要服務端進行支持, 使用Nginx可以純前端解決請求跨域問題。 特別是在前後端分離調試時, 經常需要在本地起前端工程, 接口希望拉取服務端的實際數據而不是本地的mock。 而如果本地程序直接訪問遠程接口, 肯定會遇到跨域問題。現在前端成熟的做法,一般是把node proxy server集成進來。事實上,用Nginx同樣可以解決問題,甚至可以應用於線上。 本地起一個nginx server。server_name是mysite-base.com,比如現在需要請求線上www.kaola.com域下的線上接口 www.kaola.com/getPCBanner… 的數據,當在頁面裏直接請求,瀏覽器會報錯:

爲了繞開瀏覽器的跨域安全限制,[現在需要將請求的域名改成mysite-base.com](https://link.juejin.im?target=http%3A%2F%2Fxn--mysite-base-ns3s463ahpctxy2yw6imf04apr3a22lsg1hbllm05d.com)。同時約定一個url規則來表明代理請求的身份,然後Nginx通過匹配該規則,將請求代理回原來的域。Nginx配置如下:

```
    #請求跨域,這裏約定代理請求url path是以/apis/開頭
    location ^~/apis/ {
        # 這裏重寫了請求,將正則匹配中的第一個()中$1的path,拼接到真正的請求後面,並用break停止後續匹配
        rewrite ^/apis/(.*)$ /$1 break;
        proxy_pass https://www.kaola.com/;
    }  
複製代碼
```

在頁面代碼裏,把請求url換成http://mysite-base.com/apis/getPCBannerList.html 。這樣就可以正常請求到數據。 這樣其實是通過nginx,用類似於hack的方式規避掉了瀏覽器跨域限制,實現了跨域訪問。
  1. 適配PC與移動環境 現在很多網站都存在PC站和H5站兩個站點,因此根據用戶的瀏覽環境自動切換站點是很常見的需求。Nginx可以通過內置變量$http_user_agent,獲取到請求客戶端的userAgent,從而知道用戶處於移動端還是PC,進而控制重定向到H5站還是PC站。 以筆者本地爲例,pc端站點是mysite-base.comH5端是mysite-base-H5.com。pc端Nginx配置如下: location / { # 移動、pc設備適配 if ($http_user_agent ~* '(Android|webOS|iPhone|iPod|BlackBerry)') { set $mobile_request '1'; } if ($mobile_request = '1') { rewrite ^.+ http://mysite-base-H5.com; } } 複製代碼 這樣當瀏覽設備切換成移動模式,再次刷新頁面後,站點被自動切換到H5站。如下:

4.gif

  1. 合併請求 前端性能優化中重要一點就是儘量減少http資源請求的數量。通過nginx-http-concat模塊(淘寶開發的第三方模塊,需要單獨安裝)用一種特殊的請求url規則(例子:example.com/??1.js,2.js,3.js ),前端可以將多個資源的請求合併成一個請求,後臺Nginx會獲取各個資源並拼接成一個結果進行返回。例如上面的例子通過一個請求將1.js,2.js,3js三個js資源合併成一個請求,減少了瀏覽器開銷。 本地server mysite-base.com爲例,static/js文件夾下有三個文件,文件內容很簡單,分別爲:

3.jpg

Nginx配置如下:

```
    # js資源http-concat
    # nginx-http-concat模塊的參數遠不止下面三個,剩下的請查閱文檔
    location /static/js/ {
        concat on; # 是否打開資源合併開關
        concat_types application/javascript; # 允許合併的資源類型
        concat_unique off; # 是否允許合併不同類型的資源
        concat_max_files 5; # 允許合併的最大資源數目
    }
複製代碼
```

當在瀏覽器請求http://mysite-base.com/static/js/??a.js,b.js,c.js 時,發現三個js被合併成一個返回了,如下圖:[](https://link.juejin.im?target=www.romanysoft.com)

5.jpg

  1. 圖片處理 在前端開發中,經常需要不同尺寸的圖片。現在的雲儲存基本對圖片都提供有處理服務(一般是通過在圖片鏈接上加參數)。其實用Nginx,可以通過幾十行配置,搭建出一個屬於自己的本地圖片處理服務,完全能夠滿足日常對圖片的裁剪/縮放/旋轉/圖片品質等處理需求。要用到ngx_http_image_filter_module模塊。這個模塊是非基本模塊,需要安裝。 下面是圖片縮放功能部分的Nginx配置: # 圖片縮放處理 # 這裏約定的圖片處理url格式:以 mysite-base.com/img/路徑訪問 location ~* /img/(.+)$ { alias /Users/cc/Desktop/server/static/image/$1; #圖片服務端儲存地址 set $width -; #圖片寬度默認值 set $height -; #圖片高度默認值 if ($arg_width != "") { set $width $arg_width; } if ($arg_height != "") { set $height $arg_height; } image_filter resize $width $height; #設置圖片寬高 image_filter_buffer 10M; #設置Nginx讀取圖片的最大buffer。 image_filter_interlace on; #是否開啓圖片圖像隔行掃描 error_page 415 = 415.png; #圖片處理錯誤提示圖,例如縮放參數不是數字 } 複製代碼

1.gif

這裏只是最基本的配置。此外,可以通過proxy_cache配置Nginx緩存,避免每次請求都重新處理圖片,減少Nginx服務器處理壓力;還以可以通過和[nginx-upload-module](https://link.juejin.im?target=http%3A%2F%2Fwww.grid.net.ru%2Fnginx%2Fupload.en.html "nginx-upload-module")一起使用加入圖片上傳的功能等。
  1. 頁面內容修改 Nginx可以通過向頁面底部或者頂部插入額外的css和js文件,從而實現修改頁面內容。這個功能需要額外模塊的支持,例如:nginx_http_footer_filter或者ngx_http_addition_module (都需要安裝)。 工作中,經常需要切換各種測試環境,而通過switchhosts等工具切換後,有時還需要清理瀏覽器dns緩存。可以通過頁面內容修改+Nginx反向代理來實現輕鬆快捷的環境切換。 這裏首先在本地編寫一段js代碼(switchhost.js),裏面的邏輯是:在頁面插入hosts切換菜單以及點擊具體某個環境時,將該host的ip和hostname儲存在cookie中,最後刷新頁面;接着編寫一段css代碼(switchhost.css)用來設置該hosts切換菜單的樣式。 然後Nginx腳本配置: server { listen 80; listen 443 ssl; expires -1; # 想要代理的域名 server_name m-element.kaola.com; set $root /Users/cc/Desktop/server; charset utf-8; ssl_certificate /usr/local/etc/nginx/m-element.kaola.com.crt; ssl_certificate_key /usr/local/etc/nginx/m-element.kaola.com.key; # 設置默認$switch_host,一般默認爲線上host,這裏的1.1.1.1隨便寫的 set $switch_host '1.1.1.1'; # 設置默認$switch_hostname,一般默認爲線上'online' set $switch_hostname ''; # 從cookie中獲取環境ip if ($http_cookie ~* "switch_host=(.+?)(?=;|$)") { set $switch_host $1; } # 從cookie中獲取環境名 if ($http_cookie ~* "switch_hostname=(.+?)(?=;|$)") { set $switch_hostname $1; } location / { expires -1; index index.html; proxy_set_header Host $host; #把html頁面的gzip壓縮去掉,不然sub_filter無法替換內容 proxy_set_header Accept-Encoding ''; #反向代理到實際服務器ip proxy_pass http://$switch_host:80; #全部替換 sub_filter_once off; #ngx_http_addition_module模塊替換內容。 # 這裏在頭部插入一段css,內容是hosts切換菜單的css樣式 sub_filter '</head>' '</head><link rel="stylesheet" type="text/css" media="screen" href="/local/switchhost.css" />'; #將頁面中的'網易考拉'文字後面加上環境名,便於開發識別目前環境 sub_filter '網易考拉' '網易考拉:${switch_hostname}'; #這裏用了另一個模塊nginx_http_footer_filter,其實上面的模塊就行,只是爲了展示用法 # 最後插入一段js,內容是hosts切換菜單的js邏輯 set $injected '<script language="javascript" src="/local/switchhost.js"></script>'; footer '${injected}'; } # 對於/local/請求,優先匹配本地文件 # 所以上面的/local/switchhost.css,/local/switchhost.js會從本地獲取 location ^~ /local/ { root $root; } } 複製代碼

2.gif 這個功能其實爲Nginx在前端開發中的應用提供了無限可能。例如,可以通過區分本地、測試和線上環境,爲本地/測試環境頁面增加很多開發輔助功能:給本地頁面加一個常駐二維碼便於手機端掃碼調試;本地調試線上頁面時,在js文件底部塞入sourceMappingURL,便於本地debug等等。

總結

上述只是通過一些簡單的小例子,希望能夠引起廣大前端童靴對Niginx的興趣。事實上,Nginx不僅僅侷限於這些微小的工作,在實際生產中作用其實更加巨大。對於有志於“大前端”的童靴,瞭解和熟悉Nginx絕對是必修技能之一。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章