Nginx與前端開發

ccfe發表於2018-09-27

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上的網頁內容獲取,再轉發給客戶。具體的流程如下圖。
    nginx-proxy
    nginx-proxy
    概括說:就是客戶端和代理伺服器可以直接互相訪問,屬於一個LAN(區域網);代理對使用者是非透明的,即使用者需要自己操作或者感知得到自己的請求被髮送到代理伺服器;代理伺服器通過代理使用者端的請求來向域外伺服器請求響應內容。
  2. 反向代理 反向代理則正好相反,先看流程圖圖。
    nginx-proxy-reverse
    nginx-proxy-reverse
    反向代理中(事實上,這種情況基本發生在所有的大型網站的頁面請求中),客戶端傳送的請求,想要訪問server伺服器上的內容。但將被髮送到一個代理伺服器proxy,這個代理伺服器將把請求代理到和自己屬於同一個LAN下的內部伺服器上,而使用者真正想獲得的內容就儲存在這些內部伺服器上。看到區別了嗎,這裡proxy伺服器代理的並不是客戶,而是伺服器,即向外部客戶端提供了一個統一的代理入口,客戶端的請求,都先經過這個proxy伺服器,至於在內網真正訪問哪臺伺服器內容,由這個proxy去控制。一般代理是指代理客戶端,而這裡代理的物件是伺服器,這就是“反向”這個詞的意思。Nginx就是來充當這個proxy的作用。 概括說:就是代理伺服器和真正server伺服器可以直接互相訪問,屬於一個LAN(伺服器內網);代理對使用者是透明的,即無感知。不論加不加這個反向代理,使用者都是通過相同的請求進行的,且不需要任何額外的操作;代理伺服器通過代理內部伺服器接受域外客戶端的請求,並將請求傳送到對應的內部伺服器上。
  3. 為什麼要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… 的資料,當在頁面裡直接請求,瀏覽器會報錯:

    Nginx與前端開發
    為了繞開瀏覽器的跨域安全限制,現在需要將請求的域名改成mysite-base.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的方式規避掉了瀏覽器跨域限制,實現了跨域訪問。

  3. 適配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站。如下:

    Nginx與前端開發

  4. 合併請求 前端效能優化中重要一點就是儘量減少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資料夾下有三個檔案,檔案內容很簡單,分別為:

    Nginx與前端開發
    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被合併成一個返回了,如下圖:

    Nginx與前端開發

  5. 圖片處理 在前端開發中,經常需要不同尺寸的圖片。現在的雲儲存基本對圖片都提供有處理服務(一般是通過在圖片連結上加引數)。其實用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; #圖片處理錯誤提示圖,例如縮放引數不是數字
        }
    複製程式碼

    Nginx與前端開發
    這裡只是最基本的配置。此外,可以通過proxy_cache配置Nginx快取,避免每次請求都重新處理圖片,減少Nginx伺服器處理壓力;還以可以通過和nginx-upload-module一起使用加入圖片上傳的功能等。

  6. 頁面內容修改 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;
            }
    }
    複製程式碼

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

總結

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

相關文章