最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【前端大佬 | Node 連載 5/9】涂鴉 - 龍野:《如何用 Node 建設(shè)企業(yè)級(jí)應(yīng)用網(wǎng)關(guān)》

2023-04-14 19:00 作者:前端早早聊  | 我要投稿

第 63 屆早早聊大會(huì)將于 2023 年 4 月 15 日(本周六)舉辦 - 低代碼搭建|無(wú)碼有碼 全靠拖拉,6 位講師全天直播,關(guān)鍵詞:低代碼/組件物料/游戲?qū)嵺`/ AB Test /React 玩轉(zhuǎn)。跟早早聊一起,學(xué)習(xí)低碼搭建,上車鏈接:https://www.zaozao.run/conf/c63


本文是 2021 年 12 月 26 日,第三十五屆 - 前端早早聊【前端搞 Node.js】專場(chǎng),來(lái)自涂鴉的大前端基礎(chǔ)建設(shè)團(tuán)隊(duì) —— 龍野的分享。感謝 AI 的發(fā)展,借助 GPT 的能力,最近我們終于可以非常高效地將各位講師的精彩分享文本化后,分享給大家。(完整版含演示請(qǐng)看錄播視頻和 PPT):https://www.zaozao.run/video/c35



正文如下


大家好,我是涂鴉智能的龍野,目前在團(tuán)隊(duì)中主要負(fù)責(zé)前端網(wǎng)關(guān)和 Node 這個(gè)語(yǔ)言基礎(chǔ)方向的一些建設(shè)。今天我分享的主題是《如何用 Node 建設(shè)企業(yè)級(jí)應(yīng)用網(wǎng)關(guān)》。


背景


前端網(wǎng)關(guān)的背景可以追溯到幾年前,當(dāng)微服務(wù)剛剛出現(xiàn)時(shí),我們?cè)?Java 技術(shù)體系中有一張架構(gòu)圖。


整個(gè)微服務(wù)大致是這樣的,外部請(qǐng)求從用戶端進(jìn)來(lái)后,先經(jīng)過(guò)負(fù)載均衡器,再到達(dá)一個(gè)外部網(wǎng)關(guān),最后被轉(zhuǎn)發(fā)到集群內(nèi)部的各種服務(wù),不論是 Java 還是其他語(yǔ)言的服務(wù)。在集群內(nèi)部可能存在許多各式各樣的服務(wù),服務(wù)之間的上下層劃分并不是非常明確。因此,外部網(wǎng)關(guān)到內(nèi)部服務(wù)之間的邊界并不是特別清晰。

當(dāng)某個(gè)服務(wù)出現(xiàn)問(wèn)題時(shí),外部網(wǎng)關(guān)路由層代理到內(nèi)部時(shí),我們需要定位或排查問(wèn)題時(shí)可能會(huì)比較復(fù)雜。而且,如果這個(gè)服務(wù)出現(xiàn)問(wèn)題后,需要迅速發(fā)布一個(gè)修復(fù)版本,由于各個(gè)公司的發(fā)布系統(tǒng)穩(wěn)定性依賴于整個(gè)流程,可能需要較長(zhǎng)的時(shí)間。

所以,我們希望能夠在外部網(wǎng)關(guān)到內(nèi)部服務(wù)之間架設(shè)一個(gè)業(yè)務(wù)方向的網(wǎng)關(guān),這就是現(xiàn)在 BFF 網(wǎng)關(guān)產(chǎn)生的背景。類似于現(xiàn)在的前置 BFF 網(wǎng)關(guān),它是掛在外部網(wǎng)關(guān)之后,并且掛在內(nèi)部服務(wù)集群的業(yè)務(wù)之前的一中間的代理層。這層代理層也就是聚合服務(wù),即 BFF 網(wǎng)關(guān)最初產(chǎn)生的背景,它的目的是想要將不同的上游服務(wù)的數(shù)據(jù)聚合后反饋給用戶。

在這個(gè)基礎(chǔ)服務(wù)層中,我們可以擴(kuò)展其功能。


  1. 當(dāng)上游的某個(gè)服務(wù)出現(xiàn)問(wèn)題時(shí),可以在 BFF 網(wǎng)關(guān)中進(jìn)行兜底操作,避免請(qǐng)求到達(dá)出現(xiàn)問(wèn)題的服務(wù)。

  2. BFF 網(wǎng)關(guān)可以統(tǒng)計(jì)針對(duì)某個(gè)業(yè)務(wù)方向的流量,同時(shí)對(duì)惡意請(qǐng)求進(jìn)行過(guò)濾。

  3. 引入了 BFF 網(wǎng)關(guān),用于在常用的服務(wù)中進(jìn)行封裝和泛化,以處理不同的調(diào)用方法。業(yè)務(wù)方只需在網(wǎng)關(guān)中配置一個(gè)通用的接口,并使用通用的配置方式,即可請(qǐng)求不同的服務(wù)。

  4. BFF 網(wǎng)關(guān)還具有聚合接口數(shù)據(jù)的功能,以減少前端瀏覽器發(fā)起的請(qǐng)求數(shù)量,提高頁(yè)面的流暢度。

  5. BFF 網(wǎng)關(guān)可以通過(guò)配置實(shí)現(xiàn)多接口的聚合,使前端瀏覽器只需通過(guò)一次請(qǐng)求即可獲得多個(gè)接口的數(shù)據(jù)。

  6. 我們封裝了一些公共能力,通過(guò)網(wǎng)關(guān)插件,允許對(duì)業(yè)務(wù)有更深了解的人直接編寫網(wǎng)關(guān)插件以解決業(yè)務(wù)問(wèn)題,降低業(yè)務(wù)開(kāi)發(fā)接入的復(fù)雜度。

  7. 除此之外,使用這種網(wǎng)關(guān)還有一些其他附加價(jià)值。

  1. 例如,具有數(shù)據(jù)偽造功能,網(wǎng)關(guān)可以通過(guò)配置直接提供假數(shù)據(jù),在云端或服務(wù)端暫時(shí)無(wú)法提供實(shí)際數(shù)據(jù)環(huán)境下進(jìn)行聯(lián)調(diào);

  2. 自帶服務(wù)發(fā)現(xiàn)能力,用戶只需知道網(wǎng)關(guān)地址,即可請(qǐng)求網(wǎng)關(guān),無(wú)需關(guān)心網(wǎng)關(guān)內(nèi)部的路由和數(shù)據(jù)來(lái)源;

  3. 緩存能力,如果在特殊場(chǎng)景下這些服務(wù)出現(xiàn)了問(wèn)題,我們可以返回上一次正常響應(yīng)的頁(yè)面。


今天要講的內(nèi)容一共分為以下幾個(gè)部分,后面我們將按照這個(gè)順序進(jìn)行簡(jiǎn)要講解。


歡迎報(bào)名第 63 屆早早聊大會(huì) - 低代碼無(wú)代碼,學(xué)習(xí)低代碼的應(yīng)用與實(shí)踐新玩法,上車戳:https://www.zaozao.run/conf/c63


網(wǎng)關(guān)基本架構(gòu)


我們簡(jiǎn)單聊一下 BFF 網(wǎng)關(guān)的架構(gòu)。


作為內(nèi)部用戶,對(duì)于網(wǎng)關(guān),我希望它能實(shí)現(xiàn)請(qǐng)求到達(dá)網(wǎng)關(guān)后代理到特定的服務(wù),從而完成我要做的事情。但是業(yè)務(wù)的網(wǎng)關(guān)需求較為復(fù)雜,不僅僅是簡(jiǎn)單的代理。如果只是簡(jiǎn)單的代理,那么直接部署一個(gè) Nginx 不就好了嗎?

我們希望做的是最初的設(shè)想,即具備動(dòng)態(tài)化配置 Nginx 的能力。默認(rèn)情況下,Nginx 的規(guī)則是寫在 Nginx 的 conf 文件中的,但是如果要?jiǎng)討B(tài)修改這些規(guī)則怎么辦呢?了解過(guò) Nginx 配置的同學(xué)應(yīng)該知道,修改完后執(zhí)行 reload 命令就可以使新的配置生效。

那么作為一個(gè)開(kāi)發(fā)人員而不是運(yùn)維人員,我應(yīng)該如何修改這個(gè) Nginx 配置呢?社區(qū)中有很多類似的實(shí)踐,其中一個(gè)典型的例子就是將 Nginx 的配置寫入到 Consul 中,Consul 會(huì)以文件的形式將配置下發(fā)到 Nginx 特定的目錄。這樣,Nginx 可以通過(guò)執(zhí)行 Nginx 的 reload 命令來(lái)加載新的配置。我們最初設(shè)計(jì)網(wǎng)關(guān)時(shí)的想法實(shí)際上就是這樣的。我們希望用戶能夠自定義他們的規(guī)則,并將這些規(guī)則下發(fā)到線上的各種環(huán)境中。因此,我們先開(kāi)發(fā)了一個(gè)網(wǎng)關(guān)后臺(tái)系統(tǒng),內(nèi)部用戶可以在后臺(tái)系統(tǒng)中配置代理規(guī)則。然后,我們對(duì)這些規(guī)則進(jìn)行了抽象,引入了項(xiàng)目的概念,項(xiàng)目下面包含了路由,這些路由函數(shù)的邏輯由我們自己來(lái)定義。用戶可以將這些配置和數(shù)據(jù)寫入線上特定環(huán)境的持久化存儲(chǔ)中。

在這種情況下,我們已經(jīng)解決了內(nèi)部鏈路的問(wèn)題。當(dāng)外部請(qǐng)求進(jìn)入 ?BFF 網(wǎng)關(guān)時(shí),它應(yīng)該執(zhí)行怎樣的代理邏輯呢?

直接從 MySQL 中讀取數(shù)據(jù)顯然不合適,因?yàn)榫W(wǎng)關(guān)通常會(huì)處理大量的流量,而依賴于一個(gè) IO 型的數(shù)據(jù)庫(kù)會(huì)導(dǎo)致性能問(wèn)題。因此,我們進(jìn)行了發(fā)布操作,將數(shù)據(jù)從 MySQL 直接發(fā)送到線上的 Redis 中,因?yàn)?Redis 的讀寫速度非常高,可以支持足夠的業(yè)務(wù)請(qǐng)求。因此,網(wǎng)關(guān)的所有依賴配置都是從 Redis 中讀取的,這樣可以確保在高并發(fā)的情況下,網(wǎng)關(guān)仍然能夠處理足夠的業(yè)務(wù)請(qǐng)求。

對(duì)于正常的外部請(qǐng)求,我們這里有一個(gè)簡(jiǎn)單的流程圖。


在這種情況下,外部請(qǐng)求首先經(jīng)過(guò)你的 DNS 解析,DNS 解析會(huì)將其轉(zhuǎn)發(fā)到特定的 LB(負(fù)載均衡)。根據(jù)這個(gè) LB,請(qǐng)求將被代理到特定的 Nginx 和 Ingress。

一旦請(qǐng)求被代理到 Nginx,Nginx 將根據(jù)配置規(guī)則進(jìn)行處理。在這個(gè)配置中,你可以決定將這種請(qǐng)求代理到集群內(nèi)部的網(wǎng)關(guān)服務(wù),或者是其他服務(wù),因?yàn)橛行I(yè)務(wù)方可能希望將請(qǐng)求代理到自己的服務(wù),而不需要經(jīng)過(guò)網(wǎng)關(guān)服務(wù)。這種情況通常發(fā)生在前端業(yè)務(wù)或者與前端相關(guān)的業(yè)務(wù)中。如果我是一個(gè)云端業(yè)務(wù),我肯定不會(huì)特意走網(wǎng)關(guān)。一旦你的業(yè)務(wù)請(qǐng)求到達(dá)網(wǎng)關(guān),網(wǎng)關(guān)將在運(yùn)行時(shí)執(zhí)行特定的邏輯,匹配到之前存儲(chǔ)在 Redis 中的配置,并根據(jù)規(guī)則進(jìn)行處理。規(guī)則可以包括直接代理到像 Nginx 這樣的代理服務(wù),或者請(qǐng)求云端服務(wù),以及從請(qǐng)求中獲取哪些數(shù)據(jù)等。這些規(guī)則可以通過(guò)使用 Node.js 編寫代碼的方式由用戶自定義,這也是我們網(wǎng)關(guān)的一個(gè)重要目標(biāo),即賦予前端云端服務(wù)端開(kāi)發(fā)的能力。

接下來(lái),我們將簡(jiǎn)單地看一下接下來(lái)要關(guān)注的幾個(gè)要點(diǎn)。

  1. 用戶在后臺(tái)可以進(jìn)行配置項(xiàng)目的管理,包括路由的配置和插件相關(guān)的配置。而這些配置的管理流程是怎樣的?

  2. 在運(yùn)行時(shí),我們需要解析路由代碼和插件代碼,運(yùn)行時(shí)的執(zhí)行流程是怎樣的?

  3. 插件的管理和正常代碼的管理可能存在差異,需要關(guān)注運(yùn)行時(shí)如何處理插件代碼。

  4. 為了提供更好的用戶體驗(yàn),目前我們使用 JSON 作為用戶編寫路由函數(shù)的替代方式。我們需要關(guān)注如何將 JSON 解析為 TS 代碼的邏輯。

  5. Docker 出現(xiàn)后的好處之一是隔離性,即多個(gè)實(shí)例運(yùn)行在虛擬機(jī)中環(huán)境是獨(dú)立的。在網(wǎng)關(guān)場(chǎng)景下,我們也需要關(guān)注這一點(diǎn),甚至在未來(lái)的其他場(chǎng)景下,都需要關(guān)注代碼的動(dòng)態(tài)執(zhí)行能力。如果涉及到動(dòng)態(tài)執(zhí)行代碼,需要特別關(guān)注安全性和資源使用情況。

無(wú)碼有碼,全靠拖拉。跟早早聊一起,學(xué)習(xí)低代碼搭建,上車鏈接:https://www.zaozao.run/conf/c63



后臺(tái)管理系統(tǒng)


在我們公司的網(wǎng)關(guān)后臺(tái)管理頁(yè)面中,可以看到項(xiàng)目的域名配置。這個(gè)域名會(huì)被解析到 Nginx,然后再到網(wǎng)關(guān)。域名的解析方式可以使用泛解析,例如 *.xxx.com 的方式。默認(rèn)情況下,請(qǐng)求會(huì)解析到 fast 網(wǎng)關(guān),也就是我們的 BFF 網(wǎng)關(guān)。


這些路由像你寫代碼的時(shí)候,Controller 層的一個(gè)一個(gè)的方法,它把這些方法寫到運(yùn)行時(shí)里面,運(yùn)行時(shí)就是 BFF 網(wǎng)關(guān),運(yùn)行時(shí)會(huì)解析請(qǐng)求,然后根據(jù)配置進(jìn)行代理。我們簡(jiǎn)單地新建一條路由,試一試這種配置方式。

路由的規(guī)則是默認(rèn)的,用戶需要查看網(wǎng)關(guān)相關(guān)的文檔,才能知道這些東西應(yīng)該如何配置。比如說(shuō)在這個(gè)地方配置一條 GET 請(qǐng)求的路由,我正常訪問(wèn)的時(shí)候,就可以響應(yīng)一個(gè) "hello"。

至于這里面的這些代碼運(yùn)行時(shí),我們也提供了一些依賴服務(wù)供給用戶使用,包括一些鏈路相關(guān)的數(shù)據(jù),還有一些全局變量和一些工具類的函數(shù)。這樣用戶在寫這些路由的時(shí)候相對(duì)來(lái)說(shuō)會(huì)比較方便。

接下來(lái),我們簡(jiǎn)單看一下項(xiàng)目的列表頁(yè)面。


需要提一下,由于我們公司是一個(gè)全球化的業(yè)務(wù)場(chǎng)景,為了讓用戶在使用時(shí)不需要訪問(wèn)多個(gè)系統(tǒng),以降低使用成本,我們開(kāi)發(fā)了一個(gè)功能,讓用戶可以在一個(gè)系統(tǒng)上將數(shù)據(jù)配置分發(fā)到各個(gè)國(guó)家的數(shù)據(jù)庫(kù)或 Redis 中。這種方式在安全性方面需要考慮一些因素,需要與公司的安全部門商討,考慮各國(guó)家的政策,以確定數(shù)據(jù)是否能夠傳輸?shù)讲煌貐^(qū),具體根據(jù)各公司的情況而定。

除了之前提到的列表模式之外,我們還為用戶提供了一個(gè)基于 Web 的 IDE 編輯模式。

這種編輯模式類似于使用 Web IDE 進(jìn)行編輯,編寫路由、網(wǎng)關(guān)和配置信息。通過(guò)這種方式,用戶可以在不同的區(qū)域內(nèi)同步數(shù)據(jù),并且系統(tǒng)會(huì)生成 diff 操作,方便用戶了解修改的內(nèi)容。


目前我們主要使用微軟提供的 monaco-editor 的 npm 包來(lái)實(shí)現(xiàn),該包專門用于實(shí)現(xiàn)類似于 VS Code 在線編輯器的功能。

關(guān)于后臺(tái)系統(tǒng)的核心功能主要有以下幾點(diǎn)。


路由的增加、刪除、修改和查詢是我們的主要關(guān)注點(diǎn)。另外我們還提到了一個(gè)配置發(fā)布的流程,盡管在我們的實(shí)際使用中沒(méi)有提到,但為了提升用戶的使用體驗(yàn),我們?cè)谌粘i_(kāi)發(fā)和預(yù)發(fā)布環(huán)境中采用了一些技術(shù)手段來(lái)簡(jiǎn)化發(fā)布流程,以減少繁瑣的操作。

此外我們還支持路由的引用功能,允許其他業(yè)務(wù)方引用我們的路由信息,從而避免重復(fù)維護(hù)代碼的問(wèn)題。這一功能可以提高開(kāi)發(fā)效率,同時(shí)也需要與其他業(yè)務(wù)方進(jìn)行充分的溝通和協(xié)調(diào),以確保數(shù)據(jù)的一致性和安全性。

當(dāng)涉及到企業(yè)級(jí)應(yīng)用時(shí),除了之前提到的一些功能外,還需要考慮權(quán)限管理。用戶在操作某種數(shù)據(jù)時(shí)需要具備相應(yīng)的權(quán)限,并且這些權(quán)限需要有記錄。另外,還需要一些插件、日志鏈路以及接口的統(tǒng)計(jì)等能力,來(lái)保障網(wǎng)關(guān)的健壯性。

歡迎報(bào)名第 63 屆早早聊大會(huì) - 低代碼無(wú)代碼,了解低代碼的應(yīng)用與實(shí)踐,上車戳:https://www.zaozao.run/conf/c63



運(yùn)行時(shí)要如何設(shè)計(jì)


有了后臺(tái)系統(tǒng),我們就可以把這個(gè)數(shù)據(jù)寫到 MySQL 數(shù)據(jù)庫(kù),也可以把這個(gè)數(shù)據(jù)從 MySQL 這種持久化數(shù)據(jù)庫(kù)里面去寫到 Redis 里。那么在運(yùn)行時(shí)怎么從 Redis 里面去讀這個(gè)數(shù)據(jù),并且響應(yīng)給用戶呢?

我們借鑒了 Koa 框架的核心思想,即通過(guò)在請(qǐng)求或響應(yīng)階段的任何一層,附加一個(gè)類似于 ?AOP ?的思想,在任何一層進(jìn)行攔截和處理。

使用這個(gè)框架時(shí),一個(gè)常見(jiàn)問(wèn)題是如何管理上下文數(shù)據(jù)。這個(gè)框架未能提供足夠的建議,導(dǎo)致許多用戶在上下文中掛載大量不明數(shù)據(jù)。這會(huì)導(dǎo)致后續(xù)用戶不知道上下文中到底掛載了哪些數(shù)據(jù)。


為了解決這個(gè)問(wèn)題,我們要求所有的數(shù)據(jù)只能掛載到 context.state 對(duì)象上,并對(duì)這個(gè)對(duì)象的類型進(jìn)行約束。這樣我可以通過(guò)這個(gè) context 對(duì)象了解所有后續(xù)請(qǐng)求的信息。

另外,Koa 框架的請(qǐng)求,進(jìn)來(lái)就是全部進(jìn)來(lái)了,出去就是全部出去了,沒(méi)有某個(gè)請(qǐng)求進(jìn)來(lái)時(shí)可以停止執(zhí)行的功能。因此,基于 Koa 框架,我們封裝了 Cube 和 CubeFlow 這兩個(gè)概念,其中 Cube 的概念實(shí)際上就是 Koa 的中間件,只是在這個(gè)中間件里面我們引入了 enable 或者 disable 的概念。這樣,在請(qǐng)求執(zhí)行到這個(gè)中間件時(shí),我們可以直接進(jìn)行 next 跳轉(zhuǎn)。通過(guò)這樣的方式,我們將多個(gè) Cube 進(jìn)行了串聯(lián),形成了一條 flow,這個(gè)就是請(qǐng)求的執(zhí)行鏈路。

在圖片左邊我們還可以看到,main.ts 是請(qǐng)求進(jìn)入的執(zhí)行入口,在這個(gè)入口里面主要做了兩件事情,啟動(dòng)運(yùn)行時(shí)的監(jiān)聽(tīng)端口,啟動(dòng)另外一個(gè)實(shí)例。這主要用于監(jiān)聽(tīng)另外一個(gè)端口以暴露一些指標(biāo),例如當(dāng)前網(wǎng)關(guān)服務(wù)的健壯性和運(yùn)行時(shí)的歷史記錄統(tǒng)計(jì)信息等,供其他服務(wù)進(jìn)行采集和告警。


我們簡(jiǎn)單來(lái)看一下這個(gè)目錄。適配器是一個(gè)用于處理路由規(guī)則的組件。這種適配器主要指的是路由規(guī)則的處理方式,我們默認(rèn)情況下將路由規(guī)則寫入 Redis 中,但是后面我們可能會(huì)對(duì)其進(jìn)行擴(kuò)展。因 Redis 不適合存儲(chǔ)較大的數(shù)據(jù),在直接讀取較大數(shù)據(jù)時(shí)可能會(huì)導(dǎo)致阻塞。對(duì)于一些私有化部署的場(chǎng)景,可能沒(méi)有 Redis 這樣的需求。那么我們就提供一種基于文件配置的方式,預(yù)先將客戶的一些路由規(guī)則寫入文件中,然后從文件中讀取。

很多人在項(xiàng)目中可能使用的是本地文件存儲(chǔ)的方式,這種方式確實(shí)簡(jiǎn)便,但也帶來(lái)了一個(gè)很大的隱患。如果將一些賬號(hào)、密碼等數(shù)據(jù)寫入文件中,一旦文件泄露,對(duì)公司來(lái)說(shuō)將是一個(gè)很大的安全問(wèn)題。尤其是很多人喜歡將代碼存放在類似 GitHub 這樣的地方,相關(guān)的密鑰的泄露影響會(huì)非常嚴(yán)重。此外,還包括一些工具函數(shù)的暴露,以及關(guān)于日志的處理。


我們?yōu)榱吮阌诤罄m(xù)的日志查找,需要對(duì)日志進(jìn)行標(biāo)準(zhǔn)化處理,包括為日志添加等級(jí)和類型字段。這樣,我們可以通過(guò)這些字段來(lái)索引和查找當(dāng)前請(qǐng)求經(jīng)過(guò)的所有日志的類型。我們還使用了日志鏈路和指標(biāo)工具來(lái)觀測(cè)網(wǎng)關(guān)的健壯性和風(fēng)險(xiǎn),以保證網(wǎng)關(guān)的可觀測(cè)性。

在鏈路方面,目前最流行且主要使用的工具有 SkyWalking 和 Jaeger。Jaeger是一款用 Golang 編寫的工具,符合云原生架構(gòu)的標(biāo)準(zhǔn),具有良好的擴(kuò)展性。我們同時(shí)在前端和云端都使用了這兩套工具。

經(jīng)過(guò)標(biāo)準(zhǔn)化處理后,我們目前在日志鏈路中采用了 B3 數(shù)據(jù)傳輸規(guī)范。雖然它的官方提供了不同的部署模式,但不論哪種部署模式,由于缺乏前置隊(duì)列,在 span 數(shù)量過(guò)多時(shí),可能會(huì)導(dǎo)致數(shù)據(jù)丟失。因此日志鏈路雖然方便,但并不完全可靠。如果需要更可靠的鏈路追蹤,還需依賴日志記錄。

在常用的中間件中,由于暴露了 HTTP 服務(wù),需要注意處理請(qǐng)求中的 body 數(shù)據(jù),否則無(wú)法解析 POST 請(qǐng)求的 body。網(wǎng)關(guān)還有一個(gè)核心功能,即關(guān)于登錄 session 的保存和 cookie 的處理。在后面的基礎(chǔ)中間件中,還涉及到 CSRF 安全中間件,這將在后續(xù)詳細(xì)討論。

現(xiàn)在,讓我們來(lái)看一下請(qǐng)求進(jìn)入代理流程的具體步驟。


對(duì)于熟悉 Node.js 并且有前端插件編寫經(jīng)驗(yàn)的人來(lái)說(shuō),可能會(huì)對(duì)這一過(guò)程更加了解。 通過(guò) http-proxy 將上游請(qǐng)求代理到指定的下游服務(wù),核心代碼如上所示,官方文檔中還包含其他用法,其實(shí)都大同小異。

在封裝時(shí),我們需要關(guān)注運(yùn)行時(shí)的幾個(gè)重要點(diǎn)。例如,如果監(jiān)聽(tīng)失敗、用戶自動(dòng)取消、代理超時(shí)以及上游異常應(yīng)該如何處理?應(yīng)該返回什么樣的信息給用戶?

我們主要做了以下處理,當(dāng)請(qǐng)求到來(lái)時(shí),我們首先判斷這是否為代理請(qǐng)求還是 Ajax 接口請(qǐng)求。如果是代理請(qǐng)求失敗,我們會(huì)直接通過(guò)模板引擎進(jìn)渲染一個(gè)失敗頁(yè)。如果是接口請(qǐng)求,我們會(huì)直接返回錯(cuò)誤信息,并攜帶網(wǎng)關(guān)指定的錯(cuò)誤碼。這些錯(cuò)誤碼會(huì)在文檔中進(jìn)行記錄,以便用戶在看到錯(cuò)誤時(shí)能夠了解發(fā)生了什么問(wèn)題。

關(guān)于運(yùn)行時(shí)的代碼執(zhí)行方式,我們需要查看下面的圖表。


用戶配置的數(shù)據(jù)會(huì)被寫入 router 對(duì)象,并在運(yùn)行時(shí)同步到 Redis 中。當(dāng)請(qǐng)求到來(lái)時(shí),我會(huì)先從 Redis 中獲取這個(gè)數(shù)據(jù)。獲取到的數(shù)據(jù)實(shí)際上是一個(gè)字符串。然后我們將 JS 字符串丟到 Node 提供的 vm 模塊中,然后在 vm 內(nèi)部運(yùn)行的代碼中,考慮到用戶可能使用我們提供的 context 或者外部依賴 RPC 的情況,需要先將這些能力替換為特定的函數(shù),這樣 vm 才能正確識(shí)別這些數(shù)據(jù)。

當(dāng)然,需要額外提及的是官方的 vm 模塊存在一些安全性問(wèn)題。動(dòng)態(tài)執(zhí)行代碼有很多方式,比如 function 等,但為什么在最佳實(shí)踐中,這些方式基本上都沒(méi)有被使用呢?最大的問(wèn)題就是,vm 直接將一些敏感的運(yùn)行環(huán)境數(shù)據(jù)暴露給了用戶,用戶可以在虛擬機(jī)內(nèi)部進(jìn)行修改,從而涉及到了較大的安全性問(wèn)題。因此,社區(qū)現(xiàn)在基本上都在使用 vm2 來(lái)替代官方的 vm 模塊,處理一些動(dòng)態(tài)執(zhí)行的場(chǎng)景。

目前,我們使用最普遍且隔離性最好的工具就是 ?Docker。Docker 在線程級(jí)別或進(jìn)程級(jí)別,對(duì)不同的服務(wù)和資源進(jìn)行了隔離。剛剛提到的 vm2,其安全性和隔離性方面還不夠。因此,如果我們想從線程層面對(duì)隔離進(jìn)行更加嚴(yán)格的控制,我們可以考慮在 Docker 基礎(chǔ)上進(jìn)行封裝,或者自行基于 vm 進(jìn)行更上一層的線程級(jí)別封裝。

需要額外提到的是,Docker 在容器隔離方面的安全性可能還不夠。尤其是在涉及底層 Linux 文件描述符等配置操作時(shí),容器可能會(huì)泄露一些數(shù)據(jù)到外部。社區(qū)目前也在致力于開(kāi)發(fā)更加安全的容器隔離技術(shù),建議大家密切關(guān)注相關(guān)的最新進(jìn)展。如果你從事與阿里云或其他云服務(wù)商相關(guān)的產(chǎn)品開(kāi)發(fā),更需要關(guān)注容器隔離技術(shù)的安全性。


接下來(lái),簡(jiǎn)單地使用 JSON 來(lái)描述這段代碼。


與之前的流程類似,首先從配置中提取這些數(shù)據(jù),提取到了這些數(shù)據(jù)之后,我們會(huì)對(duì)一些前置的變量和工具進(jìn)行初始化,并在運(yùn)行時(shí),當(dāng)解析到需要執(zhí)行的路由時(shí),進(jìn)行編譯。這個(gè)編譯的過(guò)程實(shí)際上就是將 JSON 對(duì)象提取出來(lái),然后進(jìn)行替換的工作。由于這樣的替換涉及到了無(wú)限的層級(jí)配置,我們不方便在代碼中進(jìn)行適配。因此,我們做了一個(gè)妥協(xié),只允許接受 JSON ?的第一層數(shù)據(jù)。

在代碼中,核心的部分其實(shí)是 router.run(),實(shí)際上執(zhí)行的是類似于以下的步驟。

Drone 是一個(gè)基于 Docker 的、更方便隔離的工具。它的上層可以使用 vm2 來(lái)封裝,但具體使用的方式取決于團(tuán)隊(duì)的需求和能力。作者寫的內(nèi)容很不錯(cuò),有興趣的讀者可以在 GitHub 上了解更多。

無(wú)碼有碼,全靠拖拉。跟早早聊一起,學(xué)習(xí)低代碼搭建,上車鏈接:https://www.zaozao.run/conf/c63



插件的加載和管理


業(yè)務(wù)方認(rèn)為在后臺(tái)配置數(shù)據(jù)和工具很麻煩,我們?yōu)樗麄兲峁┝艘环菸臋n,告訴他們?nèi)绾芜M(jìn)行插件開(kāi)發(fā),讓他們可以根據(jù)自己的能力自行編寫插件,減少溝通成本。平臺(tái)允許用戶新增插件,默認(rèn)使用內(nèi)網(wǎng)的 GitLab 進(jìn)行管理。代碼保存方式可能需要變更,不必保存在倉(cāng)庫(kù),可以直接存儲(chǔ)在 MySQL 數(shù)據(jù)庫(kù)或?qū)ο蟠鎯?chǔ)中。


目前的插件源碼放在 GitLab 上進(jìn)行管理。后續(xù)規(guī)劃中,我們計(jì)劃使用文件存儲(chǔ)的方式,直接將對(duì)象存儲(chǔ)為文件。這是因?yàn)轭愃朴?vm2 這樣的工具可以直接加載文件并執(zhí)行其中的代碼。關(guān)于數(shù)據(jù)同步功能,大家可以自行查看。



插件開(kāi)發(fā)的模版如下圖所示,需要先定義輸入?yún)?shù),并暴露一個(gè)函數(shù)作為插件的執(zhí)行邏輯。業(yè)務(wù)方自己實(shí)現(xiàn)具體的業(yè)務(wù)邏輯,可以通過(guò)函數(shù)請(qǐng)求業(yè)務(wù)依賴的服務(wù),也可以根據(jù)登錄相關(guān)的數(shù)據(jù)來(lái)修改上下文中的內(nèi)容。插件是作用在全局的整個(gè)項(xiàng)目中,配置某個(gè)插件后,每個(gè)路由都會(huì)默認(rèn)使用該插件。如果特定路由不使用插件,則插件需提供路由白名單。


  1. 項(xiàng)目級(jí)和路由級(jí)配置,最終運(yùn)行時(shí)在路由級(jí)識(shí)別最終插件代碼,并在鏈路上體現(xiàn)。

  2. 一般配項(xiàng)目級(jí)插件即可,特殊路由通過(guò)白名單跳過(guò)插件邏輯,或配置路由級(jí)插件。

  3. 不建議過(guò)度使用插件,就好比不建議 Koa 中間件過(guò)多,導(dǎo)致請(qǐng)求上下文過(guò)于復(fù)雜。

  4. 使用插件前需閱讀插件文檔,明確插件用途再來(lái)使用;如有疑問(wèn)可聯(lián)系插件開(kāi)發(fā)者。

  5. 網(wǎng)關(guān)插件有版本功能,插件版本號(hào)需要遵循 semver 規(guī)范。


歡迎報(bào)名第 63 屆早早聊大會(huì) - 低代碼無(wú)代碼,了解如何通過(guò)低代碼平臺(tái)提高生產(chǎn)力,上車戳:https://www.zaozao.run/conf/c63


網(wǎng)關(guān)的安全性加固


最后要提到的一個(gè)功能點(diǎn)是網(wǎng)關(guān)的安全性,這在之前的分享中也提到過(guò),安全性是一個(gè)不可忽視的問(wèn)題。網(wǎng)關(guān)的安全性可能會(huì)導(dǎo)致公司陷入法律訴訟等嚴(yán)重問(wèn)題,因此需要引起足夠的重視。

網(wǎng)關(guān)上可以實(shí)現(xiàn)一些在安全性方面的增強(qiáng)功能。例如,如果我經(jīng)常暴露這種接口,那么我的接口是否支持防止請(qǐng)求的重放攻擊呢?

防重攻擊

在接口防重攻擊方面,有很多種方案可供選擇。



我們這邊主要是通過(guò)左邊四個(gè)點(diǎn)的認(rèn)證來(lái)確定請(qǐng)求是否合法。只要請(qǐng)求能夠通過(guò)這個(gè)認(rèn)證,我們就認(rèn)為該接口具備了防重攻擊的能力。不過(guò),實(shí)際的防重攻擊力度應(yīng)該達(dá)到什么級(jí)別,這需要根據(jù)各個(gè)公司的安全委員會(huì)的要求來(lái)確定。具體而言,每個(gè)請(qǐng)求都必須能夠相互區(qū)別,并且可以拒絕重復(fù)的請(qǐng)求,防止請(qǐng)求內(nèi)容被篡改,同時(shí)容許容錯(cuò)。

在我們的場(chǎng)景中,我們發(fā)現(xiàn)大多數(shù)業(yè)務(wù)方在使用接口時(shí),先從網(wǎng)關(guān)加載到前端的頁(yè)面,然后在頁(yè)面上發(fā)起 Ajax 請(qǐng)求。在這種情況下,當(dāng)文檔加載完成時(shí),網(wǎng)關(guān)會(huì)返回一個(gè) nonce 作為響應(yīng)。后續(xù)的請(qǐng)求中,限制前端必須使用自己的框架去發(fā)起 HTTP 請(qǐng)求,框架會(huì)在發(fā)起請(qǐng)求時(shí)進(jìn)行封裝,將當(dāng)前時(shí)間戳和 nonce 一同攜帶過(guò)去。當(dāng)請(qǐng)求到達(dá)網(wǎng)關(guān)時(shí),網(wǎng)關(guān)會(huì)解析參數(shù),并判斷當(dāng)前時(shí)間差是否在容錯(cuò)范圍內(nèi)。然后,網(wǎng)關(guān)會(huì)在 Redis 檢查請(qǐng)求中攜帶的 nonce 字段是否合法。


防 XSS 攻擊


關(guān)于常見(jiàn)的防 Web 安全的方法,其中涉及到的一個(gè)問(wèn)題是 XSS 攻擊。


這種攻擊主要分為兩種類型,一種是反射型 XSS 攻擊。這種攻擊方式是用戶在提交表單數(shù)據(jù)中攜帶一些惡意腳本,當(dāng)后端存儲(chǔ)了這些數(shù)據(jù)后,就會(huì)出現(xiàn)存儲(chǔ)型 XSS 攻擊。當(dāng)后面用戶的請(qǐng)求從后端取出這些數(shù)據(jù)時(shí),前端的腳本會(huì)在瀏覽器中執(zhí)行,從而導(dǎo)致網(wǎng)站被攻擊。


可以通過(guò)配置兩種常見(jiàn)的包來(lái)配置 XSS 防護(hù)參數(shù),一個(gè)是 XSS 包,另外一個(gè)是 Hamlet 包。在前端的場(chǎng)景中,一般會(huì)封裝一個(gè)函數(shù)來(lái)使用這些包。在安全防護(hù)上,主要是增加安全攻擊的難度,而不是完全避免。


防 CSRF


CSRF 這個(gè)跨站腳本偽造的問(wèn)題和 XSS 攻擊可能是當(dāng)前 Web 領(lǐng)域中最常見(jiàn)且危害較大的兩個(gè)問(wèn)題。


與之前提到的防重攻擊方案類似,網(wǎng)關(guān)在渲染時(shí)會(huì)返回一個(gè) token 值,然后前端框架在發(fā)起請(qǐng)求時(shí)會(huì)攜帶這個(gè) token 值。通常情況下,前端會(huì)把 token 放在 cookie 里與登錄態(tài)一起使用。


另外,網(wǎng)關(guān)主要對(duì) post、put 和 delete 等可能對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)造成影響的接口做一些合法性校驗(yàn),對(duì) get 等獲取請(qǐng)求大多數(shù)情況下直接放行。


內(nèi)存級(jí)別和 IP 級(jí)別的限流


最后,安全性還涉及到內(nèi)存級(jí)別和 IP 級(jí)別的限流。


當(dāng)你的流量請(qǐng)求較大時(shí),你服務(wù)的穩(wěn)定性或響應(yīng)時(shí)間可能會(huì)受到一定影響。如果你的網(wǎng)關(guān)服務(wù)多個(gè)業(yè)務(wù)方,類似之前提到的默認(rèn)情況,多個(gè)業(yè)務(wù)方通過(guò)你的網(wǎng)關(guān)代理請(qǐng)求,那么如果某個(gè)業(yè)務(wù)方發(fā)起惡意攻擊,一分鐘內(nèi)發(fā)送成千上萬(wàn)個(gè)請(qǐng)求,導(dǎo)致你的網(wǎng)關(guān)負(fù)載能力增加,QPS 上升,從而導(dǎo)致每個(gè)響應(yīng)的延遲增加到四五百毫秒甚至一兩秒,其他業(yè)務(wù)方也會(huì)受到影響。


因此,首先我們需要對(duì)你的網(wǎng)關(guān)在特定配置下進(jìn)行壓力測(cè)試,了解你的 QPS 大約能夠支持多少,響應(yīng)延遲、內(nèi)存、CPU 等資源是否在可控范圍內(nèi)。可以在代碼中添加一個(gè)限流插件來(lái)進(jìn)行限流。一個(gè)簡(jiǎn)單的限流插件可以基于 map 來(lái)實(shí)現(xiàn),即請(qǐng)求進(jìn)來(lái)時(shí)進(jìn)行計(jì)數(shù),超過(guò)限定值時(shí)進(jìn)行限制,然后請(qǐng)求出去時(shí)將計(jì)數(shù)減少。如果您想要更加健壯的限流方案,可以考慮社區(qū)上的一些限流 npm 包。我們現(xiàn)在在網(wǎng)關(guān)上實(shí)現(xiàn) Mesh 化,當(dāng)某個(gè)服務(wù)出現(xiàn)問(wèn)題,不會(huì)影響其他服務(wù)。同時(shí),在這種場(chǎng)景下,業(yè)務(wù)方到網(wǎng)關(guān)之間的網(wǎng)絡(luò)帶寬和延遲也可以得到性能優(yōu)化。


其他功能


這個(gè)后臺(tái)系統(tǒng)的功能在很多系統(tǒng)中都很常見(jiàn),主要包括安全性合規(guī)、數(shù)據(jù)加密存儲(chǔ)、日志輸出時(shí)避免敏感信息泄露以及配置中心接入。


歡迎報(bào)名第 63 屆早早聊大會(huì) - 低代碼無(wú)代碼,跟早早聊一起,學(xué)習(xí)低代碼搭建,上車鏈接:https://www.zaozao.run/conf/c63



結(jié)束語(yǔ)



最后需要關(guān)注的是,我們正在開(kāi)發(fā)一個(gè)業(yè)務(wù)服務(wù)的 BFF 網(wǎng)關(guān),這將幫助解決一些痛點(diǎn)。隨著業(yè)務(wù)的不斷變化,我們還需要對(duì)這個(gè) BFF 網(wǎng)關(guān)進(jìn)行優(yōu)化,因?yàn)闃I(yè)務(wù)需求千變?nèi)f化。例如,剛才提到的管理方式的匹配化、后續(xù)的可觀測(cè)性提升,提供低代碼化或可視化的編排能力以提升用戶體驗(yàn)等。此外,在線配置路由等功能時(shí),需要提供在線和線下的單元測(cè)試,并對(duì)路由信息的相關(guān)參數(shù)進(jìn)行完善的校驗(yàn),這些都是我們需要嘗試實(shí)現(xiàn)的一些目標(biāo)。


無(wú)碼有碼,全靠拖拉。跟早早聊一起,學(xué)習(xí)低代碼搭建,上車鏈接:https://www.zaozao.run/conf/c63


最后

以上就是我的全部分享內(nèi)容。最后,推薦一本講經(jīng)濟(jì)的書(shū)《未來(lái)二十年,經(jīng)濟(jì)大趨勢(shì)》。



低代碼是當(dāng)今最熱門的技術(shù)之一,如果你對(duì)低代碼感興趣,或者正在研究低代碼,歡迎報(bào)名第 63 屆早早聊大會(huì) - 低代碼無(wú)代碼,跟早早聊一起,學(xué)習(xí)低碼搭建。

  • 舉辦時(shí)間:2023 年 4 月 15 日 ?10:00 ~ 17:00

  • 截至?xí)r間:2023 年 4 月 15 日 ?19:00

  • 舉辦方式:微信群 PPT 推送 + 線上視頻實(shí)時(shí)直播 + 會(huì)后資料推送

  • 報(bào)名方式:https://www.zaozao.run/conf/c63

  • 大會(huì)主辦方:前端早早聊


【前端大佬 | Node 連載 5/9】涂鴉 - 龍野:《如何用 Node 建設(shè)企業(yè)級(jí)應(yīng)用網(wǎng)關(guān)》的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
襄垣县| 西林县| 天祝| 谢通门县| 洪江市| 梧州市| 鄂托克前旗| 诸暨市| 肥东县| 南部县| 普安县| 五莲县| 二手房| 那曲县| 攀枝花市| 沙湾县| 读书| 神池县| 屯留县| 齐齐哈尔市| 深水埗区| 鹤庆县| 越西县| 福安市| 车致| 普安县| 广南县| 永丰县| 红河县| 全南县| 盱眙县| 安平县| 汉阴县| 华宁县| 株洲县| 双牌县| 门源| 衢州市| 青铜峡市| 澎湖县| 乐平市|