<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>

          如何將傳統(tǒng) Web 框架部署到 Serverless

          共 25429字,需瀏覽 51分鐘

           ·

          2022-07-10 20:31

          ??  這是第 150 篇不摻水的原創(chuàng),想要了解更多,請(qǐng)戳下方卡片關(guān)注我們吧~

          如何將傳統(tǒng) Web 框架部署到 Serverless

          https://www.zoo.team/article/serverless-web

          背景

          因?yàn)?Serverless 的“無(wú)服務(wù)器架構(gòu)”應(yīng)用相比于傳統(tǒng)應(yīng)用有很多優(yōu)點(diǎn),比如:無(wú)需關(guān)心服務(wù)器、免運(yùn)維、彈性伸縮、按需付費(fèi)、開(kāi)發(fā)可以更加關(guān)注業(yè)務(wù)邏輯等等,所以現(xiàn)在 Serverless 應(yīng)用已經(jīng)逐漸廣泛起來(lái)。

          但是目前原生的 Serverless 開(kāi)發(fā)框架還比較少,也沒(méi)有那么成熟,另外主流的 Web 框架還不支持直接 Serverless 部署,但好在是現(xiàn)在國(guó)內(nèi)各大云廠商比如阿里云、騰訊云已經(jīng)提供能力能夠?qū)⑽覀兊膫鹘y(tǒng)框架以簡(jiǎn)單、快速、科學(xué)的方式部署到 Serverless 上,下面讓我們一起研究看看它們是怎么做的吧。

          我們以 Node.js 的 Express 應(yīng)用為例,看看如何通過(guò)阿里云函數(shù)計(jì)算,實(shí)現(xiàn)不用按照傳統(tǒng)部署方式購(gòu)買云主機(jī)去部署,不用自己運(yùn)維,快速部署到 Serverless 平臺(tái)上。

          傳統(tǒng)應(yīng)用與函數(shù)計(jì)算的入口差異

          傳統(tǒng)應(yīng)用的入口文件

          首先看下傳統(tǒng) Express 應(yīng)用的入口文件:

          const express = require('express')
          const app = express()
          const port = 3000

          // 監(jiān)聽(tīng) / 路由,處理請(qǐng)求
          app.get('/', (req, res) => {
            res.send('Hello World!')
          })

          // 監(jiān)聽(tīng) 3000 端口,啟動(dòng) HTTP 服務(wù)
          app.listen(port, () => {
            console.log(`Example app listening on port ${port}`)
          })

          可以看到傳統(tǒng) Express 應(yīng)用是:

          1.通過(guò) app.listen() 啟動(dòng)了 HTTP 服務(wù),其本質(zhì)上是調(diào)用的 Node.js http 模塊的 createServer() 方法創(chuàng)建了一個(gè) HTTP Server

          2.監(jiān)聽(tīng)了 / 路由,由回調(diào)函數(shù) function(request, response) 處理請(qǐng)求

          函數(shù)計(jì)算的入口函數(shù)

          Serverless 應(yīng)用中, FaaS 是基于事件觸發(fā)的,觸發(fā)器是觸發(fā)函數(shù)執(zhí)行的方式, 其中 API 網(wǎng)關(guān)觸發(fā)器與 HTTP 觸發(fā)器與均可應(yīng)用于 Web應(yīng)用的創(chuàng)建。函數(shù)計(jì)算會(huì)從指定的入口函數(shù)開(kāi)始執(zhí)行,其中 API 網(wǎng)關(guān)觸發(fā)器對(duì)應(yīng)的入口函數(shù)叫事件函數(shù),HTTP 觸發(fā)器對(duì)應(yīng)的入口函數(shù)叫 HTTP 函數(shù),它們的入口函數(shù)形式不同。

          API 網(wǎng)關(guān)觸發(fā)器的入口函數(shù)形式

          API 網(wǎng)關(guān)觸發(fā)器的入口函數(shù)形式如下,函數(shù)入?yún)?event、context、callback,以 Node.js 為例,如下:

          /*
          * handler: 函數(shù)名 handler 需要與創(chuàng)建函數(shù)時(shí)的 handler 字段相對(duì)應(yīng)。例如創(chuàng)建函數(shù)時(shí)指定的 handler 為 index.handler,那么函數(shù)計(jì)算會(huì)去加載 index.js 文件中定義的 handler 函數(shù)
          * event: 您調(diào)用函數(shù)時(shí)傳入的數(shù)據(jù),其類型是 Buffer,是函數(shù)的輸入?yún)?shù)。您在函數(shù)中可以根據(jù)實(shí)際情況對(duì) event 進(jìn)行轉(zhuǎn)換。如果輸入數(shù)據(jù)是一個(gè) JSON 字符串 ,您可以把它轉(zhuǎn)換成一個(gè) Object。
          * context: 包含一些函數(shù)的運(yùn)行信息,例如 request Id、 臨時(shí) AK 等。您在代碼中可以使用這些信息
          * callback: 由系統(tǒng)定義的函數(shù),作為入口函數(shù)的入?yún)⒂糜诜祷卣{(diào)用函數(shù)的結(jié)果,標(biāo)識(shí)函數(shù)執(zhí)行結(jié)束。與 Node.js 中使用的 callback 一樣,它的第一個(gè)參數(shù)是 error,第二個(gè)參數(shù) data。
          */

          module.exports.handler = (event, context, callback) => {

            // 處理業(yè)務(wù)邏輯
            callback(null, data);

          };

          HTTP 觸發(fā)器的入口函數(shù)形式

          一個(gè)簡(jiǎn)單的 Node.js HTTP 函數(shù)示例如下所示:

          module.exports.handler = function(request, response, context)  {
            response.send("hello world");
          }

          差異對(duì)比

          對(duì)比可以看出,在傳統(tǒng)應(yīng)用中,是啟動(dòng)一個(gè)服務(wù)監(jiān)聽(tīng)端口號(hào)去處理 HTTP 請(qǐng)求,服務(wù)處理的是 HTTP 的請(qǐng)求和響應(yīng)參數(shù);而在 Serverless 應(yīng)用中, Faas 是基于事件觸發(fā)的,觸發(fā)器類型不同,參數(shù)映射和處理不同:

          • 若是 API 網(wǎng)關(guān)觸發(fā)器
            • 當(dāng)有請(qǐng)求到達(dá)后端服務(wù)設(shè)置為函數(shù)計(jì)算的 API 網(wǎng)關(guān)時(shí),API 網(wǎng)關(guān)會(huì)觸發(fā)函數(shù)的執(zhí)行,觸發(fā)器會(huì)將事件信息生成 event 參數(shù),然后 FaaS 以 event 為參數(shù)執(zhí)行入口函數(shù),最后將執(zhí)行結(jié)果返回給 API 網(wǎng)關(guān)。所以傳統(tǒng)應(yīng)用和 Serverless 應(yīng)用在請(qǐng)求響應(yīng)方式和參數(shù)的數(shù)據(jù)結(jié)構(gòu)上都有很大差異,要想辦法讓函數(shù)計(jì)算的入口方法適配 express。
          • 若是 HTTP 觸發(fā)器
            • 相對(duì) API 網(wǎng)關(guān)觸發(fā)器參數(shù)處理會(huì)簡(jiǎn)單些。因?yàn)?HTTP 觸發(fā)器通過(guò)發(fā)送 HTTP 請(qǐng)求觸發(fā)函數(shù)執(zhí)行,會(huì)把真實(shí)的 HTTP 請(qǐng)求直接傳遞給 FaaS 平臺(tái),不需要編碼或解碼成 JSON 格式,不用增加轉(zhuǎn)換邏輯,性能也更優(yōu)。

          適配層

          下面我們通過(guò)解讀阿里云 FC 提供的將函數(shù)計(jì)算的請(qǐng)求轉(zhuǎn)發(fā)給 express 應(yīng)用的 npm 包 @webserverless/fc-express 源碼,看看函數(shù)計(jì)算的入口方法是如何適配 express 的,如何適配 API 網(wǎng)關(guān) 和 HTTP 觸發(fā)器這兩種類型。

          根據(jù)上述分析,Web 應(yīng)用若想 Serverless 化需要開(kāi)發(fā)一個(gè)適配層,將函數(shù)計(jì)算接收到的請(qǐng)求轉(zhuǎn)發(fā)給 express 應(yīng)用處理,最后再返回給函數(shù)計(jì)算。

          API 網(wǎng)關(guān)觸發(fā)的適配層

          實(shí)現(xiàn)原理

          API 網(wǎng)關(guān)觸發(fā)的情況下,通過(guò)適配層將 FaaS 函數(shù)接收到的 API 網(wǎng)關(guān)事件參數(shù) event 先轉(zhuǎn)化為標(biāo)準(zhǔn)的 HTTP 請(qǐng)求,再去讓傳統(tǒng) Web 服務(wù)去處理請(qǐng)求和響應(yīng),最后再將 HTTP 響應(yīng)轉(zhuǎn)換為函數(shù)返回值。整體工作原理如下圖所示:

          適配層核心就是:把 event 映射到 express 的 request 對(duì)象上, 再把 express 的 response 對(duì)象映射到 callback 的數(shù)據(jù)參數(shù)上。

          API 網(wǎng)關(guān)調(diào)用函數(shù)計(jì)算的事件函數(shù)時(shí),會(huì)將 API 的相關(guān)數(shù)據(jù)轉(zhuǎn)換為 Map 形式傳給函數(shù)計(jì)算服務(wù)。函數(shù)計(jì)算服務(wù)處理后,按照下圖中 Output Format 的格式返回 statusCode、headers、body 等相關(guān)數(shù)據(jù)。API 網(wǎng)關(guān)再將函數(shù)計(jì)算返回的內(nèi)容映射到 statusCode、header、body等位置返回給客戶端。

          (此圖來(lái)源于阿里云)

          核心過(guò)程

          通過(guò)分析 @webserverless/fc-express 源碼,我們可以抽取核心過(guò)程實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的適配層。

          1.創(chuàng)建一個(gè)自定義 HTTP Server,通過(guò)監(jiān)聽(tīng) Unix Domain Socket,啟動(dòng)服務(wù)

          (友情鏈接:不清楚 Unix Domain Socket 的小伙伴可以先看下這篇文章: Unix domain socket 簡(jiǎn)介 (https://www.cnblogs.com/sparkdev/p/8359028.html))

          第一步我們?nèi)粝氚押瘮?shù)計(jì)算接收的 event 參數(shù)映射到 Express.js 的 request 對(duì)象上,就需要創(chuàng)建并啟動(dòng)一個(gè)自定義的 HTTP 服務(wù)來(lái)代替 Express.js 的 app.listen,然后接下來(lái)就可以將函數(shù)的事件參數(shù) event 轉(zhuǎn)換為 Express.js 的 request 請(qǐng)求參數(shù)。

          首先創(chuàng)建一個(gè) server.js 文件如下:

          // server.js
          const http = require('http');
          const ApiGatewayProxy = require('./api-gateway-proxy');// api-gateway-proxy.js 文件下一步會(huì)說(shuō)明其內(nèi)容

          /*
          * requestListener:被代理的 express 應(yīng)用
          * serverListenCallback:http 代理服務(wù)開(kāi)始監(jiān)聽(tīng)的回調(diào)函數(shù)
          * binaryTypes: 當(dāng) express 應(yīng)用的響應(yīng)頭 content-type 符合 binaryTypes 中定義的任意規(guī)則,則返回給 API 網(wǎng)關(guān)的 isBase64Encoded 屬性為 true
          */

          function Server(requestListener,serverListenCallback,binaryTypes
            this.apiGatewayProxy = new ApiGatewayProxy(this);   // ApiGatewayProxy 核心過(guò)程 2 會(huì)介紹

            this.server = http.createServer(requestListener);// 1.1 創(chuàng)建一個(gè)自定義 HTTP Server

            this.socketPathSuffix = getRandomString(); // 隨機(jī)生成一個(gè)字符串,作為 Unix Domain Socket 使用
            
            this.binaryTypes = binaryTypes ? binaryTypes.slice() : [];// 當(dāng) express 應(yīng)用響應(yīng)的 content-type 符合 Server 構(gòu)造函數(shù)參數(shù) binaryTypes 中定義的任意規(guī)則時(shí),則函數(shù)的返回值的 isBase64Encoded 為 true,從而告訴 API 網(wǎng)關(guān)如何解析函數(shù)返回值的 body 參數(shù)

            this.server.on("listening", () => {
              this.isListening = true;
              if (serverListenCallback) serverListenCallback();
            });

            this.server.on("close", () => {
              this.isListening = false;
            }).on("error", (error) => {
              // 異常處理
            });

          }

          // 暴露給函數(shù)計(jì)算入口函數(shù) handler 調(diào)用的方法
          Server.prototype.proxy = function (event, context, callback{
            const e = JSON.parse(event);
            this.apiGatewayProxy.handle({
              event: e,
              context,
              callback
            });
          }

          // 1.2 啟動(dòng)服務(wù)
          Server.prototype.startServer = function ({
            return this.server.listen(this.getSocketPath()); //  采用監(jiān)聽(tīng) Unix Domain Socket 方式啟動(dòng)服務(wù),減少函數(shù)執(zhí)行時(shí)間,節(jié)約成本
          }

          Server.prototype.getSocketPath = function ({
            /* istanbul ignore if */
            /* only running tests on Linux; Window support is for local dev only */
            if (/^win/.test(process.platform)) {
              const path = require('path');
              return path.join('\\\\?\\pipe', process.cwd(), `server-${this.socketPathSuffix}`);
            } else {
              return `/tmp/server-${this.socketPathSuffix}.sock`;
            }
          }

          function getRandomString({
            return Math.random().toString(36).substring(215);
          }

          module.exports = Server;

          在 server.js 中,我們定義了一個(gè)構(gòu)造函數(shù) Server 并導(dǎo)出。在 Server 中,我們創(chuàng)建了一個(gè)自定義的 HTTP 服務(wù),然后隨機(jī)生成了一個(gè) Unix Domain Socket,采用監(jiān)聽(tīng)該 Socket 方式啟動(dòng)服務(wù)來(lái)代替 Express.js 的 app.listen

          2.將函數(shù)計(jì)算參數(shù) event 轉(zhuǎn)換為 Express.js 的 HTTP request

          下面開(kāi)始第 2 步,創(chuàng)建一個(gè) api-gateway-proxy.js 文件,將函數(shù)計(jì)算參數(shù) event 轉(zhuǎn)換為 Express.js 的 HTTP request。

          //api-gateway-proxy.js
          const http = require('http');
          const isType = require('type-is');

          function ApiGatewayProxy(server{
            this.server = server;
          }

          ApiGatewayProxy.prototype.handle = function ({
            event,
            context,
            callback
          }
          {
            this.server.startServer()
              .on('listening', () => {
                this.forwardRequestToNodeServer({
                  event,
                  context,
                  callback
                });
              });
          }

          ApiGatewayProxy.prototype.forwardRequestToNodeServer = function ({
            event,
            context,
            callback
          }
          {
            const resolver = data => callback(null, data);
            try {
              // 2.1將 API 網(wǎng)關(guān)事件轉(zhuǎn)換為 HTTP request
              const requestOptions = this.mapContextToHttpRequest({
                event,
                context,
                callback
              });
              
              // 2.2 通過(guò) http.request() 將 HTTP request 轉(zhuǎn)發(fā)給 Node.js Server 處理,發(fā)起 HTTP 請(qǐng)求
              const req = http.request(requestOptions, response => this.forwardResponse(response, resolver));
              req.on('error', error => {
                   //...
                  });
              req.end();
            } catch (error) {
              // ...
            }
          }

          ApiGatewayProxy.prototype.mapContextToHttpRequest = function ({
            event,
            context,
            callback
          }
          {
            const headers = Object.assign({}, event.headers);
            return {
              method: event.httpMethod,
              path: event.path,
              headers,
              socketPaththis.server.getSocketPath()
              // protocol: `${headers['X-Forwarded-Proto']}:`,
              // host: headers.Host,
              // hostname: headers.Host, // Alias for host
              // port: headers['X-Forwarded-Port']
            };
          }

          // 核心過(guò)程 3 會(huì)介紹
          ApiGatewayProxy.prototype.forwardResponse = function (response, resolver{
            const buf = [];

            response
              .on('data', chunk => buf.push(chunk))
              .on('end', () => {
                const bodyBuffer = Buffer.concat(buf);
                const statusCode = response.statusCode;
                const headers = response.headers;
                const contentType = headers['content-type'] ? headers['content-type'].split(';')[0] : '';
                const isBase64Encoded = this.server.binaryTypes && this.server.binaryTypes.length > 0 && !!isType.is(contentType, this.server.binaryTypes);
                const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8');
               
                const successResponse = {
                  statusCode,
                  body,
                  headers,
                  isBase64Encoded
                };

                resolver(successResponse);
              });
          }

          module.exports = ApiGatewayProxy;

          在 api-gateway-proxy.js 中,我們定義了一個(gè)構(gòu)造函數(shù) ApiGatewayProxy 并導(dǎo)出。在這里我們會(huì)將 event 轉(zhuǎn)換為 HTTP request,然后向 Node.js Server 發(fā)起請(qǐng)求,由 Node.js Server 再進(jìn)行處理做出響應(yīng)。

          3.將 HTTP response 轉(zhuǎn)換為 API 網(wǎng)關(guān)標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),作為 callback 的參數(shù)返回給 API 網(wǎng)關(guān)

          接著繼續(xù)對(duì) api-gateway-proxy.js 文件中的http.request(requestOptions, response => this.forwardResponse(response, resolver))分析發(fā)出 HTTP 請(qǐng)求后的響應(yīng)處理部分。

          //api-gateway-proxy.js

          ApiGatewayProxy.prototype.forwardRequestToNodeServer = function ({
            event,
            context,
            callback
          }
          {
            const resolver = data => callback(null, data); // 封裝 callback 為 resolver
            //...
            // 請(qǐng)求、響應(yīng)
            const req = http.request(requestOptions, response => this.forwardResponse(response, resolver));
            //...
          }

          //3.Node.js Server 對(duì) HTTP 響應(yīng)進(jìn)行處理,將 HTTP response 轉(zhuǎn)換為 API 網(wǎng)關(guān)標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),作為函數(shù)計(jì)算返回值
          ApiGatewayProxy.prototype.forwardResponse = function (response, resolver{
            const buf = [];

            response
              .on('data', chunk => buf.push(chunk))
              .on('end', () => {
                const bodyBuffer = Buffer.concat(buf);
                const statusCode = response.statusCode;
                const headers = response.headers;
                const contentType = headers['content-type'] ? headers['content-type'].split(';')[0] : '';
                const isBase64Encoded = this.server.binaryTypes && this.server.binaryTypes.length > 0 && !!isType.is(contentType, this.server.binaryTypes);
                const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8');
              
               // 函數(shù)返回值
                const successResponse = {
                  statusCode,
                  body,
                  headers,
                  isBase64Encoded //當(dāng)函數(shù)的 event.isBase64Encoded 是 true 時(shí),會(huì)按照 base64 編碼來(lái)解析 event.body,并透?jìng)鹘o express 應(yīng)用,否則就按照默認(rèn)的編碼方式來(lái)解析,默認(rèn)是 utf8
                };
             
               // 將 API 網(wǎng)關(guān)標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)作為回調(diào) callback 參數(shù),執(zhí)行 callback,返回給 API 網(wǎng)關(guān)
                resolver(successResponse);
              });
          }



          接著第 2 步,Node.js Server 對(duì) http.request() 發(fā)出的 HTTP 請(qǐng)求做出響應(yīng)處理,將 HTTP response 轉(zhuǎn)換為 API 網(wǎng)關(guān)標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),把它作為回調(diào) callback 的參數(shù),調(diào)用 callback 返回給 API 網(wǎng)關(guān)。

          4.在入口函數(shù)中引入適配層代碼并調(diào)用

          以上 3 步就將適配層核心代碼完成了,整個(gè)過(guò)程就是:將 API 網(wǎng)關(guān)事件轉(zhuǎn)換成 HTTP 請(qǐng)求,通過(guò)本地 socket 和函數(shù)起 Node.js Server 進(jìn)行通信。

          最后我們?cè)谌肟诤瘮?shù)所在文件 index.js 中引入 server.js,先用 Server 構(gòu)建一個(gè) HTTP 代理服務(wù),然后在入口函數(shù) handler 中調(diào)用 server.proxy(event, context, callback); 即可將函數(shù)計(jì)算的請(qǐng)求轉(zhuǎn)發(fā)給 express 應(yīng)用處理。

          // index.js
          const express = require('express');

          const Server = require('./server.js'); 

          const app = express();
          app.all('*', (req, res) => {
            res.send('express-app hello world!');
          });

          const server = new Server(app); // 創(chuàng)建一個(gè)自定義 HTTP Server

          module.exports.handler = function(event, context, callback{
            server.proxy(event, context, callback); // server.proxy 將函數(shù)計(jì)算的請(qǐng)求轉(zhuǎn)發(fā)到 express 應(yīng)用
          };

          我們將以上代碼在 FC 上部署、調(diào)用,執(zhí)行成功結(jié)果如下:

          HTTP 觸發(fā)的適配層

          實(shí)現(xiàn)原理

          HTTP 觸發(fā)的情況下,不用對(duì)請(qǐng)求參數(shù)做轉(zhuǎn)換,其它原理與 API 網(wǎng)關(guān)觸發(fā)器一致:通過(guò)適配層將 FaaS 函數(shù)接收到的請(qǐng)求參數(shù)直接轉(zhuǎn)發(fā)到自定義的 Web 服務(wù)內(nèi),最后再將 HTTP 響應(yīng)包裝返回即可,整體工作原理如下圖所示:

          核心過(guò)程

          同樣我們抽取核心過(guò)程簡(jiǎn)單實(shí)現(xiàn)一個(gè)適配層,與 API 網(wǎng)關(guān)觸發(fā)器原理相同的部分將不再贅述 。

          1.創(chuàng)建一個(gè)自定義 HTTP Server,通過(guò)監(jiān)聽(tīng) Unix Domain Socket,啟動(dòng)服務(wù)

          server.js 代碼如下:

          // server.js
          const http = require('http');
          const HttpTriggerProxy = require('./http-trigger-proxy');

          function Server(requestListener,serverListenCallback{
            this.httpTriggerProxy = new HttpTriggerProxy(this);

            this.server = http.createServer(requestListener); // 1.1 創(chuàng)建一個(gè)自定義 HTTP Server

            this.socketPathSuffix = getRandomString();

            this.server.on("listening", () => {
              this.isListening = true;
              if (serverListenCallback) serverListenCallback();
            });

            this.server.on("close", () => {
              this.isListening = false;
            }).on("error", (error) => {
              // 異常處理,例如判讀 socket 是否已被監(jiān)聽(tīng)
            });

          }

          // 暴露給函數(shù)計(jì)算入口函數(shù) handler 調(diào)用的方法
          Server.prototype.httpProxy = function (request, response, context{
              this.httpTriggerProxy.handle({ request, response, context });
          }

          // 1.2 啟動(dòng)服務(wù)
          Server.prototype.startServer = function ({
            return this.server.listen(this.getSocketPath());
          }

          Server.prototype.getSocketPath = function ({
            /* istanbul ignore if */
            /* only running tests on Linux; Window support is for local dev only */
            if (/^win/.test(process.platform)) {
              const path = require('path');
              return path.join('\\\\?\\pipe', process.cwd(), `server-${this.socketPathSuffix}`);
            } else {
              return `/tmp/server-${this.socketPathSuffix}.sock`;
            }
          }

          function getRandomString({
            return Math.random().toString(36).substring(215);
          }

          module.exports = Server;

          2.將 HTTP request 直接轉(zhuǎn)發(fā)給 Web Server,再將 HTTP response 包裝返回

          創(chuàng)建一個(gè) api-trigger-proxy.js 文件如下:

          // api-trigger-proxy.js
          const http = require('http');
          const isType = require('type-is');
          const url = require('url');
          const getRawBody = require('raw-body');

          function HttpTriggerProxy(server{
            this.server = server;
          }

          HttpTriggerProxy.prototype.handle = function ({
            request,
            response,
            context
          }
          {
            this.server.startServer()
              .on('listening', () => {
                this.forwardRequestToNodeServer({
                  request,
                  response,
                  context
                });
              });
          }

          HttpTriggerProxy.prototype.forwardRequestToNodeServer = function ({
              request,
              response,
              context
          }
          {
            // 封裝 resolver
            const resolver = data => {
              response.setStatusCode(data.statusCode);
              for (const key in data.headers) {
                  if (data.headers.hasOwnProperty(key)) {
                      const value = data.headers[key];
                      response.setHeader(key, value);
                  }
              }
              response.send(data.body); // 返回 response body
            };
            try {
              // 透?jìng)?nbsp;request
              const requestOptions = this.mapContextToHttpRequest({
                  request,
                  context
              });
             // 2.將 HTTP request 直接轉(zhuǎn)發(fā)給 Web Server,再將 HTTP response 包裝返回
              const req = http.request(requestOptions, response => this.forwardResponse(response, resolver));
              req.on('error', error => {
                   // ...
                  });
              // http 觸發(fā)器類型支持自定義 body:可以獲取自定義 body
              if (request.body) {
                  req.write(request.body);
                  req.end();
              } else {
                // 若沒(méi)有自定義 body:http 觸發(fā)器觸發(fā)函數(shù),會(huì)通過(guò)流的方式傳輸 body 信息,可以通過(guò) npm 包 raw-body 來(lái)獲取
                  getRawBody(request, (err, body) => {
                      req.write(body);
                      req.end();
                  });
              }
            } catch (error) {
              // ...
            }
          }

          HttpTriggerProxy.prototype.mapContextToHttpRequest = function ({
              request,
              context
          }
          {
            const headers = Object.assign({}, request.headers); 
            headers['x-fc-express-context'] = encodeURIComponent(JSON.stringify(context));
            return {
              method: request.method,
              path: url.format({ pathname: request.path, query: request.queries }),
              headers,
              socketPaththis.server.getSocketPath()
              // protocol: `${headers['X-Forwarded-Proto']}:`,
              // host: headers.Host,
              // hostname: headers.Host, // Alias for host
              // port: headers['X-Forwarded-Port']
            };
          }

          HttpTriggerProxy.prototype.forwardResponse = function (response, resolver{
            const buf = [];

            response
              .on('data', chunk => buf.push(chunk))
              .on('end', () => {
                const bodyBuffer = Buffer.concat(buf);
                const statusCode = response.statusCode;
                const headers = response.headers;
                const contentType = headers['content-type'] ? headers['content-type'].split(';')[0] : '';
                const isBase64Encoded = this.server.binaryTypes && this.server.binaryTypes.length > 0 && !!isType.is(contentType, this.server.binaryTypes);
                const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8');
                const successResponse = {
                  statusCode,
                  body,
                  headers,
                  isBase64Encoded
                };

                resolver(successResponse);
              });
          }

          module.exports = HttpTriggerProxy;

          3.入口函數(shù)引入適配層代碼

          // index.js
          const express = require('express');
          const Server = require('./server.js');

          const app = express();
          app.all('*', (req, res) => {
            res.send('express-app-httpTrigger hello world!');
          });

          const server = new Server(app);



          module.exports.handler  = function (req, res, context
            server.httpProxy(req, res, context);
          };

          同樣地,我們將以上代碼在 FC 上部署、調(diào)用,執(zhí)行成功結(jié)果如下:

          看到最后,大家會(huì)發(fā)現(xiàn) API 網(wǎng)關(guān)觸發(fā)器和 HTTP 觸發(fā)器很多代碼邏輯是可以復(fù)用的,大家可以自行閱讀優(yōu)秀的源碼是如何實(shí)現(xiàn)的~

          其他部署到 Serverless 平臺(tái)的方案

          將傳統(tǒng) Web 框架部署到 Serverless 除了通過(guò)適配層轉(zhuǎn)換實(shí)現(xiàn),還可以通過(guò) Custom Runtime 或者 Custom Container Runtime (https://juejin.cn/post/6981921291980767269#heading-5) ,3 種方案總結(jié)如下:

          • 通過(guò)引入適配層,將函數(shù)計(jì)算接收的事件參數(shù)轉(zhuǎn)換為 HTTP 請(qǐng)求交給自定義的 Web Server 處理
          • 通過(guò) Custom Runtime
            • 本質(zhì)上也是一個(gè) HTTP Server,接管了函數(shù)計(jì)算平臺(tái)的所有請(qǐng)求,包括事件調(diào)用或者 HTTP 函數(shù)調(diào)用等
            • 開(kāi)發(fā)者需要?jiǎng)?chuàng)建一個(gè)啟動(dòng)目標(biāo) Server 的可執(zhí)行文件 bootstrap
          • 通過(guò) Custom Container Runtime
            • 工作原理與 Custom Runtime 基本相同
            • 開(kāi)發(fā)者需要把應(yīng)用代碼和運(yùn)行環(huán)境打包為 Docker 鏡像

          小結(jié)

          本文介紹了傳統(tǒng) Web 框架如何部署到 Serverless 平臺(tái)的方案:可以通過(guò)適配層和自定義(容器)運(yùn)行時(shí)。其中主要以 Express.js 和阿里云函數(shù)計(jì)算為例講解了通過(guò)適配層實(shí)現(xiàn)的原理和核心過(guò)程,其它 Web 框架 Serverless 化的原理也基本一致,騰訊云也提供了原理一樣的 tencent-serverless-http (https://github.com/serverless-plus/tencent-serverless-http) 方便大家直接使用(但騰訊云不支持 HTTP 觸發(fā)器),大家可以將自己所使用的 Web 框架對(duì)照云廠商函數(shù)計(jì)算的使用方法親自開(kāi)發(fā)一個(gè)適配層實(shí)踐一下~

          參考資料

          Webserverless - FC Express extension (https://github.com/awesome-fc/webserverless/tree/master/packages/fc-express)

          如何將 Web 框架遷移到 Serverless (https://zhuanlan.zhihu.com/p/152391799)

          Serverless 工程實(shí)踐 | 傳統(tǒng) Web 框架遷移 (https://developer.aliyun.com/article/790302)

          阿里云-觸發(fā)器簡(jiǎn)介 (https://help.aliyun.com/document_detail/53102.html)

          前端學(xué) serverless 系列—— WebApplication 遷移實(shí)踐 (https://zhuanlan.zhihu.com/p/72076708)



          往期推薦


          如果后端API一次返回10萬(wàn)條數(shù)據(jù),前端應(yīng)該如何處理?
          對(duì)話Svelte未來(lái),Rust 編譯器?構(gòu)建大型應(yīng)用?
          收藏!史上最全 Vue 前端代碼風(fēng)格指南

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 56
          點(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>
                  免费的黄色小视频 | 欧美日韩激情四射 | 天天干天天很天天狠狠 | 中文字幕在线播放第一页 | 无码操逼视频免费有声音 |