NodeServe:构建高效静态文件服务器的完美指南

151 阅读4分钟

读过这篇文章《用Node.js吭哧吭哧撸一个运动主页》的同学肯定知道,前段时间我搭了一个运动页面。现在我有个想法就是把它用Docker打包镜像推送到Docker官方仓库,像《前端切图仔入门Docker,三分钟上线自己的博客平台》文章中搭建博客系统一样,提供一个运动主页的Docker镜像,方便有想法的同学三分钟部署自己的运动主页。

什么是静态文件服务器

作为前端搬砖工,一定接触过静态文件服务器。静态文件服务器它的工作是将静态文件通过http/https传输给客户端。静态文件又是什么?静态文件是指内容不需要动态生成的文件,如:图片、CSS文件、JS文件等等文件。我们常用的静态文件服务器有webpack-dev-server这也是为什么我们能在本地开发环境可以通过链接访问页面的原因,还有就是Nginx,一般线上环境使用它,因为它性能更加高效、稳定。

运动主页简单,完全没必要在Docker打包时再下载Nginx镜像打包进去,直接用Node.js实现静态文件服务器的功能即可。

功能介绍

我需要的静态服务器只需要一个功能:

  • 当用户请求的内容是文件时,返回文件内容

项目静态文件结构是这样的:

client
          ├─src
          │  └─js文件
          └─index.html

详细文件结构请访问仓库地址

启动项目

拉取项目:

git clone https://github.com/CatsAndMice/keep

下载依赖:

npm i 

创建.env文件,写入Keep帐号、密码:

MOBILE="Keep帐号"
          PASSWORD="Keep密码"

最后,执行npm run serve,项目即可启动。当我们直接访问

http://localhost:3000就能访问index.html文件,index.html文件内容如下:

代码实现

根据上文的需求描述,我们先用流程图来设计一下我们的逻辑如何实现:

静态文件服务器的实现思路还是很简单的:先判断资源存不存在,不存在就直接报错,资源存在的话根据资源的类型返回对应的结果给客户端就可以了。

Express实现

运动主页非常简单,很多静态JS、图片文件已放置在云端,index.html文件只引用了一份本地的JS文件。不论是什么请求方式,当有请求路径存在/src时都会走app.all('/src/*',()⇒{...})

不同的静态文件对应不同的请求头Content-Type值,如:JS文件对应application/javascript 、CSS文件对应text/css等等,点Content-Type类型可以查看还有哪些值。

Content-Type 标头目的是为了告诉客户端实际返回的内容的内容类型,以便客户端依据文件类型进行解析。

                      //省略其他逻辑
                      
                      //路径存在"/src",执行下列代码块逻辑
                      app.all('/src/*', (req, res) => {
                          const { url } = req;
                              const filePath = htmlPath + url;
                                  //判断文件是否存在
                                      if (fs.existsSync(filePath)) {
                                              const extname = path.extname(filePath);
                                                      res.setHeader('Content-Type', contentType[extname]);
                                                              const content = fs.readFileSync(filePath);
                                                                      res.send(content);
                                                                              return
                                                                                  }
                                                                                      //不存在,浏览器状态码返回404
                                                                                          res.sendStatus('404')
                                                                                          })
                                                                                          
                                                                                          //访问http://localhost:3000时,执行这部分逻辑返回index.html内容
                                                                                          app.get('/', async (req, res) => {
                                                                                              res.setHeader('Content-Type', 'text/html;charset=utf8');
                                                                                                  const readHtmlPath = htmlPath + '/index.html'
                                                                                                      const html = fs.readFileSync(readHtmlPath)
                                                                                                          res.send(html)
                                                                                                          })
                                                                                                          
                                                                                                          app.listen(port, () => {
                                                                                                              console.log('服务已开启');
                                                                                                              })" aria-label="复制" data-bs-original-title="复制">
                      <i class="far fa-copy"></i>
          </button>
</div>
      </div><pre class="javascript hljs language-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
      <span class="hljs-keyword">const</span> { getTotal, getFirstPageRecentUpdates } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./src"</span>)
      <span class="hljs-keyword">const</span> { to } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'await-to-js'</span>);
      <span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
      <span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node:fs'</span>)
      <span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>();
      <span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>;
      <span class="hljs-comment">//获取client文件夹的绝对路径</span>
      <span class="hljs-keyword">const</span> htmlPath = path.<span class="hljs-title function_">join</span>(__dirname, <span class="hljs-string">'./client'</span>);
      <span class="hljs-comment">//请求头Content-Type值</span>
      <span class="hljs-keyword">const</span> contentType = {
          <span class="hljs-string">'.js'</span>: <span class="hljs-string">'application/javascript;charset=utf8'</span>
          }
          
          <span class="hljs-comment">//省略其他逻辑</span>
          
          <span class="hljs-comment">//路径存在"/src",执行下列代码块逻辑</span>
          app.<span class="hljs-title function_">all</span>(<span class="hljs-string">'/src/*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
              <span class="hljs-keyword">const</span> { url } = req;
                  <span class="hljs-keyword">const</span> filePath = htmlPath + url;
                      <span class="hljs-comment">//判断文件是否存在</span>
                          <span class="hljs-keyword">if</span> (fs.<span class="hljs-title function_">existsSync</span>(filePath)) {
                                  <span class="hljs-keyword">const</span> extname = path.<span class="hljs-title function_">extname</span>(filePath);
                                          res.<span class="hljs-title function_">setHeader</span>(<span class="hljs-string">'Content-Type'</span>, contentType[extname]);
                                                  <span class="hljs-keyword">const</span> content = fs.<span class="hljs-title function_">readFileSync</span>(filePath);
                                                          res.<span class="hljs-title function_">send</span>(content);
                                                                  <span class="hljs-keyword">return</span>
                                                                      }
                                                                          <span class="hljs-comment">//不存在,浏览器状态码返回404</span>
                                                                              res.<span class="hljs-title function_">sendStatus</span>(<span class="hljs-string">'404'</span>)
                                                                              })
                                                                              
                                                                              <span class="hljs-comment">//访问http://localhost:3000时,执行这部分逻辑返回index.html内容</span>
                                                                              app.<span class="hljs-title function_">get</span>(<span class="hljs-string">'/'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
                                                                                  res.<span class="hljs-title function_">setHeader</span>(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'text/html;charset=utf8'</span>);
                                                                                      <span class="hljs-keyword">const</span> readHtmlPath = htmlPath + <span class="hljs-string">'/index.html'</span>
                                                                                          <span class="hljs-keyword">const</span> html = fs.<span class="hljs-title function_">readFileSync</span>(readHtmlPath)
                                                                                              res.<span class="hljs-title function_">send</span>(html)
                                                                                              })
                                                                                              
                                                                                              app.<span class="hljs-title function_">listen</span>(port, <span class="hljs-function">() =&gt;</span> {
                                                                                                  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'服务已开启'</span>);
                                                                                                  })</pre><p>写一个能用的静态文件服务器还是简单的,这里依赖Express框架方便得多。原生模块实现有兴趣的同学可以去写写。</p><h2 id="item-6">最后</h2><p>文章中介绍实现了一个最简单能用的<code>静态文件服务器</code>,如果开发一个完善的静态文件服务器还有非常多的功能要考虑,如:静态文件缓存、压缩等等。</p><p>另外还有一个真实案例给大家实践,有兴趣的同学<code>clone</code>下来自己玩玩。</p><p>如果我的文章对你有帮助,您的👍就是对我的最大支持^_^。</p><p>欢迎围观朋友圈、加我微信拉您加入<a target="_blank" href="https://link.segmentfault.com/?enc=ap%2BeFUkQrcNuiwYHsmvhTg%3D%3D.8WjfIjDUKCv8aajxzpDzvEBtX2CrzI99Qj%2B7CS9rxsU%3D"><strong>人类高质量前端交流群</strong></a></p>