title: Node.js
date: 2022-06-30 21:26:11
tags:
Node.js是一个基于Chrome V8引擎的JavaScript运行环境
注意:
Node.js作为-个JavaScript的运行环境,仅仅提供了基础的功能和API。然而,基于Node.js提供的这些基础能,很多强大
的工具和框架如雨后春笋,层出不穷,所以学会了Node.jis ,可以让前端程序员胜任更多的工作和岗位:
①基于Express框架(http://www.expressjs.com.cn/) ,可以快速构建Web应用
②基于Electron框架(https://electronjs.org/) , 可以构建跨平台的桌面应用
③基于restify框架http://restify.com/) ,可以快速构建API接口项目
④读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
转为开发人员设计,用于实现人机交互的一种方式。
常用的终端命令:
命令 | 作用 |
---|---|
node -v | 查看node.js版本 |
cd / | 进入根目录 |
cd … | 去到上一层目录 |
f: | 进入F盘 |
cls | 清空终端 |
终端快捷键:
fs模块是Node.js官方提供的,用来操作文件的模块,它提供了一系列的方法和属性。用来满足用户对文件的操作需求。
例如:
如果在javaScript代码中,使用fs模块来操作文件,则需要使用如下方式导入他:
使用fs.readFile()方法,可以读取指定文件中的内容,语法格式如下:
参数解读:
示例代码:
// 导入fs模块 来操作文件
const fs = require('fs');
// 调用fs.readFile()方法读取文件
// 参数1:读取文件的存放路径
// 参数2:读取文件时候采用的编码格式 一般默认指定utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/11.txt', 'utf8', function(err, dataStr) {
// 2.1 打印失败的结果
//如果读取成功,则err的值为null
//如果读取失败,则err的值为错误对象 dataStr的值为undefined
console.log(err);
console.log('----------');
// 2.2 打印成功的结果
console.log(dataStr);
});
成功:
失败:
优化写法:
使用fs.writeFile()方法,可以向指定文件中写入内容。语法格式如下:
参数解读:
代码示例:
// 导入fs文件系统模块
const fs = require('fs');
// 调用fs.writeFile()方法,写入文件内容
//参数1:表示文件的存放路径
//参数2:表示要写入的内容
//参数3:省略
//参数4:回调函数
fs.writeFile('f:/files/2.txt', 'abc', function(err) {
// 2.1如果文件写入成功,则err的值等于null
// 2.2如果文件写入失败,则err的值等于一个错误对象
console.log(err);
});
优化写法:
有时候出现路径拼接错误是因为提供了./或者…/开头的相对路径导致
解决方法:
- 提供一个完整路径
- 但是移植性差,不利用代码维护
- 使用__dirname
- __dirname代表的是当前目录路径
fs.readFile(__dirname + '/files/2.txt', 'utf8', function(err, dataStr) {
if (err) {
return console.log('读取失败' + err.message);
}
console.log('读取成功' + dataStr);
});
path模块是Node.js官方提供的,用来处理路径的模块.它提供了一系列的方法和属性,用来满足用户对路径的处理需求.
例如:
- path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串
- path.basename()方法,用来从路径字符串中,将文件名解析出来
如果要在JavaScript代码中使用path模块来处理路径,则需要使用如下方式导入他:
const path = require('path');
使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:
path.join([...paths])
参数解读:
- …paths< string >路径片段的序列
- 返回值:< string >
代码示例:
使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:
path.basename(path[,ext])
参数解读:
- path
必选参数,表示一个路径的字符串
- ext
可选参数,表示文件扩展名
- 返回:
表示路径中的最后一部分
示例代码:
使用path.extname()方法,可以获取路径中的扩展名部分
什么是客户端?什么是服务器?
- 在网络节点中,负责消耗资源的电脑,叫做客户端
- 负责对外提供网络资源的电脑,叫做服务器
http模块是Node.js官方提供的,用来创建web服务器的模块,通过http模块提供的http.createServer()方法,就能方便把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务。
如果希望使用http模块创建Web服务器,则需要导入他:
const http = require('http');
服务器和普通电脑的区别在于,服务器上安装了web服务器插件。例如:IIS,Apache等。通过安装这些服务器软件,就能把一台普通电脑变成一台web服务器。
在node.js中我们不需要使用IIS、Apache等这些第三方web服务器软件,因为我们可以基于Node.js提供的http模块,通过几行代码就能轻松手写一个服务器软件,从而对外提供web服务。
IP地址就是互联网上每台计算机的唯-地址,因此IP地址具有唯一性。 如果把“个人电脑”比作"-台电话”,那么"IP地址"就相当于“电话号码”,只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。
IP地址的格式:通常用”点分十进制”表示成(a.b.c.d) 的形式,其中, a,b,c,d 都是0~255之间的十进制整数。例如:用点分十进表示的IP地址(192.168.1.1)
注意:
尽管IP地址能够唯-地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址
IP地址和域名是一对应的关系, 这份对应关系存放在一种叫做域名服务器(DNS, Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IP地址和域名之间的转换服务的服务器。
注意:
计算机中的端口号,就好像是现实生活中的门牌号一样。 通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务都对应-个唯的端口号. 客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务进行处理。
注意:
创建web服务器的基本步骤
- 导入http模块
- 创建web服务器实例
- 为服务器实例绑定require事件,监听客户端的请求
- 启动服务器
如果希望在自己电脑上创建一个web服务器,从而对外提供web服务,则需要导入http模块:
const http = require('http');
调用**http.sreateServer()**方法,即可快速创建一个web服务器实例:
const server = http.createServer();
为服务器实例绑定request事件,即可监听客户端发送过来的网络请求:
// 使用服务器实例的.on()方法,为服务器绑定一个request事件
sever.on('request',(req,res)=>{
// 只要有客户端来请求我们的服务器,就会触发request事件,从而调用这个事件处理函数
console.log('Someone visit our web server');
});
只要服务器接收了客户端的请求,就会调用server.on()为服务器绑定的request事件处理函数。如果想在事件处理函数中,访问与客户端相关的数据或者属性,就可以使用如下方法:
server.on('request',(req)=>{
// req 是请求对象,它包含了与客户端相关的数据和属性,例如:
// req.url 是客户端请求的url地址
// req.method 是客户端的method请求类型
const str = `Your request url is ${req.url},and request method is ${req.method}`;
console.log(str);
});
在服务器的request使事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下方式:
server.on('request', (req, res) => {
//req是请求对象,包含了与客户端相关的数据和属性
// req.url是客户端请求的url地址
const url = req.url;
// req.method是客户端请求的method类型
const method = req.method
const str = `Your request url is ${url},and request method is ${method}`;
console.log(str);
// 调用res.end()方法向客户端响应一些内容
res.end(str);
});
当调用res.end()方法,向客户端发送中文内容时候,就会出现乱码问题,此时,需要手动设置内容的编码格式:
res.setHeader('Content-Type', 'text/html;charset=utf-8');
server.on('request', (req, res) => {
// 发送内容包含中文
const str = `你请求的url地址是${req.url},请求的method类型是${req.method}`;
// 调用res.setHeader('Content-Type', 'text/html;charset=utf-8')方法
res.setHeader('Content-Type', 'text/html;charset=utf-8');
// 调用res.end()方法向客户端响应一些内容
res.end(str);
});
调用服务器实例的.listen()方法,即可启动当前的web服务器实例:
// 调用server.listen(端口号,callback回调函数)方法,即可启动web服务器
server.listen(80,()=>{
console.log('http server running at http//127.0.0.1');
});
核心实现步骤:
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
// 1. 获取请求的url地址
const url = req.url;
// 2. 设置默认的响应内容为404 Not found
let content = `404 Not Found
`;
// 3. 判断用户请求的是否为/或/index.html首页
// 4. 判断用户请求的是否为/about.html关于页面
if (url === '/' || url === '/index.html') {
content = `首页
`;
} else if (url === '/about.html') {
content = `关于页面
`;
}
// 5. 设置Content-Type响应头,防止中文乱码
res.setHeader('Content-Type', 'text/html; charset=utf-8');
// 6. 使用res.end( )把内容响应给客户端
res.end(content);
});
// 4. 启动服务器
server.listen(8080, function() {
console.log('server running at http://127.0.0.1:8080');
});
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程。对整个系统来说,模块是可以组合,分解,更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆分成独立并相互依赖的多个小模块:
把代码进行模块化拆分的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
模块化规范就是对代码进行模块化的拆分与组合时,需要遵循的那些规则。
例如:
模块化的好处:大家都遵循同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
Node.js中根据模块来源的不同,将模块分为了3大类,分别是:
使用强大的require()方法,可以加在需要的内置模块,用户自定义模块,第三方模块进行使用。例如:
**注意:**使用require()方法加载其他模块时候,会执行被加载模块中的代码。
和函数作用域类似,在自定义模块中定义的变量,方法。等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域;
防止全局变量污染的问题
当两个js文件中有相同的变量,就会造成全局变量污染
在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息,打印如下:
在自定义模块中。可以使用module.exports对象,将模块内的成员共享出去,供外界使用。
外界用**require( )**方法导入自定义模块时候。得到的就是module.exports所指向对象。
如果想要公开自定义模块中的一部分,可以直接为
module.exports
添加属性或者方法
使用require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准。
由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports 对象。默认情况下,exports 和module.exports指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准。
注意:
最终共享的结果,还是以module.exports指向的对象为准。
如果module.exports这个老六偷偷指向了另一个对象,则以它为准
时刻谨记,require()模块时,得到的永远是module exports指向的对象:
Node.js遵循了CommonJs模块化规范。CommonJS规定了模块的特性和各模块之间相互依赖。
CommonJS规定:
Node.js中的第三方模块又叫做包。
就像电脑和计算机指的是相同的东西。第三方模块和包指的是同一个概念。只不过叫法不同
不同于Node.js中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js中的包都是免费开源的,不需要付费即可免费下载使用。
由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发的时候,效率低。包是基于内置模块封装出来的,提供了更高级,更方便的API,极大的提高了开发效率。
包和内置模块之间的关系,类似于jQuery和浏览器API之间的关系。
国外有家IT公司,叫做npm, Inc.这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ .它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!
到目前位置,全球约1100多万的开发人员,通过这个包共享平台,开发并共享了超过120多万个包供我们使用。
npm, Inc. 公司提供了一个地址为https://registy.npmjs.org/的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。
注意:
npm, Inc.公司提供了-一个包管理工具,我们可以使用这个包管理工具,从https://registry.npmis org/服务器把需要的包下载到本地使用。
这个包管理”工具的名字叫做Node Package Manager (简称npm包管理工具),这个包管理工具随着Node.js的安装包-起被安装到了用户的电脑上。
大家可以在终端中执行npm-v命令,来查看自己电脑上所安装的npm包管理工具的版本号:
npm install moment
或者npm i moment
命令初次装包完成后,在项目文件夹下多-个叫做 node_ modules的文件夹和package-lock.json的配置文件。
其中:
注意:程序员不要手动修改node_ modules 或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们。
默认情况下,使用npm install命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过@符号指定具体的版本,例如:
包的版本号是以”点分十进制”形式进行定义的,总共有三位数字,例如 2.24.0
其中每一位数字所代表的的含义如下:
第1位数字:大版本
第2位数字:功能版本
第3位数字: Bug修复版本
版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。
npm规定,在项目根目录中,必须提供-个叫做 package.json的包管理配置文件。用来记录与项目有关的一一些配置信息。例如:
package.json文件中,有一个dependencies节点,专门用来记录您使用npm install命令安装了哪些包。
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快捷创建package.json这个包管理配置文件:
注意:
①上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称-定要使用英文命名,不要使用中文,不能出现空格。
②运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
在实际上开发中,上传至git的前提下会自动屏蔽掉node_modules文件中的所有第三方包。通过.gitignore
配置文件忽略掉该文件。避免项目文件过大。
而在拿到项目的时候,可以通过npm install
或者npm i
指令安装所有你需要的第三方包。
可以运行npm uninstall
命令,来卸载指定的包:
注意:npm uninstall 命令执行后,会把卸载的包,自动从package.json的dependencies中移除掉
如果某些包之在项目开发中会用到,在项目上线后不会用到,则建议把这些包记录到devDependencies节点中。与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies节点中。
使用如下命令:npm i 包名 -D
淘宝在国内搭建了-一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。
扩展:
镜像是一种文件存储形式。一个磁盘上的数据在另一个磁盘上存在完全相同的副本即为镜像
下包的镜像源,指的是下包的服务器地址
npm config get registry
查看当前下包镜像源npm config set registry=https://registry.npm.taobao.org/
将下包的镜像源切换为淘宝镜像源为了更方便的切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源。
npm i nrm -g
通过npm包管理器,将nrm安装为全局可用的工具nrm ls
查看所有可用镜像源nrm use taobao
将下包的镜像源切换为淘宝镜像那些被安装到项目的node _modules目录中的包,都是项目包。
项目包又分为两类,分别是;
在执行npm install
命令时,如果提供了-g 参数,则会把包安装为全局包。
全局包会被安装到C:\Users\用户目录\AppData\Roaming\npm\node_ modules 目录下。
注意:
iting_toc是一个可以把md文档转为html页面的小工具,使用步骤如下:
在清楚了包的概念以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合以下3点要求:
注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考网站
在符合规范包结构的前提下就可以自己制作一个包并发布到npm网站。
入口文件写入到index.js
文件下,方法文件放入src文件中
...
运算符展开这两个对象在npm官网注册好账号后在终端登录账户
使用指令npm login
依次输入用户名 - 密码 - 邮箱
注意:镜像服务器必须是npm官方地址
运行npm unpublish 包名 --force
命令,即可从npm删除已发布的包。
注意:
npm unpublish
命令只能删除72小时以内发布的包npm unpublish
删除的包,在24小时内不允许重复发布官方给出的概念: Express 是基于Node.js平台,快速、开放、极简的Web开发框架。
通俗的理解: Express 的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。
Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。
Express的中文官网:. http://www.expressjs.com.cn/
在项目所处目录中,使用npm install [email protected]
安装指定版本或者npm install express --save
安装最新版本
// 1.导入express
const express = require('express');
// 2.创建web服务器
const app = express();
// 3.启动web服务器
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
})
通过app.get()
方法,可以监听客户端的GET请求,具体的语法格式如下:
app.get('请求URL',function(req,res){/*处理函数*/});
通过app.post()
方法,可以监听客户端的POST请求,具体语法如下:
app.post('请求URL',function(req,res){/*处理函数*/});
通过res.send()
方法,可以把处理好的内容,发送给客户端:
app.get('/user',(req,res)=>{
// 向客户端发送JSON请求
res.send({
name:'zs',
age:20,
gender:'男'
})
})
app.post('/user',(req,res)=>{
// 向客户端发送文本请求
res.send('请求成功');
})
通过req.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:
:id并非固定写法,":"才是,id是可变的
// url地址中,可以通过:参数名的形式,匹配动态参数值
// 注意:这里的:id是一个动态的参数
app.get('/user/:id', (req, res) => {
// req.params 是动态匹配到的URL参数,默认也是一个空对象
console.log(req.params);
// 里面存放着通过:动态匹配到的参数值
res.send(req.params);
})
express提供了一个非常好用的函数,叫做express.static()
,通过它,我们可以非常方便地创建一个静态资源服务器。
例如,通过如下代码就可以将public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
app.use(express.static('public'))
注意: Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。
因此,存放静态文件的目录名不会出现在URL中。
当有两个文件都需要开放自己创建静态服务器,可以反复调用express.static()
方法
访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件。优先级总是第一个
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
现在,你就可以通过带有 /public 前缀地址来访问 public 目录中的文件了:
在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动,非常繁琐。
现在,我们可以使用noderaon (https://www.npmjs.com/package/nodemon) 这个工具, 它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
使用指令npm i -g nodemon
下载
通过nodemon 'js文件'
来启动服务器,当更改代码后,就不需要再重启服务器,nodemon会自动重启服务器。
在这里,路由是按键与服务之间的映射关系
在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express中的路由分3部分组成,分别是请求的**类型、请求的URL地址、处理函数,**格式如下:
app.METHOD(PATH,HANDLER)
//匹配GET请求,且请求URL为 /
app.get('/',function(req,res){
res.send('Hello World!');
})
//匹配POST请求,且请求URL为 /
app.post('/',function(req,res){
res.send('Got a POST requset');
})
每当-一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行四配,如果请求类型和请求的URL同时匹配成功,则Express会将这次请求,转交给对应的function函数进行处理。
路由匹配的注意点:
①按照定义的先后顺序进行匹配
②请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
在Express中使用路由最简单的方式,就是把路由挂载到app上,示例代码如下:
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到app.上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:
注意:**app.use( )**函数的作用。就是用来注册全局中间件
类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单:
在处理污水的时候,一般都要经过三个处理环节, 从而保证处理过后的废水,达到排放标准。
处理污水的三个中间环节,就可以叫做中间件。
当一个请求到达Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express的中间件,本质上就是一个function处理函数,Express 中间件的格式如下:
注意:中间件函数的形参列表中,**必须包含next参数。**而路由处理函数中只包含req和res.
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
可以通过如下的方式,定义一个最简单的中间件函数:
const express = require('express');
const app = express();
// 定义一个简单的中间件函数
const mw = function(req, res, next) {
console.log('这是一个简单的中间件函数');
// 把流转关系转交给下一个中间件或路由
next();
}
app.listen(80, () => {
console.log('http://127.0.0.1');
})
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
通过调用app.use(mw);
即可定义一个全局生效的中间件,示例代码如下:
const express = require('express');
const app = express();
//------------------------------------------------------------------
// 定义一个简单的中间件函数
const mw = function(req, res, next) {
console.log('这是一个简单的中间件函数');
// 把流转关系转交给下一个中间件或路由
next();
};
// 将mw注册为全局生效的中间件
app.use(mw);
//------------------------------------------------------------------
app.get('/', (req, res) => {
console.log('调用了/路由');
res.send('Home page');
});
app.get('/user', (req, res) => {
console.log('调用了/user 路由');
res.send('User page');
})
app.listen(80, () => {
console.log('http://127.0.0.1');
})
简化合并写法:
可以使用app.use0连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用,示例代码如下:
const express = require('express');
const app = express();
// 定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第一个全局中间件');
next();
});
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第二个全局中间件');
next();
});
app.get('/', (req, res) => {
res.send('Home page!');
});
app.listen(80, () => {
console.log('http://127.0.0.1');
})
不使用**app.use()**定义的中间件,叫做局部生效的中间件,示例代码如下:
const express = require('express');
const app = express();
// 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个全局中间件');
next();
};
// 创建路由 将参数添加在路由中
app.get('/', mw1, (req, res) => {
res.send('Home page!');
});
app.get('/user', (req, res) => {
res.send('User page!');
});
// 启动服务器
app.listen(80, () => {
console.log('http://127.0.0.1');
});
定义多个局部生效的中间件:
可以在路由中,通过如下两种等价的方式,使用多个局部中间件:
多个中间件之间,共享同- -份req和res.基于这样的特性,我们可以在上游的中间件中,统-为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
为了方便大家理解和记忆中间件的使用,Express 官方把常见的中间件用法,分成了5大类,分别是:
绑定到express.Router0实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router实例上,代码示例如下:
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function 处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。
注意:错误级别的中间件,必须注册在所有路由之后!
自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:
①express.static 快速托管静态资源的内置中间件,例如: HTML文件、 图片、CSS 样式等(无兼容性)
②express.json 解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
③express.urlencoded 解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
express.json实例:
const express = require('express');
const app = express();
// 配置中间件
// 通过express.json()解析表单中的json格式数据
app.use(express.json());
// 创建路由
app.post('/user', (req, res) => {
// 在服务器中可以使用req.body这个属性来接受客户端发送过来的请求体
// 默认情况下,如果不配置 解析表单数据的中间件,默认为undefined
console.log(req.body);
res.send('ok');
})
// 启动服务器
app.listen(80, () => {
console.log('http://127.0.0.1');
});
express.urlencoded实例:
express.urlencoded({ extended: false }) 固定写法
const express = require('express');
const app = express();
// 配置中间件
// 通过express.urlencoded()解析表单中的url-encoded格式的数据
app.use(express.urlencoded({ extended: false }));
// 创建路由
app.post('/book', function(req, res) {
// 在服务器中可以使用req.body这个属性来获取json格式的表单和url-encoded格式数据
console.log(req.body);
res.send('ok');
})
// 启动服务器
app.listen(80, () => {
console.log('http://127.0.0.1');
});
非Express官方内置的,而是由第三方开发出来的中间,叫做第三方中间件,在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在[email protected]之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据,使用步骤如下:
const express = require('express');
const app = express();
// ........
app.listen(80, () => {
console.log('http://127.0.0.1');
})
首先写一个模块化路由并暴露出去
// 模块化路由
const express = require('express');
const router = express.Router();
// 挂载对应的路由
// 向外暴露路由
module.exports = routers;
导入模块化路由并将路由模块挂载到中间件:
const express = require('express');
const app = express();
// 导入路由模块
const router = require('./14.APIrouter');
// 把路由模块注册到app上(中间件)
app.use('/api', router);
app.listen(80, () => {
console.log('http://127.0.0.1');
})
在路由模块 挂载对应的路由:
// 模块化路由模块
const express = require('express');
const routers = express.Router();
// 挂载对应的路由
routers.get('/get', (req, res) => {
// 通过req.query获取客户端通过查询字符串 发送到服务器的数据
const query = req.query;
// 调用res.send()方法,向客户端相应处理的数据
res.send({
status: 0, // 0处理成功,1处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query //需要响应给客户端的数据
});
})
// 向外暴露路由
module.exports = routers;
使用post请求需要配置解析表单数据的中间件:
const express = require('express');
const app = express();
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }));
// 导入路由模块
const routers = require('./14.APIrouter');
// 把路由模块注册到app上(中间件) 并添加前缀'/api'
app.use('/api', routers);
app.listen(80, () => {
console.log('http://127.0.0.1');
})
在路由模块编写POST接口:
// 模块化路由模块
const express = require('express');
const routers = express.Router();
// 挂载对应的路由
// get接口
routers.get('/get', (req, res) => {
// 通过req.query获取客户端通过查询字符串 发送到服务器的数据
const query = req.query;
// 调用res.send()方法,向客户端相应处理的数据
res.send({
status: 0, // 0处理成功,1处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query //需要响应给客户端的数据
});
});
// post接口
routers.post('/post', (req, res) => {
//通过req.body获取请求体中包含的url-encoded格式的数据
const body = req.body;
// 调用res.send()方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body
});
});
// 向外暴露路由
module.exports = routers;
刚才编写的GET和POST接口,存在一个严重问题:不支持跨域请求
解决接口跨域问题的方案主要有两种:
cors是express的一个第三方中间件。通过安装和配置cors中间件。可以很方便的解决跨域问题,使用步骤如下:
npm install cors
安装中间件const cors = require('cors')
导入中间件app.use(cors())
配置中间件const express = require('express');
const app = express();
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }));
//-----------------------------------------------------------
// 一定要在路由之前,配置cors这个中间件,从而解决接口的跨域问题
const cors = require('cors');
app.use(cors());
//-----------------------------------------------------------
// 导入路由模块
const routers = require('./14.APIrouter');
// 把路由模块注册到app上(中间件) 并添加前缀'/api'
app.use('/api', routers);
app.listen(80, () => {
console.log('http://127.0.0.1');
})
CORS (Cross-Origin Resource Sharing,跨域资源共享)由-系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的网源安全策略默认会阻止网页“跨域"获取资源。但如果接口服务器配熏了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制,
响应头部中可以携带一个Access-Control-Allow-Origin字段,其语法如下;
Access-Control-Allow-Origin:
其中,origin参数的值制定了允许访问该资源的外域URL
例如:下面的字段值将只允许来自http://itcast.cn的请求:
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn');
如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求。示例代码如下:
res.setHeader('Access-Control-Allow-Origin',*);
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
Accept、 Accept-L anguage、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、 Width 、Content-Type (值仅限于 text/plain. multipart/form-data、 application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!
res.setHeader('Access-Control-Allow-Headers','Content-Type,x-Custom-Header')
默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。
如果客户端希望通过PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods来指明实际请求所允许使用的HTTP方法。
示例代码如下:
// 只允许 POST GET DELETE HEAD 请求方式
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD');
// 允许所有http请求
res.setHeader('Access-Control-Allow-Methods',*);
客户端在请求CORS接口时,根据请求方式和请求头的不同。可以将CORS的请求分为两大类,分别是:
- 简单请求
- 预检请求
同时满足以下两大类的请求,就属于简单请求:
- 请求方式:GET POST HEAD 三者之一
- HTTP头部信息不超过以下几种字段:无自定义头部字段。Accept. Accept-Language. Content-Language. DPR.Downlink. Save-Data. Viewport-Width. Width . Content-Type (只有三个值applic ation/x-www-form-urlencoded. multipart/form- data. text/plain)
只要符合以下任意一个条件的请求。都需要进行预检请求:
- 请求方式为GET、POST、HEAD之外的请求Method类型
- 请求头中包含自定义头部字段
- 向服务器发送了application/json 格式的数据
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
概念:浏览器端通过标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做JSONP.
特点:
①JSONP 不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
②JSONP 仅支持GET请求,不支持POST、PUT、 DELETE 等请求。
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS的接口。示例代码如下:
①获取客户端发送过来的回调函数的名字
②得到要通过JSONP形式发送给客户端的数据
③根据前两步得到的数据,拼接出一个函数调用的字符串
④把上一步拼接得到的字符串,响应给客户端的标签进行解析执行