轻轻扬 發表於 2021-8-24 19:37:00

Node.js(三)node.js的常用内置模块

<h1>前言</h1>
<p>因为Node.js是运行在服务区端的JavaScript环境,服务器程序和浏览器程序相比,最大的特点是没有浏览器的安全限制了,而且,服务器程序必须能接收网络请求,读写文件,处理二进制内容,所以,Node.js内置的常用模块就是为了实现基本的服务器功能。这些模块在浏览器环境中是无法被执行的,因为它们的底层代码是用C/C++在Node.js运行环境中实现的。</p>
<h3>global</h3>
<p>在前面的JavaScript课程中,我们已经知道,JavaScript有且仅有一个全局对象,在浏览器中,叫<code>window</code>对象。而在Node.js环境中,也有唯一的全局对象,但不叫<code>window</code>,而叫<code>global</code>,这个对象的属性和方法也和浏览器环境的<code>window</code>不同。进入Node.js交互环境,可以直接输入:</p>
<div class="cnblogs_code">
<pre>&gt; <span style="color: rgba(0, 0, 255, 1)">global</span><span style="color: rgba(0, 0, 0, 1)">.console
Console {
log: ,
info: ,
warn: ,
error: ,
dir: ,
time: ,
timeEnd: ,
trace: ,
assert: ,
Console: }</span></pre>
</div>
<p>&nbsp;</p>
<h3>process</h3>
<p><code>process</code>也是Node.js提供的一个对象,它代表当前Node.js进程。通过<code>process</code>对象可以拿到许多有用信息:</p>
<div class="cnblogs_code">
<pre>&gt; process === <span style="color: rgba(0, 0, 255, 1)">global</span><span style="color: rgba(0, 0, 0, 1)">.process;
</span><span style="color: rgba(0, 0, 255, 1)">true</span>
&gt;<span style="color: rgba(0, 0, 0, 1)"> process.version;
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">v5.2.0</span><span style="color: rgba(128, 0, 0, 1)">'</span>
&gt;<span style="color: rgba(0, 0, 0, 1)"> process.platform;
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">darwin</span><span style="color: rgba(128, 0, 0, 1)">'</span>
&gt;<span style="color: rgba(0, 0, 0, 1)"> process.arch;
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">x64</span><span style="color: rgba(128, 0, 0, 1)">'</span>
&gt; process.cwd(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">返回当前工作目录</span>
<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/Users/michael</span><span style="color: rgba(128, 0, 0, 1)">'</span>
&gt; process.chdir(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/private/tmp</span><span style="color: rgba(128, 0, 0, 1)">'</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 切换当前工作目录</span>
<span style="color: rgba(0, 0, 0, 1)">undefined
</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> process.cwd();
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/private/tmp</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<p>JavaScript程序是由事件驱动执行的单线程模型,Node.js也不例外。Node.js不断执行响应事件的JavaScript函数,直到没有任何响应事件的函数可以执行时,Node.js就退出了。</p>
<p>如果我们想要在下一次事件响应中执行代码,可以调用<code>process.nextTick()</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> test.js

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> process.nextTick()将在下一轮事件循环中调用:</span>
<span style="color: rgba(0, 0, 0, 1)">process.<strong>nextTick</strong>(function () {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nextTick callback!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
});
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nextTick was set!</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>用Node执行上面的代码<code>node test.js</code>,你会看到,打印输出是:</p>
<div class="cnblogs_code">
<pre>nextTick was <span style="color: rgba(0, 0, 255, 1)">set</span>!<span style="color: rgba(0, 0, 0, 1)">
nextTick callback</span>!</pre>
</div>
<p>这说明传入<code>process.nextTick()</code>的函数不是立刻执行,而是要等到下一次事件循环。</p>
<p>Node.js进程本身的事件就由<code>process</code>对象来处理。如果我们响应<code>exit</code>事件,就可以在程序即将退出时执行某个回调函数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 程序即将退出时的回调函数:</span>
process.on(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">exit</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (code) {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">about to exit with code: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> code);
});</span></pre>
</div>
<h3>判断JavaScript执行环境</h3>
<p>有很多JavaScript代码既能在浏览器中执行,也能在Node环境执行,但有些时候,程序本身需要判断自己到底是在什么环境下执行的,常用的方式就是根据浏览器和Node环境提供的全局变量名称来判断:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span>(window) === <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">undefined</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">node.js</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">browser</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>后面,我们将介绍Node.js的常用内置模块。</p>
<p>&nbsp;</p>
<h1><span style="color: rgba(255, 102, 0, 1)"><strong>一、fs</strong></span></h1>
<p>Node.js内置的<code>fs</code>模块就是文件系统模块,负责读写文件。</p>
<p>和所有其它JavaScript模块不同的是,<code>fs</code>模块同时提供了异步和同步的方法。</p>
<p>回顾一下什么是<span style="color: rgba(255, 0, 0, 1)"><strong>异步方法</strong></span>。因为JavaScript的单线程模型,执行IO操作时,JavaScript代码无需等待,而是传入回调函数后,继续执行后续JavaScript代码。比如jQuery提供的<code>getJSON()</code>操作:</p>
<div class="cnblogs_code">
<pre>$.getJSON(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://example.com/ajax</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (data) {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">IO结果返回后执行...</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
});
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">不等待IO结果直接执行后续代码...</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>而同步的IO操作则需要等待函数返回:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据网络耗时,函数将执行几十毫秒~几秒不等:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> data = getJSONSync(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://example.com/ajax</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>同步操作的好处是代码简单,缺点是程序将等待IO操作,在等待时间内,无法响应其它任何事件。而异步读取不用等待IO操作,但代码较麻烦。</p>
<h3>异步读文件</h3>
<p>按照JavaScript的标准,异步读取一个文本文件的代码如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

fs.readFile(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (err, data) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
      console.log(err);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      console.log(data);
    }
});</span></pre>
</div>
<p>请注意,<code>sample.txt</code>文件必须在当前目录下,且文件编码为<code>utf-8</code>。</p>
<p>异步读取时,传入的回调函数接收两个参数,当正常读取时,<code>err</code>参数为<code>null</code>,<code>data</code>参数为读取到的String。当读取发生错误时,<code>err</code>参数代表一个错误对象,<code>data</code>为<code>undefined</code>。这也是Node.js标准的回调函数:第一个参数代表错误信息,第二个参数代表结果。后面我们还会经常编写这种回调函数。</p>
<p>由于<code>err</code>是否为<code>null</code>就是判断是否出错的标志,所以通常的判断逻辑总是:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 出错了</span>
} <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 正常</span>
}</pre>
</div>
<p>如果我们要读取的文件不是文本文件,而是二进制文件,怎么办?</p>
<p>下面的例子演示了如何读取一个图片文件:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

fs.readFile(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.png</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (err, data) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
      console.log(err);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      console.log(data);
      console.log(data.length </span>+ <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)"> bytes</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    }
});</span></pre>
</div>
<p>当读取二进制文件时,不传入文件编码时,回调函数的<code>data</code>参数将返回一个<code>Buffer</code>对象。在Node.js中,<code>Buffer</code>对象就是一个包含零个或任意个字节的数组(注意和Array不同)。</p>
<p><code>Buffer</code>对象可以和String作转换,例如,把一个<code>Buffer</code>对象转换成String:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Buffer -&gt; String</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> text = data.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
console.log(text);</span></pre>
</div>
<p>或者把一个String转换成<code>Buffer</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> String -&gt; Buffer</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> buf = Buffer.<span style="color: rgba(0, 0, 255, 1)">from</span>(text, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
console.log(buf);</span></pre>
</div>
<h3>同步读文件</h3>
<p>除了标准的异步读取模式外,<code>fs</code>也提供相应的同步读取函数。同步读取的函数和异步函数相比,多了一个<code>Sync</code>后缀,并且不接收回调函数,函数直接返回结果。</p>
<p>用<code>fs</code>模块同步读取一个文本文件的代码如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> data = fs.readFileSync(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
console.log(data);</span></pre>
</div>
<p>可见,原异步调用的回调函数的<code>data</code>被函数直接返回,函数名需要改为<code>readFileSync</code>,其它参数不变。</p>
<p>如果同步读取文件发生错误,则需要用<code>try...catch</code>捕获该错误:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> data = fs.readFileSync(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    console.log(data);
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 出错了</span>
}</pre>
</div>
<h3>写文件</h3>
<p>将数据写入文件是通过<code>fs.writeFile()</code>实现的:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> data = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, Node.js</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
fs.writeFile(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">output.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, data, function (err) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
      console.log(err);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ok.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    }
});</span></pre>
</div>
<p><code>writeFile()</code>的参数依次为文件名、数据和回调函数。如果传入的数据是String,默认按UTF-8编码写入文本文件,如果传入的参数是<code>Buffer</code>,则写入的是二进制文件。回调函数由于只关心成功与否,因此只需要一个<code>err</code>参数。</p>
<p>和<code>readFile()</code>类似,<code>writeFile()</code>也有一个同步方法,叫<code>writeFileSync()</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> data = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, Node.js</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
fs.writeFileSync(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">output.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, data);</pre>
</div>
<h3>stat</h3>
<p>如果我们要获取文件大小,创建时间等信息,可以使用<code>fs.stat()</code>,它返回一个<code>Stat</code>对象,能告诉我们文件或目录的详细信息:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

fs.stat(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (err, stat) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
      console.log(err);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否是文件:</span>
      console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">isFile: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> stat.isFile());
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否是目录:</span>
      console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">isDirectory: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> stat.isDirectory());
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (stat.isFile()) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 文件大小:</span>
            console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">size: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> stat.size);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建时间, Date对象:</span>
            console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">birth time: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> stat.birthtime);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 修改时间, Date对象:</span>
            console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">modified time: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> stat.mtime);
      }
    }
});</span></pre>
</div>
<p>运行结果如下:</p>
<div class="cnblogs_code">
<pre>isFile: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
isDirectory: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
size: </span><span style="color: rgba(128, 0, 128, 1)">181</span><span style="color: rgba(0, 0, 0, 1)">
birth time: Fri Dec </span><span style="color: rgba(128, 0, 128, 1)">11</span> <span style="color: rgba(128, 0, 128, 1)">2015</span> <span style="color: rgba(128, 0, 128, 1)">09</span>:<span style="color: rgba(128, 0, 128, 1)">43</span>:<span style="color: rgba(128, 0, 128, 1)">41</span> GMT+<span style="color: rgba(128, 0, 128, 1)">0800</span><span style="color: rgba(0, 0, 0, 1)"> (CST)
modified time: Fri Dec </span><span style="color: rgba(128, 0, 128, 1)">11</span> <span style="color: rgba(128, 0, 128, 1)">2015</span> <span style="color: rgba(128, 0, 128, 1)">12</span>:<span style="color: rgba(128, 0, 128, 1)">09</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> GMT+<span style="color: rgba(128, 0, 128, 1)">0800</span> (CST)</pre>
</div>
<p><code>stat()</code>也有一个对应的同步函数<code>statSync()</code>,请试着改写上述异步代码为同步代码。</p>
<h3>异步还是同步</h3>
<p>在<code>fs</code>模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?</p>
<p>由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。</p>
<p>服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行</p>
<p>&nbsp;</p>
<h1><span style="color: rgba(255, 102, 0, 1)"><strong>二、stream</strong></span></h1>
<p><code>stream</code>是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。</p>
<p>什么是流?流是一种抽象的数据结构。想象水流,当在水管中流动时,就可以从某个地方(例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)。我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(stdin)。</p>
<p>如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:标准输出流(stdout)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。</p>
<p><img src="https://img2020.cnblogs.com/blog/971787/202108/971787-20210825113055640-1764041460.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了。</p>
<p>在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:<code>data</code>事件表示流的数据已经可以读取了,<code>end</code>事件表示这个流已经到末尾了,没有数据可以读取了,<code>error</code>事件表示出错了。</p>
<p>下面是一个从文件流读取文本内容的示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开一个流:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> rs = fs.createReadStream(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

rs.on(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">data</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (chunk) {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">DATA:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
    console.log(chunk);
});

rs.on(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">end</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function () {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">END</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
});

rs.on(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">error</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, function (err) {
    console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ERROR: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> err);
});</span></pre>
</div>
<p>要注意,<code>data</code>事件可能会有多次,每次传递的<code>chunk</code>是流的一部分数据。</p>
<p>要以流的形式写入文件,只需要不断调用<span style="color: rgba(255, 0, 0, 1)"><code>write()</code></span>方法,最后以<span style="color: rgba(255, 0, 0, 1)"><code>end()</code></span>结束:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> ws1 = fs.createWriteStream(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">output1.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
ws1.write(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">使用Stream写入文本数据...\n</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
ws1.write(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">END.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
ws1.end();

</span><span style="color: rgba(0, 0, 255, 1)">var</span> ws2 = fs.createWriteStream(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">output2.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
ws2.write(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Buffer(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">使用Stream写入二进制数据...\n</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
ws2.write(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Buffer(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">END.</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
ws2.end();</span></pre>
</div>
<p>所有可以读取数据的流都继承自<code>stream.Readable</code>,所有可以写入的流都继承自<code>stream.Writable</code>。</p>
<h3>pipe</h3>
<p>就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个<code>Readable</code>流和一个<code>Writable</code>流串起来后,所有的数据自动从<code>Readable</code>流进入<code>Writable</code>流,这种操作叫<code>pipe</code>。</p>
<p>在Node.js中,<code>Readable</code>流有一个<code>pipe()</code>方法,就是用来干这件事的。</p>
<p>让我们用<code>pipe()</code>把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:</p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> rs = fs.createReadStream(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sample.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> ws = fs.createWriteStream(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">copied.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

rs.pipe(ws);</span></pre>
</div>
<p>默认情况下,当<code>Readable</code>流的数据读取完毕,<code>end</code>事件触发后,将自动关闭<code>Writable</code>流。如果我们不希望自动关闭<code>Writable</code>流,需要传入参数:</p>
<div class="cnblogs_code">
<pre>readable.pipe(writable, { end: <span style="color: rgba(0, 0, 255, 1)">false</span> });</pre>
</div>
<p>&nbsp;</p>
<h1>&nbsp;</h1>
<h1><span style="color: rgba(255, 102, 0, 1)"><strong>三、http</strong></span></h1>
<p>Node.js开发的目的就是为了用JavaScript编写Web服务器程序。因为JavaScript实际上已经统治了浏览器端的脚本,其优势就是有世界上数量最多的前端开发人员。如果已经掌握了JavaScript前端开发,再学习一下如何将JavaScript应用在后端开发,就是名副其实的全栈了。</p>
<h3>HTTP协议</h3>
<p>要理解Web服务器程序的工作原理,首先,我们要对HTTP协议有基本的了解。如果你对HTTP协议不太熟悉,先看一看HTTP协议简介。</p>
<h3>HTTP服务器</h3>
<p>要开发HTTP服务器程序,从头处理TCP连接,解析HTTP是不现实的。这些工作实际上已经由Node.js自带的<code>http</code>模块完成了。应用程序并不直接和HTTP协议打交道,而是<span style="color: rgba(255, 0, 0, 1)"><strong>操作<code>http</code>模块提供的<code>request</code>和<code>response</code></strong></span>对象。</p>
<p><code>request</code>对象封装了HTTP请求,我们调用<code>request</code>对象的属性和方法就可以拿到所有HTTP请求的信息;</p>
<p><code>response</code>对象封装了HTTP响应,我们操作<code>response</code>对象的方法,就可以把HTTP响应返回给浏览器。</p>
<p>用Node.js实现一个HTTP服务器程序非常简单。我们来实现一个最简单的Web程序<code>hello.js</code>,它对于所有请求,都返回<code>Hello world!</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入http模块:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> http = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建http server,并传入回调函数:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> server =<span style="color: rgba(0, 0, 0, 1)"> http.createServer(function (request, response) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回调函数接收request和response对象,
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获得HTTP请求的method和url:</span>
    console.log(request.method + <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将HTTP响应200写入response, 同时设置Content-Type: text/html:</span>
    response.<span style="color: rgba(255, 0, 0, 1)"><strong>writeHead</strong></span>(<span style="color: rgba(128, 0, 128, 1)">200</span>, {<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/html</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">});
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将HTTP响应的HTML内容写入response:</span>
    response.<span style="color: rgba(255, 0, 0, 1)"><strong>end</strong></span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&lt;h1&gt;Hello world!&lt;/h1&gt;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
});

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 让服务器监听8080端口:</span>
server.listen(<span style="color: rgba(128, 0, 128, 1)">8080</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Server is running at http://127.0.0.1:8080/</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>在命令提示符下运行该程序,可以看到以下输出:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ node hello.js
Server </span><span style="color: rgba(0, 0, 255, 1)">is</span> running at http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1:8080/</span></pre>
</div>
<p>不要关闭命令提示符,直接打开浏览器输入<code>http://localhost:8080</code>,即可看到服务器响应的内容:</p>
<p>&nbsp;</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/971787/202108/971787-20210825114825288-611749654.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;同时,在命令提示符窗口,可以看到程序打印的请求信息:</p>
<div class="cnblogs_code">
<pre>GET: /<span style="color: rgba(0, 0, 0, 1)">
GET: </span>/favicon.ico</pre>
</div>
<p>这就是我们编写的第一个HTTP服务器程序!</p>
<h3>文件服务器</h3>
<p>让我们继续扩展一下上面的Web程序。我们可以设定一个目录,然后让Web程序变成一个文件服务器。要实现这一点,我们只需要解析<code>request.url</code>中的路径,然后在本地找到对应的文件,把文件内容发送出去就可以了。</p>
<p>解析URL需要用到Node.js提供的<code>url</code>模块,它使用起来非常简单,通过<code>parse()</code>将一个字符串解析为一个<code>Url</code>对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> url = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(url.parse(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://user:pass@host.com:8080/path/to/file?query=string#hash</span><span style="color: rgba(128, 0, 0, 1)">'</span>));</pre>
</div>
<p>结果如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Url {
protocol: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
slashes: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
auth: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user:pass</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
host: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">host.com:8080</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
port: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">8080</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
hostname: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">host.com</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
hash: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">#hash</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
search: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">?query=string</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
query: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">query=string</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
pathname: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/path/to/file</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
path: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/path/to/file?query=string</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
href: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://user:pass@host.com:8080/path/to/file?query=string#hash</span><span style="color: rgba(128, 0, 0, 1)">'</span> }</pre>
</div>
<p>&nbsp;</p>
<p>处理本地文件目录需要使用Node.js提供的<code>path</code>模块,它可以方便地构造目录:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> path = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">path</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析当前目录:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> workDir = path.resolve(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">'</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> '/Users/michael'

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 组合完整的文件路径:当前目录+'pub'+'index.html':</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> filePath = path.join(workDir, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pub</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.html</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> '/Users/michael/pub/index.html'</span></pre>
</div>
<p>&nbsp;</p>
<p>使用<code>path</code>模块可以正确处理操作系统相关的文件路径。在Windows系统下,返回的路径类似于<code>C:\Users\michael\static\index.html</code>,这样,我们就不关心怎么拼接路径了。</p>
<p>最后,我们实现一个文件服务器<code>file_server.js</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">use strict</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)">
    fs </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    url </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    path </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">path</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    http </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从命令行参数获取root目录,默认是当前目录:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> root = path.resolve(process.argv[<span style="color: rgba(128, 0, 128, 1)">2</span>] || <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Static root dir: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> root);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建服务器:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> server =<span style="color: rgba(0, 0, 0, 1)"> http.createServer(function (request, response) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获得URL的path,类似 '/css/bootstrap.css':</span>
    <span style="color: rgba(0, 0, 255, 1)">var</span> pathname =<span style="color: rgba(0, 0, 0, 1)"> url.parse(request.url).pathname;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获得对应的本地文件路径,类似 '/srv/www/css/bootstrap.css':</span>
    <span style="color: rgba(0, 0, 255, 1)">var</span> filepath =<span style="color: rgba(0, 0, 0, 1)"> path.join(root, pathname);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取文件状态:</span>
<span style="color: rgba(0, 0, 0, 1)">    fs.stat(filepath, function (err, stats) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!err &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> stats.isFile()) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 没有出错并且文件存在:</span>
            console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送200响应:</span>
            response.writeHead(<span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将文件流导向response:</span>
<span style="color: rgba(0, 0, 0, 1)">            fs.createReadStream(filepath).pipe(response);
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 出错了或者文件不存在:</span>
            console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">404 </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送404响应:</span>
            response.writeHead(<span style="color: rgba(128, 0, 128, 1)">404</span><span style="color: rgba(0, 0, 0, 1)">);
            response.end(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">404 Not Found</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
      }
    });
});

server.listen(</span><span style="color: rgba(128, 0, 128, 1)">8080</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Server is running at http://127.0.0.1:8080/</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>&nbsp;</p>
<p>没有必要手动读取文件内容。由于<code>response</code>对象本身是一个<code>Writable Stream</code>,直接用<code>pipe()</code>方法就实现了自动读取文件内容并输出到HTTP响应。</p>
<p>在命令行运行<code>node file_server.js /path/to/dir</code>,把<code>/path/to/dir</code>改成你本地的一个有效的目录,然后在浏览器中输入<code>http://localhost:8080/index.html</code>:</p>
<p><img src="https://img2020.cnblogs.com/blog/971787/202108/971787-20210825114914772-149416571.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>只要当前目录下存在文件<code>index.html</code>,服务器就可以把文件内容发送给浏览器。观察控制台输出:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">200</span> /<span style="color: rgba(0, 0, 0, 1)">index.html
</span><span style="color: rgba(128, 0, 128, 1)">200</span> /css/<span style="color: rgba(0, 0, 0, 1)">uikit.min.css
</span><span style="color: rgba(128, 0, 128, 1)">200</span> /js/<span style="color: rgba(0, 0, 0, 1)">jquery.min.js
</span><span style="color: rgba(128, 0, 128, 1)">200</span> /fonts/fontawesome-webfont.woff2</pre>
</div>
<p>&nbsp;</p>
<p>第一个请求是浏览器请求<code>index.html</code>页面,后续请求是浏览器解析HTML后发送的其它资源请求。</p>
<h3>练习</h3>
<p>在浏览器输入<code>http://localhost:8080/</code>时,会返回404,原因是程序识别出HTTP请求的不是文件,而是目录。请修改<code>file_server.js</code>,如果遇到请求的路径是目录,则自动在目录下依次搜索<code>index.html</code>、<code>default.html</code>,如果找到了,就返回HTML文件的内容。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> fs = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> url = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> http = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> path = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">path</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">* forecast.html所在目录(C:\Users\1\Desktop\Prac\demoWeb_Temp\vue-learn\forecast) </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 0, 255, 1)">var</span> root = path.resolve(process.argv[<span style="color: rgba(128, 0, 128, 1)">2</span>] || <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> server =<span style="color: rgba(0, 0, 0, 1)"> http.createServer(function (request, response){

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathname =<span style="color: rgba(0, 0, 0, 1)"> url.parse(request.url).pathname;

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> filepath =<span style="color: rgba(0, 0, 0, 1)"> path.join(root, pathname);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> defaultPath = [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/default.html</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/forecast.html</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];

    fs.stat(filepath, function (err, stat){

      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(!err &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> stat.isFile()){

            console.log(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">200</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);

            response.writeHead(</span><span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);

            </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">* 将文件流导向response </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

            <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*没有必要手动读取文件内容。由于response对象本身是一个Writable Stream,

            * 直接用pipe()方法就实现了自动读取文件内容并输出到HTTP响应。 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">

            fs.createReadStream(filepath).pipe(response);

      }</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span>(!err &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> stat.isDirectory()){

            </span><span style="color: rgba(0, 0, 255, 1)">for</span>(<span style="color: rgba(0, 0, 255, 1)">var</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; defaultPath.length; i++<span style="color: rgba(0, 0, 0, 1)">){

                </span><span style="color: rgba(0, 0, 255, 1)">var</span> tempPath =<span style="color: rgba(0, 0, 0, 1)"> fs.existsSync(path.join(filepath, defaultPath));

                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(tempPath){

                  console.log(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">200</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);

                  response.writeHead(</span><span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);

                  fs.createReadStream(path.join(filepath, defaultPath)).pipe(response);

                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;

                }

            }

      }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{

            console.log(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">404</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> request.url);

            response.writeHead(</span><span style="color: rgba(128, 0, 128, 1)">404</span><span style="color: rgba(0, 0, 0, 1)">);

            response.end(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">404 Not Found!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

      }

    });

});

server.listen(</span><span style="color: rgba(128, 0, 128, 1)">8080</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Server is running at http://127.0.0.1:8080/</span><span style="color: rgba(128, 0, 0, 1)">'</span>);</pre>
</div>
<p>&nbsp;</p>
<h3>参考源码</h3>
<p>http服务器代码(含静态网站)</p>
<p>&nbsp;</p>
<h1><span style="color: rgba(255, 102, 0, 1)"><strong>四、crypto</strong></span></h1>
<p>crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。</p>
<h3>MD5和SHA1</h3>
<p>MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一个十六进制的字符串表示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span> crypto = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">crypto</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">const</span> hash = crypto.createHash(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">md5</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可任意多次调用update():</span>
hash.update(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, world!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
hash.update(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, nodejs!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(hash.digest(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span>)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7e1977739c748beac0c0fd14fd26a544</span></pre>
</div>
<p><code>update()</code>方法默认字符串编码为<code>UTF-8</code>,也可以传入Buffer。</p>
<p>如果要计算SHA1,只需要把<code>'md5'</code>改成<code>'sha1'</code>,就可以得到SHA1的结果<code>1f32b9c9932c02227819a4151feed43e131aca40</code>。</p>
<p>还可以使用更安全的<code>sha256</code>和<code>sha512</code>。</p>
<h3>Hmac</h3>
<p>Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span> crypto = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">crypto</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">const</span> hmac = crypto.createHmac(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sha256</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">secret-key</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

hmac.update(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, world!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
hmac.update(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, nodejs!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

console.log(hmac.digest(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span>)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 80f7e22570...</span></pre>
</div>
<p>只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。</p>
<h3>AES</h3>
<p>AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数,便于使用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span> crypto = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">crypto</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

function aesEncrypt(data, key) {
    </span><span style="color: rgba(0, 0, 255, 1)">const</span> cipher = crypto.createCipher(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">aes192</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, key);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> crypted = cipher.update(data, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    crypted </span>+= cipher.final(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> crypted;
}

function aesDecrypt(encrypted, key) {
    </span><span style="color: rgba(0, 0, 255, 1)">const</span> decipher = crypto.createDecipher(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">aes192</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, key);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> decrypted = decipher.update(encrypted, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    decrypted </span>+= decipher.final(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> decrypted;
}

</span><span style="color: rgba(0, 0, 255, 1)">var</span> data = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, this is a secret message!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> key = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Password!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> encrypted =<span style="color: rgba(0, 0, 0, 1)"> aesEncrypt(data, key);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> decrypted =<span style="color: rgba(0, 0, 0, 1)"> aesDecrypt(encrypted, key);

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Plain text: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> data);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Encrypted text: </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> encrypted);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Decrypted text: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + decrypted);</pre>
</div>
<p>&nbsp;</p>
<p>运行结果如下:</p>
<div class="cnblogs_code">
<pre>Plain text: Hello, <span style="color: rgba(0, 0, 255, 1)">this</span> <span style="color: rgba(0, 0, 255, 1)">is</span> a secret message!<span style="color: rgba(0, 0, 0, 1)">
Encrypted text: 8a944d97bdabc157a5b7a40cb180e7...
Decrypted text: Hello, </span><span style="color: rgba(0, 0, 255, 1)">this</span> <span style="color: rgba(0, 0, 255, 1)">is</span> a secret message!</pre>
</div>
<p>可以看出,加密后的字符串通过解密又得到了原始内容。</p>
<p>注意到AES有很多不同的算法,如<code>aes192</code>,<code>aes-128-ecb</code>,<code>aes-256-cbc</code>等,AES除了密钥外还可以指定IV(Initial Vector),不同的系统只要IV不同,用相同的密钥加密相同的数据得到的加密结果也是不同的。加密结果通常有两种表示方法:hex和base64,这些功能Nodejs全部都支持,但是在应用中要注意,如果加解密双方一方用Nodejs,另一方用Java、PHP等其它语言,需要仔细测试。如果无法正确解密,要确认双方是否遵循同样的AES算法,字符串密钥和IV是否相同,加密后的数据是否统一为hex或base64格式。</p>
<h3>Diffie-Hellman</h3>
<p>DH算法是一种密钥交换协议,它可以让双方在不泄漏密钥的情况下协商出一个密钥来。DH算法基于数学原理,比如小明和小红想要协商一个密钥,可以这么做:</p>
<ol>
<li>
<p>小明先选一个素数和一个底数,例如,素数<code>p=23</code>,底数<code>g=5</code>(底数可以任选),再选择一个秘密整数<code>a=6</code>,计算<code>A=g^a mod p=8</code>,然后大声告诉小红:<code>p=23,g=5,A=8</code>;</p>
</li>
<li>
<p>小红收到小明发来的<code>p</code>,<code>g</code>,<code>A</code>后,也选一个秘密整数<code>b=15</code>,然后计算<code>B=g^b mod p=19</code>,并大声告诉小明:<code>B=19</code>;</p>
</li>
<li>
<p>小明自己计算出<code>s=B^a mod p=2</code>,小红也自己计算出<code>s=A^b mod p=2</code>,因此,最终协商的密钥<code>s</code>为<code>2</code>。</p>
</li>
</ol>
<p>在这个过程中,密钥<code>2</code>并不是小明告诉小红的,也不是小红告诉小明的,而是双方协商计算出来的。第三方只能知道<code>p=23</code>,<code>g=5</code>,<code>A=8</code>,<code>B=19</code>,由于不知道双方选的秘密整数<code>a=6</code>和<code>b=15</code>,因此无法计算出密钥<code>2</code>。</p>
<p>用crypto模块实现DH算法如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span> crypto = require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">crypto</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> xiaoming's keys:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> ming = crypto.createDiffieHellman(<span style="color: rgba(128, 0, 128, 1)">512</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> ming_keys =<span style="color: rgba(0, 0, 0, 1)"> ming.generateKeys();

</span><span style="color: rgba(0, 0, 255, 1)">var</span> prime =<span style="color: rgba(0, 0, 0, 1)"> ming.getPrime();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> generator =<span style="color: rgba(0, 0, 0, 1)"> ming.getGenerator();

console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Prime: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + prime.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Generator: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + generator.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> xiaohong's keys:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> hong =<span style="color: rgba(0, 0, 0, 1)"> crypto.createDiffieHellman(prime, generator);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> hong_keys =<span style="color: rgba(0, 0, 0, 1)"> hong.generateKeys();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exchange and generate secret:</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> ming_secret =<span style="color: rgba(0, 0, 0, 1)"> ming.computeSecret(hong_keys);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> hong_secret =<span style="color: rgba(0, 0, 0, 1)"> hong.computeSecret(ming_keys);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> print secret:</span>
console.log(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Secret of Xiao Ming: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + ming_secret.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Secret of Xiao Hong: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + hong_secret.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span>));</pre>
</div>
<p>&nbsp;</p>
<p>运行后,可以得到如下输出:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ node dh.js
Prime: a8224c...deead3
Generator: </span><span style="color: rgba(128, 0, 128, 1)">02</span><span style="color: rgba(0, 0, 0, 1)">
Secret of Xiao Ming: </span><span style="color: rgba(128, 0, 128, 1)">695308</span><span style="color: rgba(0, 0, 0, 1)">...d519be
Secret of Xiao Hong: </span><span style="color: rgba(128, 0, 128, 1)">695308</span>...d519be</pre>
</div>
<p>&nbsp;</p>
<p>注意每次输出都不一样,因为素数的选择是随机的。</p>
<h3>RSA</h3>
<p>RSA算法是一种非对称加密算法,即由一个私钥和一个公钥构成的密钥对,通过私钥加密,公钥解密,或者通过公钥加密,私钥解密。其中,公钥可以公开,私钥必须保密。</p>
<p>RSA算法是1977年由Ron Rivest、Adi Shamir和Leonard Adleman共同提出的,所以以他们三人的姓氏的头字母命名。</p>
<p>当小明给小红发送信息时,可以用小明自己的私钥加密,小红用小明的公钥解密,也可以用小红的公钥加密,小红用她自己的私钥解密,这就是非对称加密。相比对称加密,非对称加密只需要每个人各自持有自己的私钥,同时公开自己的公钥,不需要像AES那样由两个人共享同一个密钥。</p>
<p>在使用Node进行RSA加密前,我们先要准备好私钥和公钥。</p>
<p>首先,在命令行执行以下命令以生成一个RSA密钥对:</p>
<div class="cnblogs_code">
<pre>openssl genrsa -aes256 -<span style="color: rgba(0, 0, 255, 1)">out</span> rsa-key.pem <span style="color: rgba(128, 0, 128, 1)">2048</span></pre>
</div>
<p>&nbsp;</p>
<p>根据提示输入密码,这个密码是用来加密RSA密钥的,加密方式指定为AES256,生成的RSA的密钥长度是2048位。执行成功后,我们获得了加密的<code>rsa-key.pem</code>文件。</p>
<p>第二步,通过上面的<code>rsa-key.pem</code>加密文件,我们可以导出原始的私钥,命令如下:</p>
<div class="cnblogs_code">
<pre>openssl rsa -<span style="color: rgba(0, 0, 255, 1)">in</span> rsa-key.pem -outform PEM -<span style="color: rgba(0, 0, 255, 1)">out</span> rsa-prv.pem</pre>
</div>
<p>输入第一步的密码,我们获得了解密后的私钥。</p>
<p>类似的,我们用下面的命令导出原始的公钥:</p>
<div class="cnblogs_code">
<pre>openssl rsa -<span style="color: rgba(0, 0, 255, 1)">in</span> rsa-key.pem -outform PEM -pubout -<span style="color: rgba(0, 0, 255, 1)">out</span> rsa-pub.pem</pre>
</div>
<p>&nbsp;</p>
<p>这样,我们就准备好了原始私钥文件<code>rsa-prv.pem</code>和原始公钥文件<code>rsa-pub.pem</code>,编码格式均为PEM。</p>
<p>下面,使用<code>crypto</code>模块提供的方法,即可实现非对称加解密。</p>
<p>首先,我们用私钥加密,公钥解密:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)">
    fs </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    crypto </span>= require(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">crypto</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从文件加载key:</span>
<span style="color: rgba(0, 0, 0, 1)">function loadKey(file) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> key实际上就是PEM编码的字符串:</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> fs.readFileSync(file, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
}

let
    prvKey </span>= loadKey(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./rsa-prv.pem</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    pubKey </span>= loadKey(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./rsa-pub.pem</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">),
    message </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hello, world!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用私钥加密:</span>
let enc_by_prv = crypto.privateEncrypt(prvKey, Buffer.<span style="color: rgba(0, 0, 255, 1)">from</span>(message, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">encrypted by private key: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + enc_by_prv.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));


let dec_by_pub </span>=<span style="color: rgba(0, 0, 0, 1)"> crypto.publicDecrypt(pubKey, enc_by_prv);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">decrypted by public key: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + dec_by_pub.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span>));</pre>
</div>
<p>&nbsp;</p>
<p>执行后,可以得到解密后的消息,与原始消息相同。</p>
<p>接下来我们使用公钥加密,私钥解密:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用公钥加密:</span>
let enc_by_pub = crypto.publicEncrypt(pubKey, Buffer.<span style="color: rgba(0, 0, 255, 1)">from</span>(message, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">encrypted by public key: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + enc_by_pub.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hex</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用私钥解密:</span>
let dec_by_prv =<span style="color: rgba(0, 0, 0, 1)"> crypto.privateDecrypt(prvKey, enc_by_pub);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">decrypted by private key: </span><span style="color: rgba(128, 0, 0, 1)">'</span> + dec_by_prv.toString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">utf8</span><span style="color: rgba(128, 0, 0, 1)">'</span>));</pre>
</div>
<p>&nbsp;</p>
<p>执行得到的解密后的消息仍与原始消息相同。</p>
<p>如果我们把<code>message</code>字符串的长度增加到很长,例如1M,这时,执行RSA加密会得到一个类似这样的错误:<code>data too large for key size</code>,这是因为RSA加密的原始信息必须小于Key的长度。那如何用RSA加密一个很长的消息呢?实际上,RSA并不适合加密大数据,而是先生成一个随机的AES密码,用AES加密原始信息,然后用RSA加密AES口令,这样,实际使用RSA时,给对方传的密文分两部分,一部分是AES加密的密文,另一部分是RSA加密的AES口令。对方用RSA先解密出AES口令,再用AES解密密文,即可获得明文。</p>
<h3>证书</h3>
<p>crypto模块也可以处理数字证书。数字证书通常用在SSL连接,也就是Web的https连接。一般情况下,https连接只需要处理服务器端的单向认证,如无特殊需求(例如自己作为Root给客户发认证证书),建议用反向代理服务器如Nginx等Web服务器去处理证书。</p>
<h3>参考源码</h3>
<p>crypto常用算法</p>

</div>
<div id="MySignature" role="contentinfo">
    声明 欢迎转载,但请保留文章原始出处:) 博客园:https://www.cnblogs.com/chenxiaomeng/
如出现转载未声明 将追究法律责任~谢谢合作<br><br>
来源:https://www.cnblogs.com/chenxiaomeng/p/15181867.html
頁: [1]
查看完整版本: Node.js(三)node.js的常用内置模块