• What is NodeJS(学习过程)


    为什么要学习node。首先是听说了这个和前后端分离有很大的关系。node作为一个基础的技术,需要提前学习。学习node,不打算直接先跟着视频去学习老师们的课程。因为想自己找到一种适合自己的学习方法。之前张龙老师的所有课程,都是根据所有技术的官方文档进行讲解的。我们为什么不可以直接在node的官网上根据官网的文档进行学习呢?当然可以。node作为我以这种学习方式的第一门技术。来记录一下此种学习方法的过程,看能否适合当前的我的学习。

    首先打开了node.js的官网。想要学习这门技术,首先要把node下载下来,然后根据官网的文档去尝试,去练习。

    打开官网就遇到了第一个问题。有两个版本,我应该下载哪个。长期稳定版本和最新版本。大娃说了一句,肯定使用最新的来学习。反正都一样,就这个吧。

    image-20200222220132093

    下载的过程,需要连接VPN,速度优点慢。我们来分析一下这个官网的文档把吧。针对于英文,看着有些吃力。还是切换成中文的来看吧。

    image-20200222220348283

    Node.js 是一个基于ChromeV8引擎的JavaScript运行环境。

    对于这句描述,没有什么太大的观念。可能是欠缺的基础知识太多了。

    简单的说 Node.js 就是运行在服务端的 JavaScript。

    Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。

    Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

    image-20200222223000200

    image-20200222223015301

    一旦你已经安装了 Node,让我们尝试构建第一个 Web 服务器。 请创建一个“app.js”文件,黏贴以下代码:

    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 3000;
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Hello World');
    });
    
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    然后使用 node app.js 运行程序,访问 http://localhost:3000,你就会看到一个消息,写着“Hello World”。

    官网的入手案例运行成功了。我先去跟着菜鸟教程看一遍简单的介绍流程。操作一下试试。

    在开始之前,我又分别的去看了看菜鸟教程里的 Node.js 的教程和廖雪峰的Node.js的教程

    https://www.liaoxuefeng.com/wiki/1022910821149312/1023025235359040

    https://www.runoob.com/nodejs/nodejs-tutorial.html

    看过之后,感觉廖雪峰的教程可以先过一遍,然后再去维护缺少的东西。最后再去看个人博客

    Ryan 。

    从本章开始,我们就正式开启JavaScript的后端开发之旅。

    Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。

    众所周知,在Netscape设计出JavaScript后的短短几个月,JavaScript事实上已经是前端开发的唯一标准。

    后来,微软通过IE击败了Netscape后一统桌面,结果几年时间,浏览器毫无进步。(2001年推出的古老的IE 6到今天仍然有人在使用!)

    没有竞争就没有发展。微软认为IE6浏览器已经非常完善,几乎没有可改进之处,然后解散了IE6开发团队!而Google却认为支持现代Web应用的新一代浏览器才刚刚起步,尤其是浏览器负责运行JavaScript的引擎性能还可提升10倍。

    先是Mozilla借助已壮烈牺牲的Netscape遗产在2002年推出了Firefox浏览器,紧接着Apple于2003年在开源的KHTML浏览器的基础上推出了WebKit内核的Safari浏览器,不过仅限于Mac平台。

    随后,Google也开始创建自家的浏览器。他们也看中了WebKit内核,于是基于WebKit内核推出了Chrome浏览器。

    Chrome浏览器是跨Windows和Mac平台的,并且,Google认为要运行现代Web应用,浏览器必须有一个性能非常强劲的JavaScript引擎,于是Google自己开发了一个高性能JavaScript引擎,名字叫V8,以BSD许可证开源。

    现代浏览器大战让微软的IE浏览器远远地落后了,因为他们解散了最有经验、战斗力最强的浏览器团队!回过头再追赶却发现,支持HTML5的WebKit已经成为手机端的标准了,IE浏览器从此与主流移动端设备绝缘。

    浏览器大战和Node有何关系?

    话说有个叫Ryan Dahl的歪果仁,他的工作是用C/C++写高性能Web服务。对于高性能,异步IO、事件驱动是基本原则,但是用C/C++写就太痛苦了。于是这位仁兄开始设想用高级语言开发Web服务。他评估了很多种高级语言,发现很多语言虽然同时提供了同步IO和异步IO,但是开发人员一旦用了同步IO,他们就再也懒得写异步IO了,所以,最终,Ryan瞄向了JavaScript。

    因为JavaScript是单线程执行,根本不能进行同步IO操作,所以,JavaScript的这一“缺陷”导致了它只能使用异步IO。

    选定了开发语言,还要有运行时引擎。这位仁兄曾考虑过自己写一个,不过明智地放弃了,因为V8就是开源的JavaScript引擎。让Google投资去优化V8,咱只负责改造一下拿来用,还不用付钱,这个买卖很划算。

    于是在2009年,Ryan正式推出了基于JavaScript语言和V8引擎的开源Web服务器项目,命名为Node.js。虽然名字很土,但是,Node第一次把JavaScript带入到后端服务器开发,加上世界上已经有无数的JavaScript开发人员,所以Node一下子就火了起来。

    在Node上运行的JavaScript相比其他后端开发语言有何优势?

    最大的优势是借助JavaScript天生的事件驱动机制加V8高性能引擎,使编写高性能Web服务轻而易举。

    其次,JavaScript语言本身是完善的函数式语言,在前端开发时,开发人员往往写得比较随意,让人感觉JavaScript就是个“玩具语言”。但是,在Node环境下,通过模块化的JavaScript代码,加上函数式编程,并且无需考虑浏览器兼容性问题,直接使用最新的ECMAScript 6标准,可以完全满足工程上的需求。

    初步认识node

    在我们创建 Node.js 第一个 "Hello, World!" 应用前,让我们先了解下 Node.js 应用是由哪几部分组成的:

    1. 引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
    2. 创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
    3. 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

    创建node.js应用步骤

    步骤一:引入required模块

    我们使用 require 指令来载入 http 模块,并将实例化的 HTTP 赋值给变量 http,实例如下:

    var http = require("http");
    

    步骤二:创建服务器

    接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。

    实例如下,在你项目的根目录下创建一个叫 server.js 的文件,并写入以下代码:

    var http = require('http');
    
    http.createServer(function (request, response) {
    
        // 发送 HTTP 头部 
        // HTTP 状态值: 200 : OK
        // 内容类型: text/plain
        response.writeHead(200, {'Content-Type': 'text/plain'});
    
        // 发送响应数据 "Hello World"
        response.end('Hello World
    ');
    }).listen(8888);
    
    // 终端打印如下信息
    console.log('Server running at http://127.0.0.1:8888/');
    

    以上代码我们完成了一个可以工作的 HTTP 服务器。

    使用 node 命令执行以上的代码:

    node server.js
    Server running at http://127.0.0.1:8888/
    

    分析Node.js 的 HTTP 服务器:

    • 第一行请求(require)Node.js 自带的 http 模块,并且把它赋值给 http 变量。
    • 接下来我们调用 http 模块提供的函数: createServer 。这个函数会返回 一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数, 指定这个 HTTP 服务器监听的端口号。

    REPL(交互式解释器)

    通过命令行直接输入命令 node . 便可打开node的交互式解释器。

    Node 自带了交互式解释器,可以执行以下任务:

    • 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
    • 执行 - 执行输入的数据结构
    • 打印 - 输出结果
    • 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

    Node 的交互式解释器可以很好的调试 Javascript 代码。


    npm

    在开始之前,所有的教程中都提到了npm,那么看一下npm是什么东西?

    npm是Node.js的包管理工具(package manager)

    为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到npm官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。相当于maven和gradle

    image-20200223091128504

    搭建了VSCode 的开发环境

    image-20200223095819094

    模块

    在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。

    为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Node环境中,一个.js文件就称之为一个模块(module)。

    使用模块有什么好处?

    最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Node内置的模块和来自第三方的模块。

    使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。

    模块基础概念

    这里举例: B页面引用A页面定义的函数。
    
    'use strict';
    function greet(name){
      console.log('我的名字是:“'+name+'"');
    }
    // 函数greet()是我们在hello模块中定义的,你可能注意到最后一行是一个奇怪的赋值语句,它的意思是,把函数greet作为模块的输出暴露出去,这样其他模块就可以使用greet函数了。
    //问题是其他模块怎么使用hello模块的这个greet函数呢?我们再编写一个main.js文件,调用hello模块的greet函数:
    module.exports = greet;
    == == == == == == == == == == == == == ==
    'use strict';
    // 引入hello模块:
    var greet = require('./hello');
    var s = '二娃';
    greet(s); // Hello, Michael!
    
    
    

    CommonJS规范

    这种模块加载机制被称为CommonJS规范。在这个规范下,每个.js文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,hello.jsmain.js都申明了全局变量var s = 'xxx',但互不影响。

    一个模块想要对外暴露变量(函数也是变量),可以用module.exports = variable;,一个模块要引用其他模块暴露的变量,用var ref = require('module_name');就拿到了引用模块的变量。

    Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

    深入了解模块原理

    如果你想详细地了解CommonJS的模块实现原理,请继续往下阅读。

    当我们编写JavaScript代码时,我们可以申明全局变量:

    var s = 'global';
    

    在浏览器中,大量使用全局变量可不好。如果你在a.js中使用了全局变量s,那么,在b.js中也使用全局变量s,将造成冲突,b.js中对s赋值会改变a.js的运行逻辑。

    也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。

    那Node.js是如何实现这一点的?

    其实要实现“模块”这个功能,并不需要语法层面的支持。Node.js也并不会增加任何JavaScript语法。实现“模块”功能的奥妙就在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。

    请注意我们编写的hello.js代码是这样的:

    var s = 'Hello';
    var name = 'world';
    
    console.log(s + ' ' + name + '!');
    

    Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:

    (function () {
        // 读取的hello.js代码:
        var s = 'Hello';
        var name = 'world';
    
        console.log(s + ' ' + name + '!');
        // hello.js代码结束
    })();
    

    这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。

    所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

    但是,模块的输出module.exports怎么实现?

    这个也很容易实现,Node可以先准备一个对象module

    // 准备module对象:
    var module = {
        id: 'hello',
        exports: {}
    };
    var load = function (module) {
        // 读取的hello.js代码:
        function greet(name) {
            console.log('Hello, ' + name + '!');
        }
        
        module.exports = greet;
        // hello.js代码结束
        return module.exports;
    };
    var exported = load(module);
    // 保存module:
    save(module, exported);
    

    可见,变量module是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js中可以直接使用变量module原因就在于它实际上是函数的一个参数:

    module.exports = greet;
    

    通过把参数module传递给load()函数,hello.js就顺利地把一个变量传递给了Node执行环境,Node会把module变量保存到某个地方。

    由于Node保存了所有导入的module,当我们用require()获取module时,Node找到对应的module,把这个moduleexports变量返回,这样,另一个模块就顺利拿到了模块的输出:

    var greet = require('./hello');
    

    以上是Node实现JavaScript模块的一个简单的原理介绍。


    module.exports vs exports 结论

    如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;

    如果要输出一个函数或数组,必须直接对module.exports对象赋值。

    所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:

    module.exports = {
        foo: function () { return 'foo'; }
    };
    

    或者:

    module.exports = function () { return 'foo'; };
    

    最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。

    练习: 引入一个模块的两个方法。
    'use strict';
    function greet(name){
      console.log('我的名字是:“'+name+'"');
    }
    function hello(name){
        console.log('hello world');
      }
    module.exports = {
        greet:greet,
        hello:hello
    }
    == == == == == == == == == == == == == == 
    
    'use strict';
    // 引入hello模块:
    var greet = require('./hello').greet;
    var hello = require('./hello').hello;
    
    var s = '二娃';
    greet(s); // Hello, Michael!
    hello();
    

    基本模块

    因为Node.js是运行在服务区端的JavaScript环境,服务器程序和浏览器程序相比,最大的特点是没有浏览器的安全限制了,而且,服务器程序必须能接收网络请求,读写文件,处理二进制内容,所以,Node.js内置的常用模块就是为了实现基本的服务器功能。这些模块在浏览器环境中是无法被执行的,因为它们的底层代码是用C/C++在Node.js运行环境中实现的。

    global

    在前面的JavaScript课程中,我们已经知道,JavaScript有且仅有一个全局对象,在浏览器中,叫window对象。而在Node.js环境中,也有唯一的全局对象,但不叫window,而叫global,这个对象的属性和方法也和浏览器环境的window不同。进入Node.js交互环境,可以直接输入:

    image-20200223110840456

    process

    process也是Node.js提供的一个对象,它代表当前Node.js进程。通过process对象可以拿到许多有用信息:

    image-20200223110911558

    JavaScript程序是由事件驱动执行的单线程模型,Node.js也不例外。Node.js不断执行响应事件的JavaScript函数,直到没有任何响应事件的函数可以执行时,Node.js就退出了。

    如果我们想要在下一次事件响应中执行代码,可以调用process.nextTick()

    // test.js
    
    // process.nextTick()将在下一轮事件循环中调用:
    process.nextTick(function () {
        console.log('nextTick callback!');
    });
    console.log('nextTick was set!');
    

    用Node执行上面的代码node test.js,你会看到,打印输出是:

    nextTick was set!
    nextTick callback!
    

    这说明传入process.nextTick()的函数不是立刻执行,而是要等到下一次事件循环。

    Node.js进程本身的事件就由process对象来处理。如果我们响应exit事件,就可以在程序即将退出时执行某个回调函数:

    // 程序即将退出时的回调函数:
    process.on('exit', function (code) {
        console.log('about to exit with code: ' + code);
    });
    
    判断JavaScript执行环境

    有很多JavaScript代码既能在浏览器中执行,也能在Node环境执行,但有些时候,程序本身需要判断自己到底是在什么环境下执行的,常用的方式就是根据浏览器和Node环境提供的全局变量名称来判断:

    if (typeof(window) === 'undefined') {
        console.log('node.js');
    } else {
        console.log('browser');
    }
    

    后面,我们将介绍Node.js的常用内置模块。

    fs

    Node.js内置的fs模块就是文件系统模块,负责读写文件。

    和所有其它JavaScript模块不同的是,fs模块同时提供了异步和同步的方法。

    回顾一下什么是异步方法。因为JavaScript的单线程模型,执行IO操作时,JavaScript代码无需等待,而是传入回调函数后,继续执行后续JavaScript代码。

    而同步的IO操作则需要等待函数返回。同步操作的好处是代码简单,缺点是程序将等待IO操作,在等待时间内,无法响应其它任何事件。而异步读取不用等待IO操作,但代码较麻烦。

    异步读取文件举例
    'use strict';
    var fs = require('fs');
    
    //异步读取文本类文件
    // 请注意,hello.js文件必须在当前目录下,且文件编码为utf-8。
    // 异步读取时,传入的回调函数接收两个参数,当正常读取时,err参数为null,data参数为读取到的String。当读取发生错误时,err参数代表一个错误对象,data为undefined。
    // 这也是Node.js标准的回调函数:第一个参数代表错误信息,第二个参数代表结果。后面我们还会经常编写这种回调函数。
    fs.readFile('hello.js', 'utf-8', function (err, data) {
        if (err) {
            console.log(err);
        } else {
            console.log(data);
        }
    });
    
    console.log("- - - - - -- - ")
    //异步读取非文本类文件举例:
    // 下面的例子演示了如何读取一个图片文件:
    fs.readFile('testImage.jpg', function (err, data) {
        if (err) {
            console.log(err);
        } else {
            console.log(data);
            console.log(data.length + ' bytes');
        }
    });
    
    '当读取二进制文件时,不传入文件编码时,回调函数的data参数将返回一个Buffer对象。在Node.js中,Buffer对象就是一个包含零个或任意个字节的数组(注意和Array不同)。'
    Buffer对象可以和String作转换,例如,把一个Buffer对象转换成String:
    // Buffer -> String
    var text = data.toString('utf-8');
    console.log(text);
    或者把一个String转换成Buffer:
    // String -> Buffer
    var buf = Buffer.from(text, 'utf-8');
    console.log(buf);
    
    同步读文件举例

    除了标准的异步读取模式外,fs也提供相应的同步读取函数。同步读取的函数和异步函数相比,多了一个Sync后缀,并且不接收回调函数,函数直接返回结果。

    'use strict';
    var fs = require('fs');
    //同步读取文件举例
    var data = fs.readFileSync('hello.js', 'utf-8');
    console.log(data);
    
    异步同步写文件

    将数据写入文件是通过fs.writeFile()实现的。

    writeFile()的参数依次为文件名、数据和回调函数。如果传入的数据是String,默认按UTF-8编码写入文本文件,如果传入的参数是Buffer,则写入的是二进制文件。回调函数由于只关心成功与否,因此只需要一个err参数。

    // 同步写,生成的文件就在当前的目录。
    'use strict';
    var fs = require('fs');
    var data = 'Hello, Node.js';
    fs.writeFile('output.txt', data, function (err) {
        if (err) {
            console.log(err);
        } else {
            console.log('ok.');
        }
    });
    
    //异步写:  生成的文件就在当前的目录。
    'use strict';
    var fs = require('fs');
    var data = 'Hello, Node.js';
    fs.writeFileSync('output.txt', data);
    
    stat

    如果我们要获取文件大小,创建时间等信息,可以使用fs.stat(),它返回一个Stat对象,能告诉我们文件或目录的详细信息:

    'use strict';
    
    var fs = require('fs');
    
    fs.stat('sample.txt', function (err, stat) {
        if (err) {
            console.log(err);
        } else {
            // 是否是文件:
            console.log('isFile: ' + stat.isFile());
            // 是否是目录:
            console.log('isDirectory: ' + stat.isDirectory());
            if (stat.isFile()) {
                // 文件大小:
                console.log('size: ' + stat.size);
                // 创建时间, Date对象:
                console.log('birth time: ' + stat.birthtime);
                // 修改时间, Date对象:
                console.log('modified time: ' + stat.mtime);
            }
        }
    });
    

    运行结果如下:

    isFile: true
    isDirectory: false
    size: 181
    birth time: Fri Dec 11 2015 09:43:41 GMT+0800 (CST)
    modified time: Fri Dec 11 2015 12:09:00 GMT+0800 (CST)
    

    stat()也有一个对应的同步函数statSync(),请试着改写上述异步代码为同步代码。

    异步还是同步

    fs模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?

    由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。

    服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。

    Stream

    stream是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。

    在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data事件表示流的数据已经可以读取了,end事件表示这个流已经到末尾了,没有数据可以读取了,error事件表示出错了。

    所有可以读取数据的流都继承自stream.Readable,所有可以写入的流都继承自stream.Writable

    //下面是一个从文件流读取文本内容的示例:
    'use strict'
    var fs = require('fs');
    var rs = fs.createReadStream('output.txt','utf-8');
    
    rs.on('data', function (chunk) {
        console.log('DATA:')
        console.log(chunk);
    });
    
    rs.on('end', function () {
        console.log('END');
    });
    
    rs.on('error', function (err) {
        console.log('ERROR: ' + err);
    });
    
    要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据。
    
    // 要以流的形式写入文件,只需要不断调用write()方法,最后以end()结束:
    'use strict';
    var fs = require('fs');
    var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
    ws1.write('使用Stream写入文本数据...
    ');
    ws1.write('END.');
    ws1.end();
    
    var ws2 = fs.createWriteStream('output2.txt');
    ws2.write(new Buffer('使用Stream写入二进制数据...
    ', 'utf-8'));
    ws2.write(new Buffer('END.', 'utf-8'));
    ws2.end();
    
    pipe

    就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个Readable流和一个Writable流串起来后,所有的数据自动从Readable流进入Writable流,这种操作叫pipe

    在Node.js中,Readable流有一个pipe()方法,就是用来干这件事的。

    让我们用pipe()把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:

    'use strict';
    var fs = require('fs');
    var rs = fs.createReadStream('sample.txt');
    var ws = fs.createWriteStream('copied.txt');
    rs.pipe(ws);
    // 结果是把读的文件,给复制了一份到 copied.txt文件当中
    
    默认情况下,当Readable流的数据读取完毕,end事件触发后,将自动关闭Writable流。如果我们不希望自动关闭Writable流,需要传入参数:
    readable.pipe(writable, { end: false });
    

    http

    要开发HTTP服务器程序,从头处理TCP连接,解析HTTP是不现实的。这些工作实际上已经由Node.js自带的http模块完成了。应用程序并不直接和HTTP协议打交道,而是操作http模块提供的requestresponse对象。

    request对象封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息;

    response对象封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器。

    用Node.js实现一个HTTP服务器程序非常简单。我们来实现一个最简单的Web程序hello.js,它对于所有请求,都返回Hello world!

    'use strict';
    //导入http模块
    var http = require('http');
    //创建服务,并传入回调函数
    var server = http.createServer((request,response) => {
        // 回调函数接收request和response对象,
        // 获得HTTP请求的method和url:
        console.log(request.method + ': ' + request.url);
        // 将HTTP响应200写入response, 同时设置Content-Type: text/html:
        response.writeHead(200, {'Content-Type': 'text/html'});
        // 将HTTP响应的HTML内容写入response:
        response.end('<h1>Hello world!</h1>');
    })
    
    // 让服务器监听8080端口:
    server.listen(8080);
    console.log('Server is running at http://127.0.0.1:8080/');
    

    同时,在命令提示符窗口,可以看到程序打印的请求信息:

    GET: /
    GET: /favicon.ico
    
    文件服务器

    让我们继续扩展一下上面的Web程序。我们可以设定一个目录,然后让Web程序变成一个文件服务器。要实现这一点,我们只需要解析request.url中的路径,然后在本地找到对应的文件,把文件内容发送出去就可以了。

    解析URL需要用到Node.js提供的url模块,它使用起来非常简单,通过parse()将一个字符串解析为一个Url对象:

    // 解析URL
    var url = require('url');
    console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash'));
    
    输出结果: 
    Url {protocol: "http:", slashes: true, auth: "user:pass", host: "host.com:8080", port: "8080", …}
    
    var path = require('path');
    // 解析当前目录:
    var workDir = path.resolve('.'); // '/Users/michael'
    // 组合完整的文件路径:当前目录+'pub'+'index.html':
    var filePath = path.join(workDir, 'hello', 'index.html');
    console.log(filePath);  //打印出当前的目录  /Users/erwa/Desktop/nodejs/hello/index.html
    

    实现一个文件服务器js

    'use strict'
    
    //导入相应的模块、
    var
        fs = require('fs'),
        url = require('url'),
        http = require('http'),
        path = require('path');
    
    var root = path.resolve(process.argv[2] || '.');
    console.log(root)
    
    //创建http server,并传入回调函数。
    var server = http.createServer((request, response) => {
        //获取URL的paht。如:/css/bootstrap.css:
        var pathname = url.parse(request.url).pathname;
        //获取对应的本地文件路径,
        var filepath =  path.join(root,pathname)
        //获取文件状态。
        fs.stat(filepath,(err,stats)=>{
            if(!err && stats.isFile()){
                //没有出错,且文件存在: 
                console.log('200'+ request.url)
                //发送200相应
                response.writeHead(200)
                //将文件流导向response
                fs.createReadStream(filepath).pipe(response);
            }else{
                 // 出错了或者文件不存在:
                 console.log('404 ' + request.url);
                 // 发送404响应:
                 response.writeHead(404);
                 response.end('404 Not Found');
            }
        });
    });
    
    //让服务器监听8080端口:
    server.listen(8080)
    console.log('server is running at http://127.0.0.1:8080')
    
    //启动服务。尝试调用了几次本地的文件。结果如下图所示。
    

    image-20200223203209617

    crypto

    crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。

    MD5和SHA1

    MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一个十六进制的字符串表示:

    update()方法默认字符串编码为UTF-8,也可以传入Buffer。

    如果要计算SHA1,只需要把'md5'改成'sha1',就可以得到SHA1的结果1f32b9c9932c02227819a4151feed43e131aca40

    还可以使用更安全的sha256sha512

    'use strict'
    
    const crypto = require('crypto');
    
    const hash = crypto.createHash('md5');
    
    //可任意调用多次update();
    hash.update('hello,world');
    hash.update('hello,world!');
    
    console.log(hash.digest('hex'));
    
    输出结果为: 
    77a0a3680ef04e4737f4b8edadd3590c
    
    Hmac

    Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥:

    只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。

    const crypto = require('crypto');
    // hmac    第二个参数,又加了一个密匙,增强版的哈希算法。
    const hmac = crypto.createHmac('sha256','sec112a'); 
    hmac.update('hello,world');
    console.log(hmac.digest('hex'));
    
    AES

    AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数,便于使用:

    const crypto = require('crypto');
    
    function aesEncrypt(data, key) {
     const cipher = crypto.createCipher('aes192', key);
     var crypted = cipher.update(data, 'utf8', 'hex');
     crypted += cipher.final('hex');
     return crypted;
    }
    
    function aesDecrypt(encrypted, key) {
     const decipher = crypto.createDecipher('aes192', key);
     var decrypted = decipher.update(encrypted, 'hex', 'utf8');
     decrypted += decipher.final('utf8');
     return decrypted;
    }
    
    var data = 'Hello, this is a secret message!';
    var key = 'Password!';
    var encrypted = aesEncrypt(data, key);
    var decrypted = aesDecrypt(encrypted, key);
    
    console.log('Plain text: ' + data);
    console.log('Encrypted text: ' + encrypted);
    console.log('Decrypted text: ' + decrypted);
    
    >>> 输出结果为: 
    Plain text: Hello, this is a secret message!
    Encrypted text: 8a944d97bdabc157a5b7a40cb180e7...
    Decrypted text: Hello, this is a secret message!
    

    可以看出,加密后的字符串通过解密又得到了原始内容。

    注意到AES有很多不同的算法,如aes192aes-128-ecbaes-256-cbc等,AES除了密钥外还可以指定IV(Initial Vector),不同的系统只要IV不同,用相同的密钥加密相同的数据得到的加密结果也是不同的。加密结果通常有两种表示方法:hex和base64,这些功能Nodejs全部都支持,但是在应用中要注意,如果加解密双方一方用Nodejs,另一方用Java、PHP等其它语言,需要仔细测试。如果无法正确解密,要确认双方是否遵循同样的AES算法,字符串密钥和IV是否相同,加密后的数据是否统一为hex或base64格式。

    Diffie-Hellman

    DH算法是一种密钥交换协议,它可以让双方在不泄漏密钥的情况下协商出一个密钥来。DH算法基于数学原理,比如小明和小红想要协商一个密钥,可以这么做:

    1. 小明先选一个素数和一个底数,例如,素数p=23,底数g=5(底数可以任选),再选择一个秘密整数a=6,计算A=g^a mod p=8,然后大声告诉小红:p=23,g=5,A=8
    2. 小红收到小明发来的pgA后,也选一个秘密整数b=15,然后计算B=g^b mod p=19,并大声告诉小明:B=19
    3. 小明自己计算出s=B^a mod p=2,小红也自己计算出s=A^b mod p=2,因此,最终协商的密钥s2

    在这个过程中,密钥2并不是小明告诉小红的,也不是小红告诉小明的,而是双方协商计算出来的。第三方只能知道p=23g=5A=8B=19,由于不知道双方选的秘密整数a=6b=15,因此无法计算出密钥2

    用crypto模块实现DH算法如下:

    const crypto = require('crypto');
    
    // xiaoming's keys:
    var ming = crypto.createDiffieHellman(512);
    var ming_keys = ming.generateKeys();
    
    var prime = ming.getPrime();
    var generator = ming.getGenerator();
    
    console.log('Prime: ' + prime.toString('hex'));
    console.log('Generator: ' + generator.toString('hex'));
    
    // xiaohong's keys:
    var hong = crypto.createDiffieHellman(prime, generator);
    var hong_keys = hong.generateKeys();
    
    // exchange and generate secret:
    var ming_secret = ming.computeSecret(hong_keys);
    var hong_secret = hong.computeSecret(ming_keys);
    
    // print secret:
    console.log('Secret of Xiao Ming: ' + ming_secret.toString('hex'));
    console.log('Secret of Xiao Hong: ' + hong_secret.toString('hex'));
    
    >>> 输出结果为 : 
    Prime: a8224c...deead3
    Generator: 02
    Secret of Xiao Ming: 695308...d519be
    Secret of Xiao Hong: 695308...d519be
    
    注意每次输出都不一样,因为素数的选择是随机的。
    
    RSA

    RSA算法是一种非对称加密算法,即由一个私钥和一个公钥构成的密钥对,通过私钥加密,公钥解密,或者通过公钥加密,私钥解密。其中,公钥可以公开,私钥必须保密。

    RSA算法是1977年由Ron Rivest、Adi Shamir和Leonard Adleman共同提出的,所以以他们三人的姓氏的头字母命名。

    当小明给小红发送信息时,可以用小明自己的私钥加密,小红用小明的公钥解密,也可以用小红的公钥加密,小红用她自己的私钥解密,这就是非对称加密。相比对称加密,非对称加密只需要每个人各自持有自己的私钥,同时公开自己的公钥,不需要像AES那样由两个人共享同一个密钥。

    在使用Node进行RSA加密前,我们先要准备好私钥和公钥。

    如果我们把message字符串的长度增加到很长,例如1M,这时,执行RSA加密会得到一个类似这样的错误:data too large for key size,这是因为RSA加密的原始信息必须小于Key的长度。那如何用RSA加密一个很长的消息呢?实际上,RSA并不适合加密大数据,而是先生成一个随机的AES密码,用AES加密原始信息,然后用RSA加密AES口令,这样,实际使用RSA时,给对方传的密文分两部分,一部分是AES加密的密文,另一部分是RSA加密的AES口令。对方用RSA先解密出AES口令,再用AES解密密文,即可获得明文。

    证书

    crypto模块也可以处理数字证书。数字证书通常用在SSL连接,也就是Web的https连接。一般情况下,https连接只需要处理服务器端的单向认证,如无特殊需求(例如自己作为Root给客户发认证证书),建议用反向代理服务器如Nginx等Web服务器去处理证书。

    Web开发

    最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件。后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种Client/Server模式简称CS架构。

    随着互联网的兴起,人们发现,CS架构不适合Web,最大的原因是Web应用程序的修改和升级非常迅速,而CS架构需要每个客户端逐个升级桌面App,因此,Browser/Server模式开始流行,简称BS架构。

    在BS架构下,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web页面,并把Web页面展示给用户即可。

    当然,Web页面也具有极强的交互性。由于Web页面是用HTML编写的,而HTML具备超强的表现力,并且,服务器端升级后,客户端无需任何部署就可以使用到新的版本,因此,BS架构迅速流行起来。

    今天,除了重量级的软件如Office,Photoshop等,大部分软件都以Web形式提供。比如,新浪提供的新闻、博客、微博等服务,均是Web应用。

    Web应用开发可以说是目前软件开发中最重要的部分。Web开发也经历了好几个阶段:

    静态Web页面:由文本编辑器直接编辑并生成静态的HTML页面,如果要修改Web页面的内容,就需要再次编辑HTML源文件,早期的互联网Web页面就是静态的;

    CGI:由于静态Web页面无法与用户交互,比如用户填写了一个注册表单,静态Web页面就无法处理。要处理用户发送的动态数据,出现了Common Gateway Interface,简称CGI,用C/C++编写。

    ASP/JSP/PHP:由于Web应用特点是修改频繁,用C/C++这样的低级语言非常不适合Web开发,而脚本语言由于开发效率高,与HTML结合紧密,因此,迅速取代了CGI模式。ASP是微软推出的用VBScript脚本编程的Web开发技术,而JSP用Java来编写脚本,PHP本身则是开源的脚本语言。

    MVC:为了解决直接用脚本语言嵌入HTML导致的可维护性差的问题,Web应用也引入了Model-View-Controller的模式,来简化Web开发。ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。

    目前,Web开发技术仍在快速发展中,异步开发、新的MVVM前端技术层出不穷。

    由于Node.js把JavaScript引入了服务器端,因此,原来必须使用PHP/Java/C#/Python/Ruby等其他语言来开发服务器端程序,现在可以使用Node.js开发了!

    用Node.js开发Web服务器端,有几个显著的优势:

    一是后端语言也是JavaScript,以前掌握了前端JavaScript的开发人员,现在可以同时编写后端代码;

    二是前后端统一使用JavaScript,就没有切换语言的障碍了;

    三是速度快,非常快!这得益于Node.js天生是异步的。

    在Node.js诞生后的短短几年里,出现了无数种Web框架、ORM框架、模版引擎、测试框架、自动化构建工具,数量之多,即使是JavaScript老司机,也不免眼花缭乱。

    常见的Web框架包括:ExpressSails.jskoaMeteorDerbyJSTotal.jsrestify……

    ORM框架比Web框架要少一些:SequelizeORM2Bookshelf.jsObjection.js……

    模版引擎PK:JadeEJSSwigNunjucksdoT.js……

    测试框架包括:MochaExpressoUnit.jsKarma……

    构建工具有:GruntGulpWebpack……

    目前,在npm上已发布的开源Node.js模块数量超过了30万个。。

    About Node.js®

    As an asynchronous event-driven JavaScript runtime, Node.js is designed to build scalable network applications. In the following "hello world" example, many connections can be handled concurrently. Upon each connection, the callback is fired, but if there is no work to be done, Node.js will sleep.

    作为异步驱动的 JavaScript 运行时,Node.js 被设计成可升级的网络应用。在下面的“Hello World”示例中,许多连接可以并行处理。每一个连接都会触发一个回调,但是如果没有可做的事情,Node.js 就进入睡眠状态。

    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 3000;
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Hello World');
    });
    
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    This is in contrast to today's more common concurrency model, in which OS threads are employed. Thread-based networking is relatively inefficient and very difficult to use. Furthermore, users of Node.js are free from worries of dead-locking the process, since there are no locks. Almost no function in Node.js directly performs I/O, so the process never blocks. Because nothing blocks, scalable systems are very reasonable to develop in Node.js.

    这与今天使用 OS 线程的更常见并发模型形成了对比。基于线程的网络效率相对低下,使用起来非常困难。此外,Node.js 的用户不必担心死锁过程,因为没有锁。Node 中几乎没有函数直接执行 I/O 操作,因此进程从不阻塞。由于没有任何阻塞,可伸缩系统在 Node 中开发是非常合理的。

    If some of this language is unfamiliar, there is a full article on Blocking vs. Non-Blocking.


    Node.js is similar in design to, and influenced by, systems like Ruby's Event Machine and Python's Twisted. Node.js takes the event model a bit further. It presents an event loop as a runtime construct instead of as a library. In other systems, there is always a blocking call to start the event-loop. Typically, behavior is defined through callbacks at the beginning of a script, and at the end a server is started through a blocking call like EventMachine::run(). In Node.js, there is no such start-the-event-loop call. Node.js simply enters the event loop after executing the input script. Node.js exits the event loop when there are no more callbacks to perform. This behavior is like browser JavaScript — the event loop is hidden from the user.

    HTTP is a first-class citizen in Node.js, designed with streaming and low latency in mind. This makes Node.js well suited for the foundation of a web library or framework.

    Node.js being designed without threads doesn't mean you can't take advantage of multiple cores in your environment. Child processes can be spawned by using our child_process.fork() API, and are designed to be easy to communicate with. Built upon that same interface is the cluster module, which allows you to share sockets between processes to enable load balancing over your cores.

    Node.js 在设计上类似于 Ruby 的事件机或 Python 的 Twisted之类的系统。Node.js 更深入地考虑事件模型。它呈现一个事件轮询作为运行时构造而不是库。在其它系统中,总是有一个阻止调用来启动事件循环。

    通常 Node.js 的行为是通过在脚本开头的回调定义的,在结束时通过阻塞调用(如 EventMachine::run() )启动服务器。在 Node.js 中没有这样的启动-事件循环调用。Node.js 在执行输入脚本后只需输入事件循环即可。 当没有更多要执行的回调时,Node.js 退出事件循环。此行为类似于浏览器中的 JavaScript ——事件循环总是对用户不可见的。

    HTTP 是 Node.js 中的一等公民。它设计的是流式和低延迟。这使得 Node.js 非常适合于 web 库或框架的基础。

    仅仅因为 Node.js 是在没有线程的情况下设计的,这并不意味着您无法利用环境中的多个内核。子进程可以通过使用我们的 child_process.fork() API 来生成,并且被设计为易于沟通。建立在同一接口上的是 cluster 模块,它允许您在进程之间共享套接字,以便在核心上启用负载平衡。

    总结

    此行学习结束,目的是为了了解node,为了解学习vue打一个基础。以后工作及学习中具体用到详细API的时候去官网查询如何使用即可。

    无论你是谁,学习任何技术的时候,我建议你从官网上学习。这将是一个好的习惯

  • 相关阅读:
    oracle闪回某个时间段的数据
    查询某个表某个字段重复记录急重复数量
    调用腾讯QQ启动
    MongoDB笔记(二):MongoDB下Shell的基本操作
    MongoDB笔记(一):MongoDB介绍及Windows下安装
    freemarker相关
    oracle获取时间毫秒数
    如何简单地理解Python中的if __name__ == '__main__'
    python套接字基本使用
    Mysql表的约束设计和关联关系设计
  • 原文地址:https://www.cnblogs.com/wobushitiegan/p/12359919.html
Copyright © 2020-2023  润新知