简单的Web服务器,在5分钟内即可完成SPA / PWA

如何仅使用标准Node.js指令创建简单的Web服务器


通常,需要一个简单的Web服务器来开发MPA / SPA / PWA应用程序。 一次,在一次大型集会上,我回答了一个问题:“你在做什么?”,我说我正在筹集一个用于托管PWA应用程序的Web服务器。 我们都笑了很久,是的,顺便说一句,PWA不是胶水。 像SPA一样,它不是美容院。 这些都是各种Web应用程序。 而且SSR不是一个国家:-)。 如果仅通过浏览器打开index.html的起始页面来启动这样的应用程序,它将无法正常工作,在最佳情况下,我们将获得脱机版本。 我喜欢JavaScript,并且会使用现成的方式解决问题,这是开箱即用的。


让我们从计划开始:


  1. 如果没有NodeJS,请下载LTS,安装,不要更改设置,请单击下一步
  2. 在收集所有项目的僻静地方,创建simple-web-server文件夹
  3. 在项目文件夹中,执行命令npm init --yes //不使用--yes,初始化程序会提出很多问题
  4. 脚本部分的package.json文件中,添加属性及其值- “ main”:“ index.js” -这样我们可以使用npm run命令快速启动服务器。
  5. 创建一个lib文件夹建议将所有代码放入其中,这不需要汇编和其他操作步骤
  6. lib文件夹中创建一个index.js文件,这是我们将来的服务器。
  7. 创建一个dist文件夹-该文件夹中将存在可公开访问的文件,包括index.html,换句话说就是我们服务器将分发的静态文件
  8. 打开文件/index.js
  9. 写一些代码

那么,我们对服务器应该做什么有什么了解?


  1. 处理请求
  2. 读取文件
  3. 回复带有文件内容的请求

首先,创建我们的服务器,将其导入到idex.js文件中


const {createServer} = require('http'); 

该指令对http模块的对象进行解构,并为createServer变量标识符分配一个表达式createServer函数。


使用以下语句创建新服务器


  const server = createServer(); 

当您第一次进入服务器主机时,浏览器会发送一个接收文档的请求。 因此,为了处理此事件,我们需要侦听此类请求。 我们创建的服务器具有一个listen方法,我们将端口号3000作为参数传递。


  const eventsEmitter = server.listen(3000); 

此方法的表达式将是一个EventEmitter对象,该对象将存储在带有标识符eventsEmitter的变量中。 该对象是可观察到的。 我们使用带有两个必需的字符串 函数参数的on / addEventListener方法调用来订阅其事件。 第一个参数指示我们感兴趣的事件;第二个参数是将处理该事件的函数。


  eventsEmitter.on('request', (req, res) => { debugger; }); 

在浏览器中打开链接


图片


因此,我们确定了调试器指令。 我们看到作为参数,我们得到了两个req对象,即res对象,这些对象是类型对象的实例因此req是读取流,而res是写入流。


我们处理了该请求,我们可以说“事情在帽子里”。 剩下的就是读取并退还文件。 首先,您需要了解我们需要哪种文件。 在调试器中,研究了req参数的所有属性后,我发现它具有url属性。 但是里面没有像index.html这样的东西


让我们再次看一下浏览器:我们发现我们没有明确指出这一点。 让我们再试一次,但我们已经明确指定index.html


图片


现在很明显,文件名出现在url属性中的请求中,仅此而已,我们不需要从请求中读取文件。 我们使用一对花括号将其分解,指定url属性的名称,并通过运算符使用有效的变量标识符(在我的情况下为requestUrl)设置任意名称。


  eventsEmitter.addListener('request', ({url: requestUrl}, res) => { debugger }); 

太好了,接下来要做什么? 实际上,我真的不喜欢总是需要显式指定index.html的事实,因此让我们立即解决此问题。 我认为最简单的方法是使用标准extname函数它包含在标准软件包中
我们使用以下语句将路径模块的NodeJS导入。


  const {extname} = require('path'); 

现在,您可以通过传递requestUrl标识符表达式作为参数来调用它,并获取近似格式为'.extension'的字符串的表达式。 如果请求未明确指定文件,则将返回一个空字符串。 使用此原理,我们将添加默认值'index.html' 。 我们写以下指令


  const url = extname(requestUrl) === '' ? DEFAULT_FILE_NAME : requestUrl; 

我确定服务器用户将要覆盖此名称,并使用以下语句设置环境变量


  const {env: {DEFAULT_FILE_NAME = '/index.html'}} = process;` 

在全局变量过程中,有很多有用的信息,我将其中的一部分埋葬,特别是env属性包含用户环境的所有属性,如果用户未指定,则我们将在其中查找DEFAULT_FILE_NAME-我们默认使用index.html。


重要说明:如果环境属性DEFAULT_FILE_NAME的值不是未定义的,则分配默认值将不起作用。 这值得记住,但不是现在,我们正在尽一切努力:-)

图片


现在我们有了文件的相对链接,我们需要获取服务器文件系统中文件的绝对路径。 我们决定将所有公共文件都存储在dist文件夹中,因此要获取文件的绝对路径 ,我们将使用已知模块中的另一个resolve函数
路径仅在第5行的先前创建的指令中指出


  const {resolve, extname} = require('path'); 

接下来的第10行,我们编写了一条指令,该指令将接收并保存绝对路径到 filePath变量中我也提前“ wang”了一下,此文件夹的名称可以重新定义以提高灵活性。 因此,我在第6行上扩展了指令,并添加了名称
环境变量DIST_FOLDER


图片


现在一切就绪,可以读取文件了。 您可以异步,同步地以不同方式读取文件,也可以使用stream 。 从消耗的资源的角度来看,我将使用流:-)它是美丽且更有效的。 首先,在dist文件夹中创建一个测试文件,以便于阅读:-)


 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> TESTING 1,2,3... </body> </html> 

现在我们需要一个函数来创建文件读取流,它也包含在标准NodeJS发行版中,我们使用以下指令从fs模块中提取文件


  const {createReadStream} = require('fs'); 

在请求处理器主体的第12行,我们使用以下语句


  createReadStream(filePath) 

结果,将使用它返回读取流对象的实例
我们可以将读取流切换为写入流,也可以转换流和许多其他有用的东西。 所以res参数是读取流,对吗?


让我们尝试立即将我们创建的文件读取流切换为用于
其中,在第12行,我们通过调用pipe方法继续执行该指令,并作为参数传递了写入流res


  createReadStream(filePath).pipe(res); 

图片


这就是全部吗? 不 谁来处理错误? 什么样的错误? 让我们尝试将css文件上传到index.html文件,但是我们不会创建它,看看会发生什么:-)


 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="index1.css"> </head> <body> TESTING 1,2,3... </body> </html> 

图片


预期中,但是服务器崩溃了! 事实并非如此。 事实是,默认情况下错误不会被捕获到流中,您需要自己执行此操作:-) createReadStream返回发生错误的流。 因此,我们添加了一个错误处理程序。 使用对on方法的调用,指定错误事件的名称,并为处理程序指定函数。 它将使用404响应代码终止读取的读取流。


  createReadStream(filePath) .on('error', error => res.writeHead(404).end()) .pipe(res); 

检查!


图片


另一件事。 顺便说一句,服务器还没有准备好,如果我们尝试在其他浏览器中打开它,则该页面将无法正常工作:-)谁猜到了,请在评论中写下来,您忘记了做什么? 事实是,当服务器用文件回答服务器的请求时,一个扩展名不足以使浏览器了解该文件的类型,而其他浏览器则不是:chrome和较早版本都无法在不指定Content-Type响应标头的情况下处理下载的文件。处理文件,其中,我们的服务器必须指定MIME类型,为此,我们将创建一个包含所有常见May类型的单独变量。 我们还提供了通过传递为环境变量来扩展它们的机会


  const {env: {DEFAULT_FILE_NAME = '/index.html', DIST_FOLDER = 'dist', DEFAULT_MIME_TYPES = '{}'}} = process; const {text} = mimeTypes = { 'html': 'text/html', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'js': 'text/javascript', 'css': 'text/css', 'text': 'plain/text', 'json': 'application/json', ...JSON.parse(DEFAULT_MIME_TYPES) }; 

好了,现在您需要以某种方式指定MIME类型,然后再将读取流切换到写入流。 我使用文件扩展名作为键,因此我们将使用熟悉的extname函数获得文件扩展名


  const fileExtension = extname(url).split('.').pop(); 

并在管道事件处理程序的帮助下,设置了所需的MIME类型


 res.on('pipe', () => res.setHeader(contentType, mimeTypes[fileExtension] || text)); 

检查一下


图片


就是这样-服务器已准备就绪。 当然,它并不是完美的,但是快速入门就可以了。 如果您有兴趣发展这个想法,请在评论中写:-)


完整的项目代码



Source: https://habr.com/ru/post/zh-CN481898/


All Articles