Node.js概述
Node.js概述
Node.js 就是运行在服务端的 JavaScript。
Node.js 是一个基于Chrome JavaScript 运行时建立的一 个平台。
Node.js是一个事件驱动、I/O、服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript 的速度非常快,性能非常好。
Node.js的结构
- Node.js标准库:是在使用过程中能直接调用的API
- 具体 包括http、net、stream、fs、buffer、events等模块
- Node bindings
- 是JavaScript与底层C/C++能够沟通的关键,前者通过bindings调用后者,相互交换数据
- Node基础构件
- V8
- libuv
- http_parser、OpenSSL、zlib等
Node.js特点
- 事件驱劢
- 异步、非阻塞I/O
- 性能出众
- 单线程
Node.js 的失误
- 没有坚持使用 Promise,Node里面就遍布着async/await 和 promise的不同 异步API的设计,直至现在都极难整合
- 看轻了安全性,有可以直接读取Memory的例子,或者 linter 可以直接使用网络功能等的漏洞
- package.json以及依赖了npm,Node的Module变成了由npm集权
- 在任何地方也可以require(“somemodule”)
- package.json 提供了错误的module 概念
- 设计了软件界黑洞node_modules,包太大
- require(“module”)没有逼人用文件扩展名.js,不过得多几次查询
Node.js应用
- JSON APIs
- 单页面、多Ajax请求应用
- 基于Node.js开发UNIX命令行工具
- 流式数据
- 准实时应用系统
Node.js语法基础
交互式运行环境——REPL
在这个交互式环境中可以运行简单的应用程序。在 控制台直接输入node即可进入这个环境
在REPL运行环境中操作变量
Node.js的控制台
console.log方法
console.error方法
console.dir方法
console.time方法与console.timeEnd方法
Node.js中的包管理
NPM介绍
包管理工具,可以很方 便地下载使用第三方模块,简化开发工作,提高项目开发效率。
NPM常用命令
npm –v、npm version
npm init -y
npm install
package.json文件
提供包描述的文件
模块加载原理与加载方式
通过require方法导入模块
const myModule = require('./myModule'); console.log(myModule);
通过exports方法导出模块
module.exports = util
Node.js事件处理机制
事件处理机制
许多对象也将触发各种事件。例如,针对代表了Web服务器的http.Server对 象来说,可能会触发“接收到客户端请求”、“产生连接错误”等各种事件。针对每个不同的事件,都需要进行不同的事件处理。
EventEmitter类
所有可能触发事 件的对象都是一个继承了EventEmitter类的子类的实例对象
方法名与参数 描述
addListener(event, listener) 对指定事件绑定事件处理函数
on(event, listener) 对指定事件绑定事件处理函数,上一个方法的别名
once(event, listener) 对指定事件指定只执行一次的事件处理函数
removeListener(event,listener) 对指定事件解除事件处理函数
removeAllListeners([event]) 对指定事件解除所有事件处理函数
setMaxListeners(n) 指定事件处理函数的最大数量,n为整数值代表最大的可指定事件处理函数的数量。
listeners(event) 获取指定事件的所有事件处理函数
emit(event, [arg1], [arg2],[...])手动触发指定事件
事件环机制
在回调函数的执行过程中,他将转而处理新的事件,在该事件处理完毕之后,转而继续处理原回调函 数。这种环状处理机制,在Node.js中称为事件环机制。
Node.js核心模块
http模块
Node.js的http模块本身就可以构建服务器,而且性能非常可靠。
const http = require('http');
const server = http.createServer(function (req, res) {
res.writeHead(200, {
'content-type': 'text/plain'
});
res.end('Hello, Node.js!');
});
server.listen(3000, function () {
console.log('listening port 3000');
});
http.Server的事件主要有
- request:当客户端请求到来时,该事件被触发,提供req和res两个参数,表示请 求和响应信息。
- connection:当TCP连接建立时,该事件被触发,提供一个socket参数,是net.Socket的实例
- close:当服务器关闭时,触发事件(注意不是在用户断开连接时)
- http.createServer():添加了一个request事件监听
http.IncomingMessage是HTTP请求的信息,3个事件:
- data:当请求体数据到来时该事件被触发。该事件提供一个chunk参数,表示接受的数据。
- end:当请求体数据传输完毕时该事件被触发,此后不会再有数据。
- close:用户当前请求结束时,该事件被触发。
http.IncomingMessage提供的主要属性有:
- method:HTTP请求的方法,如GET。
- headers:HTTP请求头。
- url:请求路径。
- httpVersion:HTTP协议的版本。
const http = require('http');
const server = http.createServer(function (req, res) {
let data = '';
req.on('data', function (chunk) {
data += chunk;
});
req.on('end', function () {
let method = req.method;
let url = req.url;
let headers = JSON.stringify(req.headers);
let httpVersion = req.httpVersion;
res.writeHead(200, {
'content-type': 'text/html'
});
let dataHtml = '<p>data:' + data + '</p>';
let methodHtml = '<p>method:' + method + '</p>';
let urlHtml = '<p>url:' + url + '</p>';
let headersHtml = '<p>headers:' + headers + '</p>';
let httpVersionHtml = '<p>httpVersion:' + httpVersion + '</p>';
let resData = dataHtml + methodHtml + urlHtml + headersHtml +
httpVersionHtml;
res.end(resData);
});
});
server.listen(3000, function () {
console.log('listening port 3000');
});
客户端向http服务器发起请求
- http.request(option[,callback]):
- option为json对象,主要字段有host、port(默认为80)、
- method(默认为GET)、
- path(请求的相对于根的路径,默认为“/”)、headers等。
- 该方法返回 一个httpClientRequest实例。
- http.get(option[,callback]):http.request()调用HTTP请求方式GET方法。
url模块-url地址处理
url模块是一个分析、解析url的模块,主要提 供以下三种方法:
- URL.hash :获取和设置网址的片段部分。
- URL.host:获取和设置网址的主机部分。
- URL.hostname:获取和设置网址的主机名部分。 url.host 和 url.hostname 之间的主要区别在于 url.hostname 不包括端口。
- URL.href:获取和设置序列化的网址。
const myURL = new URL('https://example.org/foo#bar');
console.log(myURL.hash);//#bar
myURL.hash = 'baz';
console.log(myURL.href);//https://example.org/foo#baz
const myURL = new URL('https://example.org:81/foo');
console.log(myURL.host);//example.org:81
const myURL = new URL('https://example.org:81/foo');
console.log(myURL.hostname);//example.org
const myURL = new URL('https://example.org/foo');
console.log(myURL.href);//https://example.org/foo
util模块
-
util.inspect():返回一个对象反序列化形成的字符串。
-
util.format():返回一个使用占位符格式化的字符串,类似于C语言的printf。可以使用的占位符
有%s、%d、%j。
- util.log():在控制台输出,类似于console.log(),但这个方法带有时间戳。
path模块-路径处理
-
path.join():将所有的参数连接起来,返回一个路径。
-
path.extname():返回路径参数的扩展名,无扩展名时返回空字符串。
-
path.parse():将路径解析为一个路径对象。
-
path.format():接受一个路径对象为参数,返回一个完整的路径地址。
dns模块
-
dns.resolve():将一个域名解析为一个指定类型的数组。
-
dns.lookup():返回第一个被发现的IPv4或者IPv6的地址。
-
dns.reverse():通过IP解析域名。
Node.js中的Buffer类
在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制 数据的缓存区。
创建Buffer对象
new Buffer(size)
buf = new Buffer(128);
使用Buffer对象的fill方法来初始化缓存区中的所有内容
buf.fill(value, [offset], [end])
用于取出指定位置处数据的slice方法(左闭右开)
str='我喜爱编程';buf =new Buffer(str);str.slice(2,4);//'爱编'buf.slice(2,4);//<Buffer 91 e5>
由于Buffer对象的slice方法并不是复制缓存区中的数据,而是与该数据共享内存区域,因 此,如果修改使用slice方法取出的数据,则缓存区中保存的数据也将被修改。
Node.js中Buffer对象的转换
toString方法
buf.toString([encoding], [start], [end])buf.toString("utf8",9,12);//'编'
write方法
buf.write(string, [offset], [length], [encoding])buf.write("热",3,3);//3buf.toString();//'我热爱编程'
Buffer对象与JSON对象之间的相互转换
> buf=new Buffer('我喜爱编程');<Buffer e6 88 91 e5 96 9c e7 88 b1 e7 bc 96 e7 a8 8b>> json = JSON.stringify(buf);'[230,136,145,229,150,156,231,136,177,231,188,150,231,168,139]'> JSON.parse(json);[230,136,145,229,150,156,231,136,177,231,188,150,231,168,139]> copy = new Buffer(JSON.parse(json));<Buffer e6 88 91 e5 96 9c e7 88 b1 e7 bc 96 e7 a8 8b>> copy.toString();'我喜爱编程'
复制缓存数据
buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
Buffer类的类方法
-
isBuffer方法
-
isBuffer方法用于判断一个对象是否为一个Buffer对象
-
Buffer.isBuffer(obj)
-
-
byteLength方法
-
使用byteLength方法计算一个指定字符串的字节数
-
Buffer.byteLength(string, [encoding])//encoding默认值为utf8
-
-
concat方法
-
用于将几个Buffer对象结合创建为一个新的Buffer对象
-
Buffer.concat(list,[totalLength]);var buf=Buffer.concat([str1,str2,str3,str4,str5]);buf.toString()//'我喜爱编程'
-
Node.js文件系统(fs)
同步和异步
使用fs模块来实现所有有关文件及目录的创建、写入及删除操作。
在这些方法中,所有方法名中具有Sync后缀的方法均为同步方法, 而不具有Sync后缀的方法均为异步方法。
区别:同步方法立即返回操作结果,在使用同步方法执行的操作结束之前,不能执行后续代码
异步方法:
同时调用多个异步方法时,并不能确保操作结果的返回顺序。完全 取决于程序读取该文件所花费的时间。
对文件执行读写操作
文件的读操作
异步读取:
fs.readFile(filename, [options], callback)filename:用于指定读取文件的完整文件路径及文件名options:为一个对象,在其中指定读取文件时需要使用的选项(flag属性指定)1. 'r':读取文件,如果文件不存在则抛出异常。2. 'r+':读取并写入文件,如果文件不存在则抛出异常。3. 'rs':以同步方式读取文件并通知操作系统忽略本地文件系统缓存,如果文件不存在则抛出异常。 由于该属性值的使用将使操作系统忽略本地文件系统缓存机制,因此在操作网络文件系统时建议使用该属性值,但由于其对性能产生一定的负面影响,所以在其他场合下不建议使用。4. 'w':写入文件。如果文件不存在则创建该文件,如果该文件已存在则清空文件内容。5. 'wx':作用与'w'类似,但是以排他方式写入文件。6. 'w+':读取并写入文件。如果文件不存在则创建该文件,如果该文件已存在则清空文件内容。7. 'wx+':作用与'w+'类似,但是以排他方式打开文件。8. 'a':追加写入文件,如果文件不存在则创建该文件。9. 'ax':作用与'a'类似,但是以排他方式写入文件。10. 'a+':读取并追加写入文件,如果文件不存在则创建该文件。11. 'ax+':作用与'a+'类似,但是以排他方式打开文件。
var fs = require('fs');fs.readFile('./test.txt', function (err, data) { if (err) console.log('读取文件时发生错误。'); // 在控制台中输出文件内容 else console.log(data.toString());});
同步读取:
var data=fs.readFileSync(filename, [options])var fs = require('fs');try { var data = fs.readFileSync('./test.txt', 'utf8'); // 在控制台中输出文件内容 console.log(data);} catch (ex) { console.log('读取文件时发生错误。');}
文件的读操作
fs.writeFile(filename,data,[options],callback)filename:用于指定需要被写入文件的完整文件路径及文件名,data:参数用于指定需要写入的内容,参数值可以为一个字符串或一个Buffer对象,该字符串或缓存区中的内容将被完整地写入到文件中。options:参数值为一个对象,在其中指定写入文件时需要使用的选项1. flag属性:用于指定对该文件采取何种操作,默认值为'w'2. mode属性:用于指定当文件被打开时对该文件的读写权限,默认值为0666(可读写)。3. encoding属性:用于指定使用何种编码格式来写入该文件
//覆盖数据var fs = require('fs');var data = new Buffer.from('我喜爱编程');fs.writeFile('./test.txt', data, function (err) { if (err) console.log('写文件操作失败。'); else console.log('写文件操作成功。');});//追加数据var fs = require('fs');var options = { flag: 'a'}fs.writeFile('./test.txt', '这是追加的数据。', options, function (err) { if (err) console.log('写文件操作失败。'); else console.log('写文件操作成功。');});
创建与读取目录
创建目录
fs.mkdir(path,[mode],callback)path参数用于指定需要被创建的目录的完整路径及目录名mode参数值用于指定该目录的权限,默认值为0777(表示任何人可读写该目录)callback参数用于指定创建目录操作完毕时调用的回调函数
var fs = require('fs');fs.mkdir('./test', function (err) { if (err) console.log("创建目录操作失败。"); else console.log("创建目录操作成功。");});
读取目录
fs.readdir(path,callback)path参数用于指定需要被读取的目录的完整路径及目录名;callback参数用于指定读取目录操作完毕时调用的回调函数
var fs = require('fs');fs.readdir('./', function (err, files) { if (err) console.log('读取目录操作失败。'); else console.log(files);});
查看与修改文件或目录的信息
查看文件或目录的信息
使用stat方法或lstat方法查看一个文件或目录的信息
fs.stat(path, callback)fs.lstat(path, callback)path参数用于指定需要被查看的文件或目录的完整路径及文件名或目录名,callback参数用于指定查看文件或目录信息操作完毕时执行的回调函数。
var fs = require('fs');fs.stat('./test.txt', function (err, stats) { console.log(stats);});
检查文件或目录是否存在
使用exists方法检查一个文件或目录是否存在
fs.exists(path, callback)path参数用于指定需要被检查的文件或目录的完整路径及文件名或目录名,callback参数用于指定检查文件或目录信息操作完毕时执行的回调函数
var fs = require('fs');fs.exists('./test.txt', function (exists) { if (exists) console.log('该文件存在。'); else console.log('该文件不存在。');})
修改文件或目录的读写权限
fs.chmod(path, mode, callback)0600代表所有者可读写,其他人没有任何权限0644代表所有者可读写,其他人只读0755代表所有者有所有权限,其他所有人可读和执行0740代表所有者有所有权限,所有者所在的组只读// 0740代表所有者有所有权限,所有者所在的组只读fs.chmod('./test4.txt', 0740, function (err) { if (err) console.log("修改文件权限操作失败。"); else console.log("修改文件权限操作成功。");});
删除空目录
fs.rmdir(path,callback)var fs = require('fs');fs.rmdir('./test', function (err) { if (err) console.log('删除空目录操作失败。'); else console.log('删除空目录操作成功。');});
监视文件或目录
以使用watchFile方法对文件进行监视,并且在监视到文件被修改时执行某些处理
fs.watchFile(filename,[options],listener)listener参数值用于指定当被监视的文件发生改变时调用的回调函数
var fs = require('fs');fs.watchFile('./message.txt', function (curr, prev) { if (Date.parse(prev.ctime) == 0) console.log('message.txt文件被创建。'); else if (Date.parse(curr.ctime) == 0) console.log('message.txt文件被删除。'); else if (Date.parse(prev.mtime) != Date.parse(curr.mtime)) console.log('message.txt文件内容被修改。');});
Node.js网络开发(net)
使用Node.js创建TCP服务器
net.createServer([options][,connectionListener])1.options是一个对象参数值,有两个布尔类型的属性allowHalfOpen和pauseOnConnect。这两个属性默认都是false。2.connectionListener是一个当客户端与服务端建立连接时的回调函数,这个回调函数以socket端口对象作为参数。事件:1. connection:当有新的链接创建时触发,回调函数的参数为socket连接对象。2. close:TCP服务器关闭的时候触发,回调函数没有参数。3. error:TCP服务器发生错误的时候触发,回调函数的参数为error对象。4. listen:监听客户端的连接server.listen(port[,host][,backlog][,callback]);1. port:为需要监听的端口号。此参数值为0的时候将随机分配一个端口号。2. host:服务器地址。3. backlog:连接等待队列的最大长度。4. callback:回调函数
var net = require('net');var server = new net.Server();server.on('connection', function (socket) { console.log('someone connects');});server.listen(8001);server.on('listening', function () { console.log('server is listening');});server.on('close', function () { console.log('server closed');});server.on('error', function (err) { console.log('error');});
查看服务器监听的地址
当创建了一个TCP服务器后,可以通过server.address()方法来查看这个TCP服务器监听的地址,并返回 一个JSON对象。
- port:TCP服务器监听的端口号。
- family:说明TCP服务器监听的地址是IPv6还是IPv4。
- address:TCP服务器监听的地址。
var net = require('net');var server = net.createServer(function (socket) { console.log('someone connects');});server.listen(8001, function () { var address = server.address(); console.log('the port of server is ' + address.port); console.log('the address of server is ' + address.address); console.log('the famaily of server is ' + address.family);});
连接服务器的客户端数量
通过server.getConnections()方法获取连接这个TCP服务器的客户端数量。这个方法是一个异步的方法
- 第一个参数为error对象。
- 第二个参数为连接TCP服务器的客户端数量。
var net = require('net');var server = net.createServer(function (socket) { console.log('someone connects'); server.maxConnections = 3; server.getConnections(function (err, count) { console.log('the count of client is ' + count); });});server.listen(8001, function () { console.log('server is listening');});
获取客户端发送的数据socket
var net = require('net');var server = net.createServer(function (socket) { socket.on('data', function (data) { console.log(data.toString()); });});server.listen(8001, function () { console.log('server is listening');});
发送数据给客户端用socket.write()
socket对象
1. socket.localPort:本地端口的地址。2. socket.localAddress:本地IP地址。3. socket.remotePort:进程端口地址。4. socket.remoteFamily:进程IP协议族。5. socket.remoteAddress:进程IP地址。6. socket.bytesWritten:发送数据的字节数7. socket.bytesRead:接收数据的字节数
var net = require('net');var server = net.createServer(function (socket) { var address = server.address(); var message = 'client, the server address is ' + JSON.stringify(address); socket.write(message, function () { var writeSize = socket.bytesWritten; console.log(message + 'has send'); console.log('the size of message is ' + writeSize); }); socket.on('data', function (data) { console.log(data.toString()); var readSize = socket.bytesRead; console.log('the size of data is ' + readSize); });});server.listen(8001, function () { console.log('server is listening');});
Node.js构建HTTP服务器
创建HTTP服务器
var http = require('http');var fs = require('fs');var server = http.createServer(function (req, res) { res.writeHead(200, { 'content-type': 'text/html' }); var data = fs.readFileSync('./index.html'); res.write(data); res.end();});server.listen(3000, function () { console.log('listening port 3000');});
HTTP服务器的路由控制
module.exports = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", ".gif": "image/gif", ".ico": "image/x-icon", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".png": "image/png"};var http = require('http');var fs = require('fs');var url = require('url');var mime = require('./mime');var path = require('path');var server = http.createServer(function (req, res) { var filePath = '.' + url.parse(req.url).pathname; if (filePath === './') { filePath = './index.html'; } fs.exists(filePath, function (exist) { if (exist) { var data = fs.readFileSync(filePath); var contentType = mime[path.extname(filePath)]; res.writeHead(200, { 'content-type': contentType }); res.write(data); res.end(); } else { res.end('404'); } })});server.listen(3000, function () { console.log('listening port 3000');});
Node.js—Express框架
核心特性
- 通过设置中间件来处理HTTP请求。
- 通过路由来执行不同的HTTP请求操作。
- 通过模板来渲染HTML页面。
Express框架-路由
路由是指应用程序如何根据指定的路由路径和指定的HTTP请求方法(GET和POST等)来处理请 求。
app.METHOD(PATH, HANDLER)1. app:应用实例。2. METHOD:小写get和post等的HTTP请求方法。3. PATH:路由路径。4. HANDLER:路由匹配时执行的函数。
路由方法
路由方法是从HTTP方法派生的
Express支持所有的HTTP请求方法:1. get2. post3. head4. options5. delete6. put7. patch
需要使用一个路由来处理所有的请求方法,则可以调用app.all():
app.all('/', (req, resp) => { resp.send('请求首页');});
路由路径
定义了可以发出请求的地址。路由路径可以是字符串、字符串模式或正则 表达式
“?”号在正则表达式中代表“至多一个”
+在正则表达式中代表“至少一个”
()在正则表达式中代表分组,要么有一个bc,要 么没有bc
//以下路由路径将与/acd和/abcd相匹配。app.get('/ab?cd', (req, resp) => { resp.send('ab?cd');});//以下路由路径将与/abcd、/abbcd、/abbbcd等等相匹配app.get('/ab+cd', (req, resp) => { resp.send('ab+cd');});//以下路由路径将与/abcd和/ad相匹配app.get('/a(bc)?d', (req, resp) => { resp.send('a(bc)?d');});
路由参数
用于捕获URL中各位置的值。捕获的值将填充到req.params对象中,并将路由路径中指定 的route参数名称作为req.params对象的键。
//http://127.0.0.1:8080/users/1/timelines/1app.get('/users/:userId/timelines/:timelineId', (req, resp) => { resp.json(req.params);});
路由函数
路由方法和路由路径匹配之后就会执行对应的路由函数
function(request, response, next)1. request Express:请求对象。2. response Express:响应对象。3. next:匹配的下一个路由函数(可选参数)。
单个路由函数
app.get('/', (req, resp) => { resp.send('/');});
多个路由函数
app.get('/', (req, resp, next) => { console.log(`${req.method} ${req.path}`); next();}, (req, resp) => { resp.send('首页');});//另一种形式function logger(req, resp, next) { console.log(`${req.method} ${req.path}`); next();}function home(req, resp) { resp.send('首页');}// 设置路由app.get('/', [logger, home]);
公共路由路径
app.router('/user/login') .get((req, resp) => { resp.send('登录页面'); }) .post((req, resp) => { resp.send('登录处理'); })
模块化的路由
使用Express提供的Router对象可以创建模块化的对象,实现路由和入口JS的解耦。使用模块化的 路由有以下优点:
- 便于维护。
- 统一的路由前缀
- 模块化。
const express = require('express');const router = express.Router();router.get('/login', (req, resp) => { resp.send('登录');});router.get('/register', (req, resp) => { resp.send('注册');});// 导出路由对象module.exports = router;
Express框架-请求对象
每个路由函数都会接收一个request对象,通过该对象可以获取本次请求的一些信息,比如请求方 法、请求路径、请求参数等等
属性名 | 说明 | 备注 |
---|---|---|
method | 请求方法 | |
path | 请求路径 | GET参数不包含在path中 |
url | 请求URL | 除域名外的完整URL,包含GET参数 |
query | GET参数对象 | |
params | 路由参数对象 | |
headers | 请求报头 | |
cookies | 请求cookie | 需要使用cookie-parser中间件 |
ip | 客户端IP | |
body | POST请求数据 | 需要使用body-parser中间件 |
获取请求Cookie
中间件是可以访问请求对象、响应对象以及next()的函数。中间件可以完成以下任务: 1. 执行任何代码。 2. 更改请求和响应对象。 3. 结束请求处理。 4. 调用下一个中间件。
npm install cookie-parser --save
获取请求体
Express默认也不处理请求体,如果需要获取请求体,需要监听request的data和end事件手动解 析,这里我们直接使用body-parser中间件即可。
安装body-parser中间件:
npm install body-parser –save
响应对象
resp.status(statusCode);
:设置响应状态码。resp.set(field[, value])
:设置响应报头。要一次设置多个响应报头字段,需要传递对象作为参数。- field:响应报头字段名称。
- value:响应报头字段值。
resp.download(path[, filename][, options][, callback])
通过Content-Disposition响应报头提示客户端下载文件。- path:需要提供给客户端下载的服务端文件路径。
- filename:客户端下载文件时的别名。
- options:下载选项。
- callback:回调函数。
resp.end([chunk][, encoding][, callback])
结束响应过程。- chunk:响应数据。
- encoding:响应体编码。
- callback:回调函数。
resp.redirect([status,] path)
重定向到指定的URL Path或者完整的URL链接。默认情况下响应状态码为302。- status:响应状态码,301或者302。
- path:重定向路径。
resp.render(view[,locals][,callback])
渲染HTML模板页面。渲染模板页面需要使用到模板引擎- view:视图名称。
- locals:传递到视图的变量对象,视图可以访问到这些变量并进行渲染。
- callback:回调函数。如果提供,该方法将返回可能的错误和HTML字符串,但不是自动发送HTML 到客户端。
resp.cookie(name, value[, options])
设置Cookie。- name:Cookie名称。
- value:Cookie值。
- options选项:
- domain:域名。默认为当前域名。
- expires GMT:到期时间。如果未设置或者设置为0,则浏览器关闭后Cookie失效。
- httpOnly:将Cookie标记为只有HTTP服务器能访问(客户端JS无法访问)。
- maxAge:以毫秒为单位的过期时间。通常比expires选项使用方便。
- path:Cookie路径。
- secure:标记为仅在HTTPS协议下才发送。
- signed:是否对Cookie签名。
rresp.send([body])
发送HTTP响应。该方法可以根据传入的内容来输出不同格式的响应内容。- String:Content-Type将自动设置为text/html。
- Buffer:Content-Type将自动设置为application/octet-stream。
- Object或Array:Content-Type将自动设置为application/json。
Express框架-中间件
中间件是一个函数,在应用的请求-响应周期中,能够访问请求对象、响应对象和next函数。 中间件可以执行以下任务:
- 执行逻辑代码。
- 更改请求和响应对象。
- 结束请求-响应周期。
- 调用下一个中间件。
全局中间件
const express = require('express');const app = express();function logger(req,resp,next){ console.log(`${req.method}${req.path}"${req.headers['user-agent']}"`); next();}app.use(logger);
路由中间件
const express = require('express');const app = express();function logger(req,resp,next){ console.log(`${req.method}${req.path}"${req.headers['user-agent']}"`); next();}app.get('/',logger,(req,resp)=>{ resp.send('Hello World');});
可配置的中间件
app.use(cookieParser());
静态资源中间件
为了提供图片、CSS和JS之类的静态文件的访问,可以使用内置的express.static中间件。
express.static(root, [options])
Express框架-EJS模板渲染
模板渲染
app.set('views','./templates'); //设置模板文件目录app.set('view engine','ejs'); //设置模板引擎
Node.js-Koa框架
Koa框架
通过基于Promise的异步编程,Koa应用可以不使用回调(callback),大大提高了开发效率。(Koa框架使用了ES2017最新的async/await语法来进行异步编程)
Koa在其核心并未捆绑任何中间件(甚至于路由功能都需要外部中间件完成)
Bluebird
提供了 包装方法,可以快速地将Node.js回调风格的函数包装为Promise
function(err, data1, data2, ..., dataN)
Bluebird的使用
npm install bluebird --savebluebird.promisifyAll(target, options)1. target需要包装的对象2. options选项: suffix:异步API方法名后缀,默认为“Async”。 multiArgs:是否允许多个回调参数,默认false。
const bluebird = require('bluebird');const fs = require('fs');const app = new Koa();bluebird.promisifyAll(fs);//回调函数示例fs.readFile('./data.log',{encoding:'utf8'},(err,data)=>{ if(err){ console.warn(err); return; } console.log(data); });//Promise示例fs.readFileAsync('./data.log', { encoding: 'utf8'}).then((data) => { console.log(data);}).catch((err) => { console.warn(err);});
Context
Context在Koa应用中又称为“上下文”,该对象包含了Koa请求对象、Koa响应对象和应用实例,Context可以理解一个容器,该容器挂载了本次请求的请求对象和响应对象等信息
Context实例有以下常用的几个属性或方法:
ctx.request
:Koa的请求对象,一般不直接使用,通过别名引用来访问。ctx.response
:Koa的响应对象,一般不直接使用,通过别名引用来访问。- *
ctx.state
:自定义数据存储,比如中间件需要往请求中挂载变量就可以存放在ctx.state中,后续 中间件可以读取。 - *
ctx.throw()
:抛出HTTP异常。 ctx.headers
:请求报头,ctx.request.headers的别名。ctx.method
:请求方法,ctx.request.method的别名。ctx.url
:请求链接,ctx.request.url的别名。ctx.path
:请求路径,ctx.request.path的别名。*ctx.query
:解析后的GET参数对象,ctx.request.query的别名。ctx.host
:当前域名,ctx.request.host的别名。*ctx.ip
:客户端IP,ctx.request.ip的别名。ctx.ips
:反向代理环境下的客户端IP列表,ctx.request.ips的别名。ctx.get()
:读取请求报头,ctx.request.get的别名。*ctx.body
:响应内容,支持字符串、对象、Buffer,ctx.response.body的别名。*ctx.status
:响应状态码,ctx.response.status的别名。ctx.type
:响应体类型,ctx.response.type的别名。*ctx.redirect()
:重定向,ctx.response.redirect的别名。*ctx.set()
:设置响应报头,ctx.response.set的别名
// 导入模块const Koa = require('koa');// 实例化应用const app = new Koa();app.proxy = true;// 中间件app.use(async (ctx) => { ctx.set('x-version', '1.0.0'); ctx.body = { method: ctx.method, path: ctx.path, url: ctx.url, query: ctx.query, headers: ctx.headers, ip: ctx.ip };});// 监听app.listen(10000, () => { console.log('listen on 10000');});
const Koa = require('koa');const app = new Koa();app.keys = ['signeds2ssscKey']; // 推荐使用随机字符串// 中间件app.use(async (ctx) => { //设置cookies ctx.cookies.set('logged', 1, { signed: true, //cookie签名 httpOnly: true, maxAge: 3600 * 24 * 1000 //有效期1天 }); // 删除cookies ctx.cookies.set('logged', 0, { signed: true, //cookie签名 httpOnly: true, maxAge: 3600 * 24 * 1000 //有效期1天 }); //读取cookies ctx.body = ctx.cookies.get('logged', 1, { signed: true //cookie签名 });});// 监听app.listen(10000, () => { console.log('listen on 10000');});
Koa框架-中间件
Koa的中间件也能访问请求对象、响应对象和next函数
任务
- 执行逻辑代码。
- 更改请求和响应对象。
- 结束请求-响应周期。
- 调用下一个中间件。
- 错误处理。
async function middleware(ctx, next)1. ctx:上下文对象。2. next:下一个中间件。
运行完逻辑代码,将需要传递的数据挂载到ctx.state,并且调用await next()才能将请求交给下一个中间 件处理。
Koa的中间件模型称为“洋葱圈模型”,请求从左边进入,有序地经过中间件处理,最终从右边输出响 应。
一般的中间件会执行两次(下面会给出示例),调用next之前为第一次,也就是“洋葱左半边”这一 部分,从外层向内层依次执行。当后续没有中间件时,就进入响应流程,也就是“洋葱右半边”这一部 分,从内层向外层依次执行,这是第二次执行。
// 导入模块const Koa = require('koa');// 实例化应用const app = new Koa();async function middleware1(ctx, next) { console.log('middleware1 start'); await next(); console.log('middlware1 end')}async function middleware2(ctx, next) { console.log('middleware2 start'); await next(); console.log('middlware2 end')}// 中间件app.use(middleware1);app.use(middleware2);// 路由app.use(async (ctx) => { console.log('router'); ctx.body = 'Hello World';});// 监听app.listen(10000, () => { console.log('listen on 10000');})
可配置的中间件
// 导入模块const Koa = require('koa');// 实例化应用const app = new Koa();//日志中间件function logger(options) { return async function (ctx, next) { const start = Date.now(); await next(); const parts = []; options.method && parts.push(ctx.method); options.path && parts.push(ctx.path); options.userAgent && parts.push(ctx.headers['user-agent']); parts.push(`${Date.now()-start}ms`); console.log(parts.join(' ')); }}app.use(logger({ method: true, path: true})); //挂载中间件并传递选项//路由app.use(async (ctx) => { ctx.body = 'hello world';});// 监听app.listen(10000, () => { console.log('listen on 10000');});//打印结果:GET / 2 m
Cookie解析中间件
编写的中间件解析完请求Cookie之后,需要将其挂载到ctx.state中,然 后在路由中间件使用
// 导入模块const Koa = require('koa');// 实例化应用const app = new Koa();// 日志中间件async function cookieParser(ctx, next) { const headerCookie = ctx.headers.cookie; ctx.state.cookies = {}; if (headerCookie) { const cookies = headerCookie.split(';'); cookies.forEach((cookie) => { const parts = cookie.split('='); ctx.state.cookies[parts[0]] = parts[1]; // 挂载到 ctx.state.cookies 下 }); } await next();}app.use(cookieParser);// 路由app.use(async (ctx) => { ctx.body = ctx.state.cookies;});// 监听app.listen(10000, () => { console.log('listen on 10000');});//打印结果:{"logged": "1"," logged.sig": "TZf_T485Y1yxOStEuL35vA_qz_U"}
路由函数
async function(ctx, next)app.use((ctx) => { ctx.body = 'Hello World';});
多个路由函数
一般来说,路由函数只有一个,设置响应数据到ctx.body,执行完中间件后,请求终止。但是Koa 支持同一个路由来使用多个路由函数。
Koa框架-路由系统
npm install koa-router --save
// 导入模块const Koa = require('koa');const Router = require('koa-router');// 实例化应用const app = new Koa();// 实例化路由const router = new Router();// 路由定义router.get('/', async (ctx) => { ctx.body = 'Hello World';});router.get('/user', async (ctx) => { ctx.body = 'User';});// 挂载路由中间件app.use(router.routes());app.use(router.allowedMethods());// 监听app.listen(10000, () => { console.log('listen on 10000');});
路由对象
需要实例化之后才能进行配置和挂载
function Router([options])1. options选项,一般使用的选项就是prefix。 prefix:路由前缀。router.method(path, handler);1. method:HTTP请求方法,支持get/post/put/delete/head/options。2. path:路由路径。3. handler:路由处理函数,支持多个。
路由路径
koa-router的路由路径支持字符串和字符串模式。
路由函数
支持多个路由函数处理同一个请求。但 是ctx.params只有koa-router的路由函数才可以访问
路由级别中间件
Koa默认的中间件是应用级别的,所有的请求都会被中间件处理。
路由前缀
路由前缀可以将同一个模块的路由聚合在一起,提供一个统一的URL前缀供客户端访问。
const router = new Router({ prefix: '/user' });
模块化路由
Koa使用模块化路由的步骤非常简单:
- 独立文件中实现路由逻辑。
- 入口文件中挂载路由。