{"componentChunkName":"component---src-templates-best-practice-detail-tsx","path":"/best-practice/2020-07-01-serverless-http","result":{"data":{"currentBlog":{"id":"ac7f04ed-9f64-581f-b574-b0d5529a61d7","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/202072/1593676142784-http.jpg","authors":["Yugasun"],"categories":["best-practice"],"date":"2020-07-01T00:00:00.000Z","title":"如何将 Web 框架迁移到 Serverless","description":"传统 Web 服务想迁移到 Serverless 上，需要进行相关改造和特殊处理的。本文将具体帮助大家剖析下，如何 Serverless 化传统的 Web 服务","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless","Web"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":490,"sentences":89,"paragraphs":88},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-07-01-serverless-http.md","fields":{"slug":"/best-practice/2020-07-01-serverless-http/","keywords":["nodejs","serverless","云函数","Serverless","Web","server","serverless","http","Express","服务"]},"html":"<p>Serverless 通常翻译为「无服务架构」，是一种软件系统设计架构思想和方法，并不是一个开发框架或者工具。他的出现是为了让开发者更加关注业务的开发，而将繁杂的运维和部署交给云厂商。Serverless 由 Faas 和 Baas 组成，Faas 为开发者提供业务运算环境，然后与 Baas 提供的数据和存储服务，进行交互，从而提供与传统服务一致的体验。但是由于 Faas 是无状态的，并且其运行环境是有读写限制的，最重要的是它是基于事件触发的。因此如果传统 Web 服务想迁移到 Serverless 上，是需要进行相关改造和特殊处理的，为此迁移成本是必不可少的。本文将具体帮助大家剖析下，如何 Serverless 化传统的 Web 服务。</p>\n<p>读完本文将了解到：</p>\n<ol>\n<li><a href=\"#%E4%BC%A0%E7%BB%9F-Web-%E6%9C%8D%E5%8A%A1%E7%89%B9%E7%82%B9\">传统 Web 服务特点</a></li>\n<li><a href=\"#Serverless-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF\">Serverless 适用场景</a></li>\n<li><a href=\"#Web-%E6%A1%86%E6%9E%B6%E5%A6%82%E4%BD%95%E8%BF%81%E7%A7%BB%E5%88%B0-Serverless\">Web 框架如何迁移到 Serverless</a></li>\n<li><a href=\"#%E4%BD%BF%E7%94%A8-Serverless-Components-%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2-Web-%E6%A1%86%E6%9E%B6\">使用 Serverless Components 快速部署 Web 框架</a></li>\n</ol>\n<h2 id=\"传统-web-服务特点\"><a href=\"#%E4%BC%A0%E7%BB%9F-web-%E6%9C%8D%E5%8A%A1%E7%89%B9%E7%82%B9\" aria-label=\"传统 web 服务特点 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>传统 Web 服务特点</h2>\n<p>Web 服务定义：</p>\n<blockquote>\n<p>Web 服务是一种 <code class=\"language-text\">面向服务的架构</code> (SOA) 的技术，通过标准的 Web 协议提供服务，目的是保证不同平台的应用服务可以互操作。</p>\n</blockquote>\n<p>日常生活中，接触最多的就是基于 HTTP 协议的服务，客户端发起请求，服务端接受请求，进行计算处理，然后返回响应，简单示意图如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202072/1593676142984-http.jpg\" alt=\"web-service-flow\"></p>\n<p>传统 Web 服务部署流程：通常需要将项目代码部署到服务器上，启动服务进程，监听服务器的相关端口，然后等待客户端请求，从而响应返回处理结果。而这个服务进程是常驻的，就算没有客户端请求，也会占用相应服务器资源。</p>\n<p>一般我们的服务是由高流量和低流量场景交替组成的，但是为了考虑高流量场景，我们需要提供较高的服务器配置和多台服务进行负载均衡。这就导致服务处在低流量场景时，会多出很多额外的闲置资源，但是购买的资源却需要按照高流量场景进行付费，这是非常不划算的。</p>\n<p>如果我们的服务能在高流量场景自动扩容，低流量场景自动缩容，并且只在进行计算处理响应时，才进行收费，而空闲时间不占用任何资源，就不需要收费呢？</p>\n<p>答案就是 <code class=\"language-text\">Serverless</code>。</p>\n<h2 id=\"serverless-适用场景\"><a href=\"#serverless-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF\" aria-label=\"serverless 适用场景 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Serverless 适用场景</h2>\n<p>上面已经提到了 Serverless 的两个核心特点：<code class=\"language-text\">按需使用和收费</code> 和 <code class=\"language-text\">自动扩缩容</code>。而且近几年 Serverless 的应用也越来越广泛，但是它并不是银弹，任何技术都是有它的适合场景和不适合场景。我们不能因为一项技术的火热，而盲目的追捧。Serverless 是有它的局限性的，一般 Serverless 适合如下几种场景：</p>\n<ol>\n<li>异步的并发，组件可独立部署和扩展</li>\n<li>应对突发或服务使用量不可预测</li>\n<li>无状态，计算耗时较短服务</li>\n<li>请求延时不敏感服务</li>\n<li>需要快速开发迭代的业务</li>\n</ol>\n<p>如果你的服务不满足以上条件，笔者是不推荐迁移到 Serverless。</p>\n<h2 id=\"web-框架如何迁移到-serverless\"><a href=\"#web-%E6%A1%86%E6%9E%B6%E5%A6%82%E4%BD%95%E8%BF%81%E7%A7%BB%E5%88%B0-serverless\" aria-label=\"web 框架如何迁移到 serverless permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Web 框架如何迁移到 Serverless</h2>\n<p>如果你的服务是以上提到的任何话一个场景，那么就可以尝试迁移到 Serverless 上。</p>\n<p>常见的 Serverless HTTP 服务结构图如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202072/1593676142478-http.jpg\" alt=\"serverless-http-framework\"></p>\n<p>那么我们如何将 Web 服务进行迁移呢？</p>\n<p>我们知道 Faas （云函数）是基于事件触发的，也就是云函数被触发运行时，接收到的是一个 <code class=\"language-text\">JSON 结构体</code>，它跟传统 Web 请求时有区别的，这就是为什么需要额外的改造工作。而改造的工作就是围绕<code class=\"language-text\">如何将事件 JSON 结构体转化成标准的 Web 请求</code>。</p>\n<p>所以 Serverless 化 Web 服务的核心就是需要开发一个 <code class=\"language-text\">适配层</code>，来帮我们将触发事件转化为标准的 Web 请求。</p>\n<p>整个处理流程图如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202072/1593676143735-http.jpg\" alt=\"serverless-http-flow\"></p>\n<p>接下来将介绍如何为 Express 框架开发一个适配层。</p>\n<h3 id=\"serverless-express-适配层开发\"><a href=\"#serverless-express-%E9%80%82%E9%85%8D%E5%B1%82%E5%BC%80%E5%8F%91\" aria-label=\"serverless express 适配层开发 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Serverless Express 适配层开发</h3>\n<h4 id=\"实现原理\"><a href=\"#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86\" aria-label=\"实现原理 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>实现原理</h4>\n<p>首先我们先来看看一个标准的云函数结构：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"36296439570790117000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports.handler = (event, context) => {\n  // do some culculation\n  return res;\n};`, `36296439570790117000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">module<span class=\"token punctuation\">.</span>exports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">handler</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event<span class=\"token punctuation\">,</span> context</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// do some culculation</span>\n  <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>在介绍如何开发一个 Express 的适配层前，我们先来熟悉下 Express 框架。</p>\n<p>一个简单的 Node.js Web 服务如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"91067366333867070000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const http = require(&quot;http&quot;);\nconst server = http.createServer(function (req, res) {\n  res.end(&quot;helloword&quot;);\n});\nserver.listen(3000);`, `91067366333867070000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> http <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  res<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"helloword\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nserver<span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token number\">3000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Express 就是基于 Node.js 的 Web 框架，而 Express 核心就是 <code class=\"language-text\">通过中间件的方式，生成一个回调函数</code>，然后提供给 <code class=\"language-text\">http.createServer()</code> 方法使用。</p>\n<p>Express 核心架构图如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202072/1593676143739-http.jpg\" alt=\"express-framework\"></p>\n<p>由此可知，我们可以将 Express 框架生成的回调函数，作为 <code class=\"language-text\">http.createServer()</code> 的参数，来创建可控的 HTTP Server，然后将云函数的 <code class=\"language-text\">event</code> 对象转化成一个 <code class=\"language-text\">request</code> 对象，通过 <code class=\"language-text\">http.request()</code> 方法发起 HTTP 请求，获取请求响应，返回给用户，就可以实现我们想要的结果。</p>\n<h3 id=\"nodejs-server-的监听方式选择\"><a href=\"#nodejs-server-%E7%9A%84%E7%9B%91%E5%90%AC%E6%96%B9%E5%BC%8F%E9%80%89%E6%8B%A9\" aria-label=\"nodejs server 的监听方式选择 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Node.js Server 的监听方式选择</h3>\n<p>对于 Node.js 的 HTTP Server，可以通过调用 <code class=\"language-text\">server.listen()</code> 方法来启动服务，<code class=\"language-text\">listen()</code> 方法支持多种参数类型，主要有两种监听方式 <code class=\"language-text\">从一个TCP端口启动监听</code> 和 <code class=\"language-text\">从一个UNIX Socket套接字启动监听</code>。</p>\n<blockquote>\n<ul>\n<li><code class=\"language-text\">server.listen(port[, hostname][, backlog][, callback])</code>：从一个TCP端口启动监听</li>\n<li><code class=\"language-text\">server.listen(path, [callback])</code>：从一个UNIX Domain Socket启动监听</li>\n</ul>\n</blockquote>\n<p>服务器创建后，我们可以像下面这样启动服务器：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"31512366564846928000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// 从'127.0.0.1'和3000端口开始接收连接\nserver.listen(3000, '127.0.0.1', () => {});\n// 从 UNIX 套接字所在路径 path 上监听连接\nserver.listen('path/to/socket', () => {})`, `31512366564846928000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// 从'127.0.0.1'和3000端口开始接收连接</span>\nserver<span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token number\">3000</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'127.0.0.1'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// 从 UNIX 套接字所在路径 path 上监听连接</span>\nserver<span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token string\">'path/to/socket'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>无论是 <code class=\"language-text\">TCP Socket</code> 还是 <code class=\"language-text\">Unix Domain Socket</code>，每个 Socket 都是唯一的。<code class=\"language-text\">TCP Socket</code> 通过 <code class=\"language-text\">IP和端口</code> 描述，而 <code class=\"language-text\">Unix Domain Socket</code> 通过 <code class=\"language-text\">文件路径</code> 描述。</p>\n<p>TCP 属于传输层的协议，使用 TCP Socket 进行通讯时，需要经过传输层 TCP/IP 协议的解析。</p>\n<p>而 <code class=\"language-text\">Unix Domain Socket</code> 可用于不同进程间的通讯和传递，使用 <code class=\"language-text\">Unix Domain Socket</code> 进行通讯时不需要经过传输层，也不需要使用 <code class=\"language-text\">TCP/IP 协议</code>。所以，理论上讲 <code class=\"language-text\">Unix Domain Socket</code> 具有更好的传输效率。</p>\n<p>因此这里在设计启动服务时，采用了 <code class=\"language-text\">Unix Domain Socket</code> 方式，以便减少函数执行时间，节约成本。</p>\n<blockquote>\n<p>关于 Node.js 如何实现 IPC 通信，这里就不详细介绍的，感兴趣的小伙伴可以深入研究下，这里有个简单的示例，<a href=\"https://github.com/yugasun/nodejs-ipc\">nodejs-ipc</a></p>\n</blockquote>\n<h4 id=\"代码实现\"><a href=\"#%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0\" aria-label=\"代码实现 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>代码实现</h4>\n<p>原理大概介绍清楚了，我们的核心实现代码需要以下三步：</p>\n<ol>\n<li>通过 Node.js HTTP Server 监听 Unix Domain Socket，启动服务</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"47266531948173386000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`function createServer(requestListener, serverListenCallback) {\n  const server = http.createServer(requestListener);\n\n  server._socketPathSuffix = getRandomString();\n  server.on(&quot;listening&quot;, () => {\n    server._isListening = true;\n    if (serverListenCallback) serverListenCallback();\n  });\n  server\n    .on(&quot;close&quot;, () => {\n      server._isListening = false;\n    })\n    .on(&quot;error&quot;, (error) => {\n      // ...\n    });\n  server.listen(\\`/tmp/server-\\${server._socketPathSuffix}.sock\\`)\n  return server;\n}`, `47266531948173386000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">requestListener<span class=\"token punctuation\">,</span> serverListenCallback</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span>requestListener<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  server<span class=\"token punctuation\">.</span>_socketPathSuffix <span class=\"token operator\">=</span> <span class=\"token function\">getRandomString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  server<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"listening\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    server<span class=\"token punctuation\">.</span>_isListening <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>serverListenCallback<span class=\"token punctuation\">)</span> <span class=\"token function\">serverListenCallback</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  server\n    <span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"close\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      server<span class=\"token punctuation\">.</span>_isListening <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"error\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">error</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// ...</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  server<span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">/tmp/server-</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>server<span class=\"token punctuation\">.</span>_socketPathSuffix<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">.sock</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">return</span> server<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<ol start=\"2\">\n<li>将 Serverless Event 对象转化为 Http 请求</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"61443633583636630000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`function forwardRequestToNodeServer(server, event, context, resolver) {\n  try {\n    const requestOptions = mapApiGatewayEventToHttpRequest(\n      event,\n      context,\n      getSocketPath(server._socketPathSuffix),\n    );\n    // make http request to node server\n    const req = http.request(requestOptions, (response) =>\n      forwardResponseToApiGateway(server, response, resolver),\n    );\n    if (event.body) {\n      const body = getEventBody(event);\n      req.write(body);\n    }\n\n    req\n      .on('error', (error) =>\n        // ...\n      )\n      .end();\n  } catch (error) {\n    // ...\n    return server;\n  }\n}`, `61443633583636630000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">forwardRequestToNodeServer</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">server<span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">,</span> resolver</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> requestOptions <span class=\"token operator\">=</span> <span class=\"token function\">mapApiGatewayEventToHttpRequest</span><span class=\"token punctuation\">(</span>\n      event<span class=\"token punctuation\">,</span>\n      context<span class=\"token punctuation\">,</span>\n      <span class=\"token function\">getSocketPath</span><span class=\"token punctuation\">(</span>server<span class=\"token punctuation\">.</span>_socketPathSuffix<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// make http request to node server</span>\n    <span class=\"token keyword\">const</span> req <span class=\"token operator\">=</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">request</span><span class=\"token punctuation\">(</span>requestOptions<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">response</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n      <span class=\"token function\">forwardResponseToApiGateway</span><span class=\"token punctuation\">(</span>server<span class=\"token punctuation\">,</span> response<span class=\"token punctuation\">,</span> resolver<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> body <span class=\"token operator\">=</span> <span class=\"token function\">getEventBody</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      req<span class=\"token punctuation\">.</span><span class=\"token function\">write</span><span class=\"token punctuation\">(</span>body<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    req\n      <span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'error'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">error</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n        <span class=\"token comment\">// ...</span>\n      <span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>error<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// ...</span>\n    <span class=\"token keyword\">return</span> server<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<ol start=\"3\">\n<li>将 HTTP 响应转化为 API 网关标准数据结构</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"27761492387190390000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`function forwardResponseToApiGateway(server, response, resolver) {\n  response\n    .on(&quot;data&quot;, (chunk) => buf.push(chunk))\n    .on(&quot;end&quot;, () => {\n      // ...\n      resolver.succeed({\n        statusCode,\n        body,\n        headers,\n        isBase64Encoded,\n      });\n    });\n}`, `27761492387190390000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">forwardResponseToApiGateway</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">server<span class=\"token punctuation\">,</span> response<span class=\"token punctuation\">,</span> resolver</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  response\n    <span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"data\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">chunk</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> buf<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>chunk<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"end\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// ...</span>\n      resolver<span class=\"token punctuation\">.</span><span class=\"token function\">succeed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        statusCode<span class=\"token punctuation\">,</span>\n        body<span class=\"token punctuation\">,</span>\n        headers<span class=\"token punctuation\">,</span>\n        isBase64Encoded<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>最后函数的 handler 将异步请求返回就可以了。</p>\n<h4 id=\"借助-tencent-serverless-http-库实现\"><a href=\"#%E5%80%9F%E5%8A%A9-tencent-serverless-http-%E5%BA%93%E5%AE%9E%E7%8E%B0\" aria-label=\"借助 tencent serverless http 库实现 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>借助 tencent-serverless-http 库实现</h4>\n<p>如果不想手写这些适配层代码，可以直接使用 <a href=\"https://github.com/serverless-plus/tencent-serverless-http\">tencent-serverless-http</a> 模块。</p>\n<p>它使用起来很简单，创建我们的 Express 应用入口文件 <code class=\"language-text\">sls.js</code>：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"57227896499497484000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const express = require(&quot;express&quot;);\nconst app = express();\n\n// Routes\napp.get(\\`/\\`, (req, res) => {\n  res.send({\n    msg: \\`Hello Express\\`,\n  });\n});\n\nmodule.exports = app;`, `57227896499497484000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> express <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"express\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">express</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Routes</span>\napp<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">/</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    msg<span class=\"token punctuation\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">Hello Express</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> app<span class=\"token punctuation\">;</span></code></pre></div>\n<p>然后创建云函数 <code class=\"language-text\">sl_handler.js</code> 文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"21377790302070145000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const { createServer, proxy } = require(&quot;tencent-serverless-http&quot;);\nconst app = require(&quot;./app&quot;);\n\nexports.handler = async (event, context) => {\n  const server = createServer(app);\n  const result = await proxy(server, event, context, &quot;PROMISE&quot;).promise;\n};`, `21377790302070145000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> createServer<span class=\"token punctuation\">,</span> proxy <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"tencent-serverless-http\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./app\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nexports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">handler</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event<span class=\"token punctuation\">,</span> context</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span>app<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">proxy</span><span class=\"token punctuation\">(</span>server<span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">,</span> <span class=\"token string\">\"PROMISE\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>promise<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>接下来，将业务代码和依赖模块一起打包部署到云函数就可以了（记得指定 <code class=\"language-text\">执行方法</code> 为 <code class=\"language-text\">sl_handler.handler</code> ）。</p>\n<h3 id=\"其他-nodejs-框架\"><a href=\"#%E5%85%B6%E4%BB%96-nodejs-%E6%A1%86%E6%9E%B6\" aria-label=\"其他 nodejs 框架 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>其他 Node.js 框架</h3>\n<p>除了 <code class=\"language-text\">Express</code> 框架，其他的 Node.js 框架也基本类似，只需要按照要求，<code class=\"language-text\">exports</code> 一个 <code class=\"language-text\">HTTP Server</code> 的回调函数就可以。</p>\n<p>比如 <code class=\"language-text\">Koa</code>，我们拿到初始化的 <code class=\"language-text\">Koa</code> 应用后，只需要将 <code class=\"language-text\">app.callback()</code> 作为 <code class=\"language-text\">createServer()</code> 方法的参数就可以了，如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"51064737292451290000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const { createServer, proxy } = require(&quot;tencent-serverless-http&quot;);\nconst app = require(&quot;./app&quot;);\n\nexports.handler = async (event, context) => {\n  // 这里和 Express 略有区别\n  const server = createServer(app.callback());\n  const result = await proxy(server, event, context, &quot;PROMISE&quot;).promise;\n};`, `51064737292451290000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> createServer<span class=\"token punctuation\">,</span> proxy <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"tencent-serverless-http\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./app\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nexports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">handler</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event<span class=\"token punctuation\">,</span> context</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// 这里和 Express 略有区别</span>\n  <span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span>app<span class=\"token punctuation\">.</span><span class=\"token function\">callback</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">proxy</span><span class=\"token punctuation\">(</span>server<span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">,</span> <span class=\"token string\">\"PROMISE\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>promise<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"其他语言框架\"><a href=\"#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E6%A1%86%E6%9E%B6\" aria-label=\"其他语言框架 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>其他语言框架</h3>\n<p>对于非 Node.js 框架，比如 <code class=\"language-text\">Python</code> 的 <code class=\"language-text\">Flask</code> 框架，原理都是一样的，核心只需要做到 <code class=\"language-text\">将 Serverless Event 对象转化为 Http 请求</code>，就可以了。由于笔者对其他语言不太熟悉，这里就不做深入介绍了，感兴趣的小伙伴，可以到 Github 社区搜索下，已经有很多对应的解决方案了，或者自己尝试手撸也是可以的。</p>\n<h2 id=\"使用-serverless-components-快速部署-web-框架\"><a href=\"#%E4%BD%BF%E7%94%A8-serverless-components-%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2-web-%E6%A1%86%E6%9E%B6\" aria-label=\"使用 serverless components 快速部署 web 框架 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>使用 Serverless Components 快速部署 Web 框架</h2>\n<p>读到这里，相信你已经清楚，如何将自己的 Node.js 框架迁移到 Serverless 了。但是在这之前，我们都是手动处理的，而且每次都需要自己创建 <code class=\"language-text\">handler.js</code> 文件，还是不够方便。</p>\n<p>为此开源社区提供了一套优秀的解决方案 <a href=\"https://github.com/serverless/components\">Serverless Component</a>，通过组件，我们进行简单的 <code class=\"language-text\">yaml</code> 文件配置后，就可以方便的将我们的框架代码部署到云端。</p>\n<p>比如上面提到的 <code class=\"language-text\">Express</code> 框架，就有对应的组件，我们只需要在项目根目录下创建 <code class=\"language-text\">serverless.yml</code> 配置文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"52366134790308690000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`component: express\nname: expressDemo\n\ninputs:\n  src: ./\n  region: ap-guangzhou\n  runtime: Nodejs10.15\n  apigatewayConf:\n    protocols:\n      - https\n    environment: release`, `52366134790308690000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> express\n<span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> expressDemo\n\n<span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">src</span><span class=\"token punctuation\">:</span> ./\n  <span class=\"token key atrule\">region</span><span class=\"token punctuation\">:</span> ap<span class=\"token punctuation\">-</span>guangzhou\n  <span class=\"token key atrule\">runtime</span><span class=\"token punctuation\">:</span> Nodejs10.15\n  <span class=\"token key atrule\">apigatewayConf</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">protocols</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> https\n    <span class=\"token key atrule\">environment</span><span class=\"token punctuation\">:</span> release</code></pre></div>\n<p>然后全局安装 <code class=\"language-text\">serverless</code> 命令 <code class=\"language-text\">npm install serverless -g</code> 之后，执行部署命令即可：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"32943054473142830000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ serverless deploy`, `32943054473142830000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ serverless deploy</code></pre></div>\n<p>耐心等待几秒，我们的 Express 应用就成功部署到云端了。更多详细信息，请参考 <a href=\"https://github.com/serverless-components/tencent-express/tree/v2\">Express 官方文档</a></p>\n<blockquote>\n<p>注意：本文 Serverless 服务均基于 <code class=\"language-text\">腾讯云</code> 部署。</p>\n</blockquote>\n<p>Serverless Express 组件不仅能帮我们快速部署 Express 应用，而且它还提供了 <code class=\"language-text\">实时日志</code> 和 <code class=\"language-text\">云端调试</code> 的能力。</p>\n<p>只需要在项目目录下执行 <code class=\"language-text\">serverless dev</code> 命令，serverless 命令行工具就会自动监听项目业务代码的更改，并且实时部署，同时我们可以通过打开 Chrome Devtools 来调试 Express 应用。</p>\n<blockquote>\n<p>关于云端调试，<a href=\"https://serverlesscloud.cn/blog/2020-04-07-sfga/\">腾讯云 Serverless Framework 正式发布公告</a> 中有详细的介绍，并且有视频演示。</p>\n</blockquote>\n<p>而且除了 Express 组件，还支持： Koa.js，Egg.js，Next.js，Nuxt.js.....</p>\n<p><a href=\"https://registry.serverless.com/\">发现更多组件</a></p>\n<h2 id=\"最后\"><a href=\"#%E6%9C%80%E5%90%8E\" aria-label=\"最后 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>最后</h2>\n<p>当然 Serverless 化 Web 服务并没有本文介绍的那么简单，比如文件读写，服务日志存储，<code class=\"language-text\">Cookie/Session</code> 存储等......实际开发中，我们还会面临各种未知的坑，但是比起困难，Serverless 带给我们的收益是值得去尝试的。当然传统 Web 服务真的适合迁移到 Serverless 架构上，也是值得我们去思考的问题，毕竟现有的 Web 框架都是面向传统 Web 服务开发实现的 （推荐阅读 <a href=\"https://www.serverlesschina.com/post/43.html\">利与弊-传统框架要不要部署在 Serverless 架构上</a>）。但是笔者相信，很快就会出现一个专门为 Serverless 而生的 Web 框架，可以帮助我们更好地基于 Serverless 开发应用 ~</p>\n<hr>\n<hr>\n<div id='scf-deploy-iframe-or-md'></div>\n<hr>\n<blockquote>\n<p><strong>传送门：</strong></p>\n<ul>\n<li>GitHub: <a href=\"https://github.com/serverless/serverless/blob/master/README_CN.md\">github.com/serverless</a></li>\n<li>官网：<a href=\"https://serverless.com/\">serverless.com</a></li>\n</ul>\n</blockquote>\n<p>欢迎访问：<a href=\"https://serverlesscloud.cn/\">Serverless 中文网</a>，您可以在 <a href=\"https://serverlesscloud.cn/best-practice\">最佳实践</a> 里体验更多关于 Serverless 应用的开发！</p>","tableOfContents":"<ul>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E4%BC%A0%E7%BB%9F-web-%E6%9C%8D%E5%8A%A1%E7%89%B9%E7%82%B9\">传统 Web 服务特点</a></li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#serverless-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF\">Serverless 适用场景</a></li>\n<li>\n<p><a href=\"/best-practice/2020-07-01-serverless-http/#web-%E6%A1%86%E6%9E%B6%E5%A6%82%E4%BD%95%E8%BF%81%E7%A7%BB%E5%88%B0-serverless\">Web 框架如何迁移到 Serverless</a></p>\n<ul>\n<li>\n<p><a href=\"/best-practice/2020-07-01-serverless-http/#serverless-express-%E9%80%82%E9%85%8D%E5%B1%82%E5%BC%80%E5%8F%91\">Serverless Express 适配层开发</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86\">实现原理</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-07-01-serverless-http/#nodejs-server-%E7%9A%84%E7%9B%91%E5%90%AC%E6%96%B9%E5%BC%8F%E9%80%89%E6%8B%A9\">Node.js Server 的监听方式选择</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0\">代码实现</a></li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E5%80%9F%E5%8A%A9-tencent-serverless-http-%E5%BA%93%E5%AE%9E%E7%8E%B0\">借助 tencent-serverless-http 库实现</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E5%85%B6%E4%BB%96-nodejs-%E6%A1%86%E6%9E%B6\">其他 Node.js 框架</a></li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E6%A1%86%E6%9E%B6\">其他语言框架</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E4%BD%BF%E7%94%A8-serverless-components-%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2-web-%E6%A1%86%E6%9E%B6\">使用 Serverless Components 快速部署 Web 框架</a></li>\n<li><a href=\"/best-practice/2020-07-01-serverless-http/#%E6%9C%80%E5%90%8E\">最后</a></li>\n</ul>"},"previousBlog":{"id":"a95f844d-eae9-5465-b1e8-d5dcd4464453","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020713/1594610687799-Nextjs-93.jpg","authors":["Yugasun"],"categories":["best-practice"],"date":"2020-07-12T00:00:00.000Z","title":"如何优雅地部署一个 Serverless Next.js 应用","description":"本篇专门针对 Next.js 的 SSR 方案进行了探索和优化，一步一步带大家了解，如何基于 Serverless 架构部署一个实际的线上业务","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless SSR","Next.js"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":478,"sentences":81,"paragraphs":81},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-07-12-serverless-nextjs.md","fields":{"slug":"/best-practice/2020-07-12-serverless-nextjs/","keywords":["nextjs","serverless","ssr","云函数","serverless","Next","部署","nextjs","静态","Serverless","Layer"]}},"nextBlog":{"id":"9a325d75-7685-5ae2-88f2-ca493e145e28","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020623/1592900177481-00%E8%80%81%E5%8F%B8%E6%9C%BA.jpg","authors":["王盛/张升涛"],"categories":["best-practice"],"date":"2020-06-23T00:00:00.000Z","title":"腾讯云 Serverless 技术在「老司机」的落地实践","description":"「老司机」在其推荐系统、用户画像和后端的业务上使用腾讯云 serverless 技术。","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["云函数 SCF","客户案例"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":171,"sentences":22,"paragraphs":22},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-06-23-driver-case.md","fields":{"slug":"/best-practice/2020-06-23-driver-case/","keywords":["serverless","Serverless","服务","开发人员","部署","serverless","迁移","技术","汽车"]}}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"ac7f04ed-9f64-581f-b574-b0d5529a61d7","previousBlogId":"a95f844d-eae9-5465-b1e8-d5dcd4464453","nextBlogId":"9a325d75-7685-5ae2-88f2-ca493e145e28"}}}