参考文献:https://www.tutorialspoint.com/nodejs/nodejs_quick_guide.htm
introduction
Node.js是一个基于V8引擎的服务端平台。简单来说
Node.js = Runtime Environment + JavaScript Library
Node.js应用程序由以下三个重要组件组成:
- 导入所需模块
- 创建服务器
- 读取请求和响应请求
创建Node.js应用程序
step 1. 导入所需模块
我们使用require命令加载所需模块并返回模块实例到局部变量中,如下所示:
var http = require("http"); //导入Node.js Build-in模块
var square = require("./hello.js"); //导入自定义js文件,所有hello.js文件内容都会被执行。
附录:Node.js Build-in Modules and functions
参考:https://www.w3schools.com/nodejs/ref_modules.asp
Module | Description |
---|---|
assert | 提供一系列的断言测试功能 |
buffer | 处理二进制数据 |
child_process | 运行子进程 |
cluster | 将当前进程克隆成多个同时运行的子进程 |
crypto | OpenSSL加密函数 |
dgram | 提供UDP数据报套接字的实现 |
dns | 域名查询服务 |
events | 处理事件 |
fs | 文件系统模块 |
http | 将Node.Js设为http服务器 |
https | 将Node.js设为https服务器 |
path | 处理文件路径 |
net | 创建服务器和客户端 |
os | 提供操作系统的信息 |
querystring | 将请求参数字符串转为一个object对象 |
readline | 将数据流中数据以行的形式读出 |
stream | 处理数据流 |
string_decoder | 将Buffer对象解码为字符串 |
timers | 在指定时间后执行一个命令 |
tls | 提供了实现传输层(TLS Transport Layer Security)和安全套接层(SSL Secure Socket Layer)的方法 |
url | 实现url对象和字符串之间的转换 |
util | 一些实用的函数 |
zlib | 用于压缩和解压文件 |
vm | 在虚拟机中编译JavaScript代码 |
step 2. 创建服务器
通过http.createServer()创建http服务器,然后通过listen将服务器绑定到8081端口。
var http = require('http');
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World!\n');
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
step 3. 测试请求和响应
将第二步代码保存为main.js,然后进入该文件目录,运行node命令:
node main.js
验证输出为:
Server running at http://127.0.0.1:8081/
打开浏览器输入http://127.0.0.1:8081/
NPM
Node包管理器(Node Package Manager)主要有两个作用:
- 在线查询node.js包的仓库(search.nodejs.org)
- 提供安装node.js包的命令行工具,以及包的版本管理和依赖管理
npm随Node.js绑定安装。如果需要把npm升级到最新版本,通过如下命令进行:
npm install npm -g
其中-g表示global全局。
安装任何Node.js模块的命令如下:
npm install <Module Name>
比如安装大名鼎鼎的web框架模块express
npm install express
安装好后,express就会出现在工程目录下的node_modules目录中。
查看安装信息:
npm list
现在就可以通过require语句导入express模块:
var express = require('express');
卸载一个模块:
npm uninstall express
更新一个模块:
npm update express
回调的概念
回调函数是指当满足某种条件后用函数指针的形式调用函数。在Node.js中,所有API都支持回调的方式编写。当异步执行API函数时,不会等待API函数执行完,而是继续下个语句,当API函数执行完后再以回调的方式返回函数执行结果。比如fs模块下异步读取文件的API函数readFile的阻塞形式为readFileSync。
现创建一个名为input.txt的文本文件,内容如下:
Tutorials Point is giving self learning content
to teach the world in simple and easy way!!!!!
阻塞代码示例:
使用以下代码创建一个名为main.js的js文件
var fs = require('fs');
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log("Program Ended");
运行结果:
Tutorials Point is giving self learning content
to teach the world in simple and easy way!!!!!
Program Ended
非阻塞代码示例:
var fs = require('fs');
fs.readFile('input.txt', function(err, data){
if(err)
return console.error(err);
console.log(data.toString());
});
console.log("Program Ended");
运行结果:
Program Ended
Tutorials Point is giving self learning content
to teach the world in simple and easy way!!!!!
结论:阻塞程序按顺序执行,异步程序按回调的方式执行
在Node.js中,任何异步函数都接受回调函数作为最后一个参数,而回调函数接受错误作为第一个参数。
事件
事件和回调不同之处在于,回调函数是在异步函数执行完成之后执行的,事件是在事件触发之后执行的。事件采用观察者模式,事件监听函数充当Observer,观察者会不断循环检测事件是否被触发。
- 创建事件发射器
var events = require('events'); //导入事件模块 var eventEmitter = new events.EventEmitter(); //创建事件发射器
- 注册事件
eventEmitter.on('eventName', eventHandler);
- 触发事件
eventEmitter.emit('eventName');
举例:
var events = require('events');
var eventEmitter = new events.EventEmitter();
var connectHandler = function connected(){
console.log('connnected successful.');
eventEmitter.emit('data_received');
};
eventEmitter.on('connection', connectHandler);
eventEmitter.on('data_received', function(){
console.log('data received successfully.');
});
eventEmitter.emit('connection');
console.log('Program Ended.');
注:
- 同一个事件可以添加多个监听器,事件触发后会执行所有监听器,执行顺序按照添加时的列表顺序执行。
输出:var events = require('events'); var eventEmitter = new events.EventEmitter(); var eventHandler1 = function(){ console.log('event 1'); } var eventHandler2 = function(){ console.log('event 2'); } var eventHandler3 = function(){ console.log('event 3'); } eventEmitter.addListener('event', eventHandler1); eventEmitter.on('event', eventHandler2);//on完全等价于addListener eventEmitter.on('event', eventHandler3); eventEmitter.emit('event');
event 1 event 2 event 3
- EventEmitter方法列表
No. | Method | Description |
---|---|---|
1 | addListener(event, listener) | 为事件注册监听器 |
2 | on(event, listener) | 为事件注册监听器 |
3 | once(event, listener) | 只添加一次性的监听器,执行一次后就被删除 |
4 | removeListener(event, listener) | 移除事件的某个监听器 |
5 | removeAllListeners([event]) | 移除事件的所有监听器 |
6 | setMaxListeners(n) | 默认情况下,事件添加10个监听器就会给出警告,通过该函数可以调整监听器的数量,当为0时表示可以添加无限个监听器 |
7 | listeners(event) | 返回某事件的监听器列表 |
8 | emit(event, [arg1], [arg2], … \dots …) | 触发事件 |
9 | listenerCount(emitter, event) | 返回某事件的监听器数量 |
http模块
本节参考:
[1]. https://nodejs.org/api/http.html
[2]. https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
HTTP(Hypertext Transfer Protocol, 超文本传输协议)是基于请求和响应模式、无状态的应用层协议。
Http请求由三部分组成,分别是:请求行、消息报头、请求正文。
- 请求行
请求行格式:Method Request-URI HTTP-Version CRLF
中间以空格隔开。其中Method表示请求方法;Request-URI是统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行。
请求方法列表:
Method | Description |
---|---|
GET | 请求Request-URI所标识的资源 |
POST | 在Request-URI所标识的资源后附加新的数据 |
HEAD | 请求由Request-URI所标识的资源的响应消息头 |
PUT | 请求服务器存储一个资源,并用Request-URI作为其标识 |
DELETE | 请求服务器删除Request-URI所标识的资源 |
- 消息报头
HTTP消息由客户端到服务端请求和服务端到客户端响应组成。具体包括普通报头、请求报头、响应报头、实体报头。
(1). 请求报头允许客户端向服务端传递请求的附加信息以及客户端自身的信息。
常用的请求报头包括:
Name | Description |
---|---|
Accept | 用于指定客户端接受哪些类型的信息。比如Accept: image/gif,表示客户端希望接受GIF图像格式的资源;Accept: text/html,表示客户端希望接受html文本。 |
Accept-Charset | 用于指定客户端接受的字符集。比如Accept-Charset:iso-8859-1,gb2312,缺省值是任何字符都接受 |
Accept-Encoding | 用于指定客户端可接受的内容编码。比如Accept-Encoding:gzip,deflate。大流量的站点都采用gzip压缩技术将网页内容压缩,以提高访问速度,是指WWW服务器中安装的功能。 |
Accept-Language | 用于指定客户端的语言,如Accept-Language: zh-cn |
Authorization | 用于证明客户端有权查看某个资源,当收到服务器的响应代码为401(未授权),可以发送Authorization请求,要求服务器对其进行验证。比如Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l,具体请参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization |
Host(必需) | 用于指定被请求资源的主机和端口号,通常从HTTP-URL中提取出来,比如:我们在浏览器中输入:http://www.guet.edu.cn/index.html,就会包含Host请求报头域,如下:Host: www.guet.edu.cn,此处使用了缺省端口号80 |
User-Agent | 允许客户端将操作系统、浏览器和其他属性告诉服务器。 |
请求报头举例:
GET /form.html HTTP/1.1 (CRLF)
Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/* (CRLF)
Accept-Language:zh-cn (CRLF)
Accept-Encoding:gzip,deflate (CRLF)
If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)
If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)
User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)
Host:www.guet.edu.cn (CRLF)
Connection:Keep-Alive (CRLF)
(CRLF)
(2). 响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息。
Name | Description |
---|---|
Location | 用于重定向接受者到一个新的位置。常用于响应在更换域名的时候。 |
Server | 包含了服务器信息,比如Server: Apache-Coyote/1.1 |
WWW-Authenticate | 该报头域必须包含在401(未授权)响应消息中,比如WWW-Authentivate |
(3). 实体报头,
Name | Description |
---|---|
Content-Encoding | 表示被应用到实体正文内容的编码信息,比如服务器采用了gzip压缩方法,则Content-Encoding: gzip |
Content-Length | 指定实体正文的长度,以字节的形式存储 |
Content-Type | 指明发送给接收者的实体正文的媒体类型,比如Conten-Type:text/html;charset=ISO-8859-1 |
Last-Modified | 指定资源最后修改日期和时间 |
Expires | 用于指定浏览器缓存页面的最后有效时间,超过这段时间浏览器就无法使用缓存页面,比如Expires:Thu,15 Sep 2006 16:23:12 GMT,为了让浏览器不要缓存页面,可将Expires设为o |
Web模块
web服务器时用于处理HTTP请求的软件应用程序,通常会返回一些带图片、样式表和脚本代码的html文档。
web应用的架构通常分为四层:
- 创建web服务器
创建一个server.js文件,内容如下:
创建index.htmlvar http = require('http'); var fs = require('fs'); var url = require('url'); http.createServer(function(request, response){ var pathname = url.parse(request.url).pathname; console.log("Request for "+pathname+" received"); const filename = pathname.substring(1); fs.readFile(filename, function(err, data){ if(err){ console.log(err); response.writeHead(404, {'Content-Type': 'text/html'}); }else{ response.writeHead(200, {'Content-Type': 'text/html'}); response.write(data.toString()); } }); }).listen(8081); console.log('Server running at http://127.0.0.1:8081');
运行server<html> <head> <title>Sample Page</title> </head> <body> Hello World! </body> </html>
node server.js
Express框架
本节参考:
https://expressjs.com/en/starter/installing.html
Express框架的核心功能:
- 允许设置中间件来响应HTTP请求
- 定义路由表,根据HTTP方法和URL来执行不同的操作
- 允许将参数传递给模板来动态呈现HTML页面
Hello world
const express = require('express');
const app = express();
const port = 8081;
app.get('/', (request, response)=>{
response.send('Hello World!');
});
app.listen(port, function(){
console.log('App listening at port ${port}');
});
node main.js
路由
1. 路由格式:
路由是确定服务器如何响应客户端请求的特定端点。路由的定义如下:
app.METHOD(PATH, HADLE)
其中:
- app是express的实例
- METHOD是HTTP请求方法,小写。比如get,post,put,delete,也可以使用app.all()处理所有HTTP方法,并使用app.use()将中间件指定为回调函数
- PATH是服务器上的路径
- HANDLER是路由匹配时的回调函数
同一个端点可以有多个回调函数,但是要添加next参数,使用next()将控制权转交给下一个回调函数。
var express = require('express');
var app = express();
app.use(express.static('public'));
function callback1(request, response, next){
console.log('Get request 1');
next();
};
function callback2(request, response, next){
console.log('Get request 2');
response.end();
};
app.get('/', [callback1, callback2]);
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
Get request 1
Get request 2
Route参数
用于捕捉URL中特定位置的参数值,捕捉的参数值被放到request.params中。比如:
app.get('/user/:name/:age', (request, response)=>{
response.send(request.params);
});
express.Router模块化路由
所谓模块化就是将路由封装起来,以中间件的形式使用,避免主程序代码过多冗余,从而不利于代码阅读。
首先创建一个route.js文件
const express = require('express');
const router = express.Router();
router.use(function(request, response, next){
console.log('Time: ', Date.now());
next();
});
router.get('/', function(request, response){
response.send('API Home Page');
});
router.get('/about', function(request, response){
response.send('API About Page');
});
module.exports = router;
然后在主程序中以中间件的形式使用它
var express = require('express');
var app = express();
const api = require('./route');
app.use('/api', api);
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
现在,/api就表示该路由的主端点:
response返回方法:
response对象用于向客户端发送响应,如果路由回调函数没有发送以下方法作为响应,客户端的请求将会一直挂起。
Method | Description |
---|---|
res.download() | 提示客户端下载文件 |
res.end() | 结束response响应,如果没有参数,则不回传任何数据结束响应 |
res.json() | 响应一个json数据 |
res.redirect([status, ]path) | 以指定的status请求重定向到指定path下,status缺省值为302"Found" |
res.render(view) | 渲染一个view,并返回渲染好的html文档,view是个view模板的路径字符串参数 |
res.send([body]) | 向客户端发送数据,参数可以是Buffer对象、字符串、object、boolean、array |
res.sendFile(path [,options][, fn]) | 向客户端发送文件,文件类型由response的HTTP头域Content-Type指定,如下例所示。 |
res.sendStatus() | 设置response的状态并以字符形式向客户端发送 |
var express = require('express');
var app = express();
app.get('/', (request, response)=>{
//response.set('Content-Type', 'text/html');
const path = __dirname+"/success.html";
var options = {
headers: {
'Content-Type': 'text/html'
}
};
response.status(404).sendFile(path, options, function(err){
if(err)
next(err);
else
console.log('Send:', path);
});
});
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
Response设置HTTP头方法
Name | Description |
---|---|
res.set() | 设置response的HTTP头 |
res.status(code) | 设置response的HTTP状态 |
res.type() | 设置Content-Type内容格式 |
res.append() | 添加特定的头域内容到HTTP头中,如果HTTP头不存在则先创建。注:如果使用append后再使用set会覆盖 |
res.cookie(k, v[, options]) | 设置客户端的cookie键值对,options请查看链接中的表格 |
res.clearCookie(k[, options]) | 清除k所对应的cookie |
流
Stream是一个对象,该对象主要功能是:
(1). 读取内容
(2). 写入内容
在Node.js中有四种类型的流,分别是:
- Readable - 用于读取数据的Stream
- Writable - 用于写入数据的Stream
- Duplex - 用于读写数据的Stream
- Transform - 用于处理读入的数据,并给出相应的输出
每个Stream都是一个事件观察者EventEmitter,它们注册的事件包括:
- data - 当Stream中有可读取的数据时触发该事件
- end - 当Stream中没有可读取的数据时触发该事件
- error - Stream中出现错误时触发该事件
- finish - 当Stream中所有数据都flush到系统中触发
1. 利用Stream读取数据
创建input.txt文件,内容如下:
Tutorials Point is giving self learning content
to teach the world in simple and easy way!!!!!
main.js内容:
var express = require('express');
const path = require('path');
var fs = require('fs');
var app = express();
var data = '';
var readerStream = fs.createReadStream(path.join(__dirname, './input.txt'));
readerStream.setEncoding('UTF-8');
readerStream.on('data', function(chunk){
data += chunk;
});
readerStream.on('end', function(){
console.log(data);
});
readerStream.on('error', function(err){
console.log(err.stack);
});
app.get('/', function(request, response, next){
response.write(data, "utf-8", function(err){
if(err)
next(err)
else
console.log('Write data finished.\n');
});
response.end();
});
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
2. 利用Stream写入数据
var express = require('express');
const path = require('path');
var fs = require('fs');
var app = express();
var data = 'Hello World!';
var writerStream = fs.createWriteStream('output.txt');
writerStream.write(data, 'utf-8');
writerStream.end();
writerStream.on('finish', function(){
console.log("Write Complete.");
});
writerStream.on('error', function(err){
console.log(err.stack);
});
app.get('/', function(request, response, next){
response.end();
});
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
3. 管道机制
管道是将一个Stream的输出作为另一个Stream的输入。比如:
var express = require('express');
const path = require('path');
var fs = require('fs');
var app = express();
var data = 'Hello World!';
var readerStream = fs.createReadStream(path.join(__dirname, "/input.txt"));
var writerStream = fs.createWriteStream('output.txt');
readerStream.pipe(writerStream);
writerStream.on('finish', function(){
console.log("Write Complete.");
});
writerStream.on('error', function(err){
console.log(err.stack);
});
app.get('/', function(request, response, next){
response.end();
});
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
压缩input.txt
var express = require('express');
const path = require('path');
var fs = require('fs');
var app = express();
var zlib = require('zlib');
fs.createReadStream(path.join(__dirname, '/input.txt')).pipe(zlib.createGzip()).pipe(fs.createWriteStream('input.txt.gz'));
app.get('/', function(request, response, next){
response.end();
});
var server = app.listen(8081, function(){
var host = '127.0.0.1';
var port = server.address().port;
console.log("Server Running at http://%s:%s", host, port);
});
解压缩
// Decompress the file input.txt.gz to input.txt
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('input.txt'));
Child Process模块
本节参考:
https://nodejs.org/docs/latest-v15.x/api/child_process.html
子进程模块用于创建子进程。每个子进程自带三个流对象:child.stdin、child.stdout、和child.stderr。在node.js中。
1.异步创建子进程
有四种方式异步创建子进程:spawn()、fork()、exec()、execFile()。
Name | Description |
---|---|
spawn(command[, args][, options]) | 使用command创建新进程 |
fork(modulePath[, args][, options]) | 是spawn的特殊形式,如fork(’./son.js’)相当于spawn(‘node’, [’./son.js’])。而且fork会在父进程和子进程之间建立通信管道,用于进程之间的通信 |
exec(command[, options], callback) | 生成一个shell,然后command在该shell中运行,子进程执行完毕后将结果存放在回调函数的参数中。 |
execFile(file[, args][, options][, callback]) | 功能类似于exec,但不生成shell |
2. 同步创建子进程
Name | Description |
---|---|
object spawnSync(command[, args][, options]) | 等价于spawn,但是只有子进程退出才返回 |
string execSync(command[, options]) | 等价于exec,但是只有子进程退出才返回 |
string execFileSync(command[, args][, options]) | 等价于execFile,但是只有子进程退出才返回 |
3. 事件
Name | Description |
---|---|
close | 子进程的stdio流被关闭时触发 |
exit | 子进程结束时触发,由于多个子进程可以共享流,所以进程结束时,其流并没有被关闭。 |
disconnect | 在执行disconnect()方法时触发 |
message | 子进程使用process.send()时触发 |
spawn | 子进程spawn成功时触发,如果spawn不成功则触发error |
error | 1. 该进程无法spawn 2. 该进程无法被killed 3. 向子进程发送消息失败 触发 |