<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從項(xiàng)目實(shí)際問題引發(fā)的思考

          共 5507字,需瀏覽 12分鐘

           ·

          2019-12-08 23:20

          閱讀本文大概需要 5 分鐘。


          沒錯(cuò),轉(zhuǎn)載的公眾號(hào)——崔慶才丨靜覓。

          崔老師靈魂三解答

          1. 該公眾號(hào)為新開的同名個(gè)人公眾號(hào),崔老師打算發(fā)點(diǎn)閱讀量沒那么可愛的純技術(shù)的解 bug 過程和工作感悟。

          2. 崔慶才的名字其實(shí)是“崔慶才丨(gun)靜覓“,中間是中文字。

          3. 崔老師這周末的體重是 139 斤,同比去年這個(gè)時(shí)間胖了 23 斤。讓我們?yōu)樗恼啤?/span>

          d34fc12af8aeca96cc7b8c8ee9fefb38.webp


          最近在開發(fā)過程中遇到了這么一個(gè)問題:

          現(xiàn)在有一個(gè) Web 項(xiàng)目,前端是使用 Vue.js 開發(fā)的,整個(gè)前端需要部署到 K8S 上,后端和前端分開,同樣也需要部署到 K8S 上,因此二者需要打包為 Docker 鏡像。

          對(duì)前端來說,打包 Docker 就遇到了一個(gè)問題:跨域訪問問題。

          因此一個(gè)普遍的解決方案就是使用 Nginx 做反向代理。

          一般來說,我們需要在打包時(shí)配置一下 nginx.conf 文件,然后在 Dockerfile 里面指定即可。

          Dockerfile

          首先看下 Dockerfile:

          # build stageFROM node:lts-alpine as build-stageWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .RUN npm run build
          # production stageFROM nginx:lts-alpine as production-stageCOPY --from=build-stage /app/dist /usr/share/nginx/htmlCOPY nginx.conf /etc/nginx/conf.d/RUN rm /etc/nginx/conf.d/default.conf \&& mv /etc/nginx/conf.d/nginx.conf /etc/nginx/conf.d/default.confEXPOSE 80CMD ["nginx", "-g", "daemon off;"]

          一般來說,對(duì)于常規(guī)的 Vue.js 前端項(xiàng)目,Dockerfile 就這么寫就行了。

          簡(jiǎn)單介紹一下:

          ?第一步,使用 Node.js 鏡像,在 Node.js 環(huán)境下對(duì)項(xiàng)目進(jìn)行編譯,默認(rèn)會(huì)輸出到 dist 文件夾下。?第二步,使用新的 Nginx 鏡像,將編譯得到的前端文件拷貝到 nginx 默認(rèn) serve 的目錄,然后把自定義的 nginx.conf 文件替換為 Nginx 默認(rèn)的 conf 文件,運(yùn)行即可。

          反向代理

          這里比較關(guān)鍵的就是 nginx.conf 文件了,為了解決跨域問題,我們一般會(huì)將后端的接口進(jìn)行反向代理。

          一般來說,后端的 API 接口都是以 api 為開頭的,所以我們需要代理 api 開頭的接口地址,nginx.conf 內(nèi)容一般可以這么寫:

          server {    listen       80;    server_name  localhost;
          ??? location?/api/?{????????proxy_pass?http://domain.com/api/; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; }
          ??? location?/?{ root /usr/share/nginx/html; index index.html index.htm; }
          ????location?=?/50x.html?{ root /usr/share/nginx/html; }
          error_page 404 /404.html; error_page 500 502 503 504 /50x.html;}

          一般來說,以上的寫法是沒有問題的,proxy_set_header 也把一些 Header 進(jìn)行設(shè)置,轉(zhuǎn)發(fā)到后端服務(wù)器。

          如果你這么寫,打包 Docker 之后,測(cè)試沒有遇到問題,那就完事了。

          問題

          但我遇到了一個(gè)奇怪的問題,某個(gè)接口在請(qǐng)求的時(shí)候,狀態(tài)碼還是 200,但其返回值總是為空,即 Response Data 的內(nèi)容完全為空。

          但是服務(wù)器端看 Log 確實(shí)有正常返回 Response,使用 Vue 的 devServer 也是正常的,使用 Postman 來請(qǐng)求也是正常的,但是經(jīng)過 Nginx 這么一反向代理就不行了,什么 Response 都接收不到。

          部署到 Prod 環(huán)境之后,瀏覽器上面可以得到這么個(gè)錯(cuò)誤:

          ERR_INCOMPLETE_CHUNKED_ENCODING
          3eab71abe78830f1085dd703445f3587.webp

          最后經(jīng)排查,發(fā)現(xiàn)后端接口使用時(shí)設(shè)定了?Transfer-Encoding: chunked?響應(yīng)頭:

          Transfer-Encoding: chunked

          這是啥?這時(shí)候就需要引出 Keep-Alive 的相關(guān)問題了。

          什么是 Keep-Alive?

          我們知道 HTTP 協(xié)議采用「請(qǐng)求-應(yīng)答」模式,當(dāng)使用普通模式,即非 Keep-Alive 模式時(shí),每個(gè)請(qǐng)求/應(yīng)答客戶和服務(wù)器都要新建一個(gè)連接,完成之后立即斷開連接(HTTP 協(xié)議為無連接的協(xié)議)。當(dāng)使用 Keep-Alive 模式(又稱持久連接、連接重用)時(shí),Keep-Alive 功能使客戶端到服務(wù)器端的連接持續(xù)有效,當(dāng)出現(xiàn)對(duì)服務(wù)器的后繼請(qǐng)求時(shí),Keep-Alive 功能避免了建立或者重新建立連接。

          ?HTTP 1.0 中默認(rèn)是關(guān)閉 Keep-Alive 的,需要在 HTTP 頭加入Connection: Keep-Alive,才能啟用 Keep-Alive?HTTP 1.1 中默認(rèn)啟用 Keep-Alive,如果請(qǐng)求頭中加入?Connection: close,Keep-Alive 才關(guān)閉。

          目前大部分瀏覽器都是用 HTTP 1.1 協(xié)議,也就是說默認(rèn)都會(huì)發(fā)起 Keep-Alive 的連接請(qǐng)求了,所以是否能完成一個(gè)完整的 Keep-Alive 連接就看服務(wù)器設(shè)置情況。

          啟用 Keep-Alive 模式肯定更高效,性能更高。因?yàn)楸苊饬私?釋放連接的開銷。

          Keep-Alive 模式下如何傳輸數(shù)據(jù)

          Keep-Alive 模式,客戶端如何判斷請(qǐng)求所得到的響應(yīng)數(shù)據(jù)已經(jīng)接收完成呢?或者說如何知道服務(wù)器已經(jīng)發(fā)生完了數(shù)據(jù)?

          我們已經(jīng)知道了,Keep-Alive 模式發(fā)送完數(shù)據(jù),HTTP 服務(wù)器不會(huì)自動(dòng)斷開連接,所有不能再使用返回 EOF(-1)來判斷。

          那么怎么判斷呢?一個(gè)是使用 Content-Length ,一個(gè)是使用 Transfer-Encoding。

          Content-Length

          顧名思義,Conent-Length 表示實(shí)體內(nèi)容長度,客戶端(服務(wù)器)可以根據(jù)這個(gè)值來判斷數(shù)據(jù)是否接收完成。

          由于?Content-Length?字段必須真實(shí)反映實(shí)體長度,但實(shí)際應(yīng)用中,有些時(shí)候?qū)嶓w長度并沒那么好獲得,例如實(shí)體來自于網(wǎng)絡(luò)文件,或者由動(dòng)態(tài)語言生成。這時(shí)候要想準(zhǔn)確獲取長度,只能開一個(gè)足夠大的 buffer,等內(nèi)容全部生成好再計(jì)算。但這樣做一方面需要更大的內(nèi)存開銷,另一方面也會(huì)讓客戶端等更久。

          我們?cè)谧?WEB 性能優(yōu)化時(shí),有一個(gè)重要的指標(biāo)叫 TTFB(Time To First Byte),它代表的是從客戶端發(fā)出請(qǐng)求到收到響應(yīng)的第一個(gè)字節(jié)所花費(fèi)的時(shí)間。大部分瀏覽器自帶的 Network 面板都可以看到這個(gè)指標(biāo),越短的 TTFB 意味著用戶可以越早看到頁面內(nèi)容,體驗(yàn)越好。可想而知,服務(wù)端為了計(jì)算響應(yīng)實(shí)體長度而緩存所有內(nèi)容,跟更短的 TTFB 理念背道而馳。但在 HTTP 報(bào)文中,實(shí)體一定要在頭部之后,順序不能顛倒,為此我們需要一個(gè)新的機(jī)制:不依賴頭部的長度信息,也能知道實(shí)體的邊界。

          但是如果消息中沒有 Conent-Length,那該如何來判斷呢?又在什么情況下會(huì)沒有 Conent-Length 呢?

          Transfer-Encoding

          當(dāng)客戶端向服務(wù)器請(qǐng)求一個(gè)靜態(tài)頁面或者一張圖片時(shí),服務(wù)器可以很清楚地知道內(nèi)容大小,然后通過 Content-length 消息首部字段告訴客戶端需要接收多少數(shù)據(jù)。但是如果是動(dòng)態(tài)頁面等時(shí),服務(wù)器是不可能預(yù)先知道內(nèi)容大小,這時(shí)就可以使用 分塊編碼模式來傳輸數(shù)據(jù)了。即如果要一邊產(chǎn)生數(shù)據(jù),一邊發(fā)給客戶端,服務(wù)器就需要在請(qǐng)求頭中使用Transfer-Encoding: chunked?這樣的方式來代替 Content-Length,這就是分塊編碼。

          分塊編碼相當(dāng)簡(jiǎn)單,在頭部加入?Transfer-Encoding: chunked?之后,就代表這個(gè)報(bào)文采用了分塊編碼。這時(shí),報(bào)文中的實(shí)體需要改為用一系列分塊來傳輸。每個(gè)分塊包含十六進(jìn)制的長度值和數(shù)據(jù),長度值獨(dú)占一行,長度不包括它結(jié)尾的 CRLF(\r\n),也不包括分塊數(shù)據(jù)結(jié)尾的 CRLF。最后一個(gè)分塊長度值必須為 0,對(duì)應(yīng)的分塊數(shù)據(jù)沒有內(nèi)容,表示實(shí)體結(jié)束。

          回歸問題

          那么我說了這么一大通有什么用呢?

          OK,在我遇到的業(yè)務(wù)場(chǎng)景中,我發(fā)現(xiàn)服務(wù)器的響應(yīng)頭中就包含了Transfer-Encoding: chunked?這個(gè)字段。

          而這個(gè)字段,在 HTTP 1.0 是不被支持的。

          而 Nginx 的反向代理,默認(rèn)用的就是 HTTP 1.0,那就導(dǎo)致了數(shù)據(jù)無法獲取的問題,可以參考 Nginx 的官方文檔說明:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass。

          原文中:

          Syntax: proxy_http_version 1.0 | 1.1;Default: proxy_http_version 1.0;By default, version 1.0 is used. Version 1.1 is recommended for use with keepalive connections and NTLM authentication.

          所以,我們?nèi)绻鉀Q這個(gè)問題,只需要設(shè)置一下 HTTP 版本為 1.1 就好了:

          修改 nginx.conf 文件如下:

          location /api/ {    proxy_pass http://domain.com/api/;    proxy_http_version 1.1;    proxy_set_header X-Forwarded-Proto $scheme;    proxy_set_header Host $http_host;    proxy_set_header X-Real-IP $remote_addr;}

          這里就增加了一行:

          proxy_http_version 1.1;

          這樣再測(cè)試,反向代理就會(huì)支持?Transfer-Encoding: chunked?模式了,這也就呼應(yīng)了之前在瀏覽器中遇到的 ERR_INCOMPLETE_CHUNKED_ENCODING 錯(cuò)誤。

          自此,問題完美解決。

          復(fù)盤記錄

          一開始本來只想簡(jiǎn)單一記錄就了事的,但一邊寫,發(fā)現(xiàn)某個(gè)地方還可以展開寫得更詳細(xì)。

          所以干脆最后我對(duì)這個(gè)問題進(jìn)行了詳細(xì)的復(fù)盤和記錄。在寫本文之前,我其實(shí)只思考到了 Keep-Alive 和 HTTP 1.1 的問題,其實(shí)我對(duì) Transfer-Encoding 這個(gè)并沒有去深入思考。在邊寫邊總結(jié)的過程中,為了把整個(gè)脈絡(luò)講明白,我又查詢了一些 Transfer-Encoding 和 Nginx 的官方文檔,對(duì)這塊的了解變得更加深入,相當(dāng)于我在整個(gè)記錄的過程中,又對(duì)整個(gè)流程梳理了一遍,同時(shí)又有額外的收獲。

          所以,遇到問題,深入去思考、總結(jié)和復(fù)盤,是很有幫助的,這會(huì)讓我們對(duì)問題的看法和理解更加透徹。

          怎么說呢?在開發(fā)過程中,難免會(huì)遇到一些奇奇怪怪的 Bug,但這其實(shí)只是技術(shù)問題,總會(huì)解決的。

          但怎樣在開發(fā)過程中,不斷提高自己的技術(shù)能力,我覺得需要從每一個(gè)細(xì)節(jié)出發(fā),去思考一些事情的來龍去脈。思考得越多,我們對(duì)整個(gè)事件的把握也會(huì)越清晰,以后如果再遇到類似的或者關(guān)聯(lián)的事情,就會(huì)迎刃而解了。

          平時(shí)我們可能很多情況下都在寫業(yè)務(wù)代碼,可能比較枯燥,感覺對(duì)技術(shù)沒有實(shí)質(zhì)性的提升,但如果我們能從中提煉出一些核心的問題或解決方案,這才是能真正提高技術(shù)的時(shí)候,這才是最有價(jià)值的。

          參考文章

          本文部分內(nèi)容改寫或摘自下列內(nèi)容。

          ?HTTP Keep-Alive模式:https://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html?Nginx proxy_set_header 理解:https://www.jianshu.com/p/cc5167032525?使用 Docker 打造超溜的前端環(huán)境:https://github.com/axetroy/blog/issues/178?? HTTP 協(xié)議中的 Transfer-Encoding:https://imququ.com/post/transfer-encoding-header-in-http.html

          崔慶才丨靜覓

          隱形字

          同名公眾號(hào)「崔慶才丨靜覓」


          在這里分享自己的一些經(jīng)驗(yàn)、想法和見解。


          c6f22143900d8c6cd95b204c84f42989.webp28e320d72d895296bfbdc704d95dfb73.webp

          長按識(shí)別二維碼關(guān)注

          好文和朋友一起看~
          瀏覽 77
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产日韩二区 | 欧美成人午夜一区二区三区 | 最新国产成人在线观看 | 亚洲黄色片免费看 | 黄射网站 |