目录
一、初识 Nodejs
1、nodejs是什么?
2、nodejs可以干什么
3、Node.js 环境的安装
4、在 Node.js 环境中执行 JavaScript 代码
二、fs模块
1、fs.readFile()
2、fs.writeFile()
3、文件路径问题
1、__dirname
2、path 模块
二、http 模块
1、http.createServer()
2、server.on()
3、server.listen()
(1)req 是请求对象,包含了与客户端相关的数据和属性
4、简单路由演示
三、模块化
1、加载模块
2、模块作用域
3、向外共享模块作用域中的成员
(1)module 对象
(2)module.exports
(3)exports
(4)CommonJS 规范
四、npm 与 包
1、安装包
2、切换下包镜像源
3、nrm
4、安装完包
5、项目包--dependencies devDependencies
6、全局包
7、i5ting_toc
8、规范的包结构
9、开发属于自己的包(day3里面的代码)
1.初始化包的基本结构
2.初始化package.json
3.在index.js中定义相关方法(写相关方法)
4.编写包的说明文档
5.发布包
6.删除已发布的包
8、卸载包
9、模块加载机制
五、Express
1.安装
2.创建基本的 Web 服务器
3.监听 GET 请求
4.监听 POST 请求
5.把内容响应给客户端
6.获取 URL 中携带的查询参数
7.获取 URL 中的动态参数
8.托管静态资源
9.nodemon
六、Express 路由
1.普通路由
2.模块化路由
七、Express 中间件
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
注意:
1、浏览器是 JavaScript 的前端运行环境。
2、Node.js 是 JavaScript 的后端运行环境。
3、Node.js 中无法调用 DOM 和 BOM等浏览器内置 API。
4、JavaScript 基础语法 + 浏览器内置 API(DOM + BOM)+ 第三方库(jQuery、art-template 等)
5、JavaScript 基础语法 + Node.js 内置 API 模块(fs、path、http等)+ 第三方 API 模块(express、mysql 等)
基于 Express 框架 (opens new window),可以快速构建 Web 应用
基于 Electron 框架 (opens new window),可以构建跨平台的桌面应用
基于 restify 框架 (opens new window),可以快速构建 API 接口项目
读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
https://nodejs.org/en/
LTS 为长期稳定版
①打开终端 ②输入node要执行的js文件的路径
fs模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
引用fs:
const fs = require('fs')
options 代表编码格式读取
fs.readFile(path[, options], callback)
const fs = require('fs')
// 示例 读取成功 err=null 读取失败 data=undefined
fs.readFile('./files/11.text','utf8',function(err,data){
if(err){
//读取失败err是个obj
return console.log('读取失败'+err)
}
console.log(data)
})
没有该文件就创建,但是不能创建目录 写入会覆盖原文件
// options 默认值utf8
fs.writeFile(path,data[,options],callback)
// 示例 写入失败 err为错误对象,写入成功 err=null
fs.writeFile('./files/11.text','hello',function(err){
// 如果文件写入成功,则 err 的值等于 null
if(err){
// 如果文件写入失败,则 err 的值等于一个 错误对象
return console.log('写入失败'+err)
}
})
表示当前 js
的目录 表示当前文件所处的目录
path.join() // 将多个路径片段拼接
path.basename(path[,ext]) // 从路径字符串中取出文件名
path.extname(path) // 从路径字符串中取出文件扩展名
const path = require('path')
// 演示 注意../ 会抵消前面的路径
const pathStr = path.join('/a','/b/c','../','/d','/e')
console.log(pathStr) // \a\b\d\e
const pathStr2 = path.join(__dirname,'./files/1.txt')
const fpath = '/a/b/c/index.html'
var fullName = path.basename(fpath) // index.html
var fullName2 = path.basename(fpath,'.html') // index.html
var fext = path.extname(fpath) // .html
(1)什么是http模块回顾:
什么是客户端、什么是服务器?
在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。
http模块是Node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。
(2)服务器和普通电脑的区别在于,
服务器上安装了web服务器软件,例如:IIS、Apache等。通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器。在Node.js中,我们不需要使用IIS、Apache等这些第三方web服务器软件。因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。
在Node.js中,我们不需要使用IIS、Apache等这些第三方web服务器软件。因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。
(3) IP地址
IP地址就是互联网上每台计算机的唯一地址,
互联网中每台Web服务器,都有自己的IP地址,例如:大家可以在Windows的终端中运行pingwww.baidu.com命令,即可查看到百度服务器的IP地址。
在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入127.0.0.1这个IP地址,就能把自己的电脑当做一台服务器进行访问了。
(4)域名和域名服务器
尽管IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址。
IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IP地址和域名之间的转换服务的服务器。
注意:
①单纯使用IP地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。
②在开发测试期间,127.0.0.1对应的域名是localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。
(5)端口号
计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务进行处理。
注意:
①每个端口号不能同时被多个web服务占用。
②在实际应用中,URL中的80端口可以被省略。
(6)创建web服务器的基本步骤
①导入http模块
②创建web服务器实例
③为服务器实例绑定request事件,监听客户端的请求
④启动服务器
const http = require('http')
创建web服务器实例
const server = http.createServer()
为服务器实例绑定 request 事件,监听客户端发送的请求
server.on('request',(req,res)=>{
console.log(req.url) // 客户端请求地址
console.log(req.method) // 客户端请求方法
// 防止中文乱码,设置响应头
res.setHeader('Content-type','text/html; charset=utf-8')
res.end('Hello World') // 给客户端发送指定内容并结束这次请求
})
启动web服务器监听端口 80端口可省略 http://127.0.0.1
server.listen(80, (req, res) => {})
req.url 是客户端请求的 URL 地址
req.method 是客户端请求的 method 类型
(2)res
调用 res.end(‘’) 方法,向客户端响应一些内容
设置 Content-Type 响应头,防止中文乱码-- res.setHeader('Content-Type', 'text/html; charset=utf-8')
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// req 是请求对象,包含了与客户端相关的数据和属性
// 获取请求的 url 地址
const url = req.url
// 设置默认响应内容
let content = '404 Not found!'
// 判定路径设置返回内容
if (url === '/' || url === '/index.html') {
content = '首页
'
} else if (url === '/about.html') {
content = '关于页面
'
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 调用 res.end() 方法,向客户端响应一些内容
res.end(content)
})
server.listen(80, () => {
console.log('服务器启动:'+'http://127.0.0.1')
})
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
①提高了代码的复用性
②提高了代码的可维护性
③可以实现按需加载
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
①内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
②自定义模块(用户创建的每个 .js 文件,都是自定义模块)
③第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
require()
// 加载内置模块
const fs = require('fs')
// 加载用户的自定义模块
const custom = require('./custom.js')
// 加载第三方模块
const moment = require('moment')
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
防止了全局变量污染的问题
简称暴露
每个 .js 自定义模块中都有一个 module 对象,导入模块时,其实是导入module.exports指向的对象。默认值为 {}
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
//test.js
// 在一个自定义模块中,默认情况下, module.exports = {}
const age = 20
// 向 module.exports 对象上挂载 username 属性
module.exports.username = 'zs'
// 向 module.exports 对象上挂载 sayHello 方法
module.exports.sayHello = function() {
console.log('Hello!')
}
module.exports.age = age
//使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。
// 让 module.exports 指向一个全新的对象
module.exports = {
nickname: '小黑',
sayHi() {
console.log('Hi!')
}
}
//index.js
const custom = require('./test')
console.log(custom)
简化 module.exports。默认情况下 exports 和 module.exports 指向同一个对象。最终结果以 module.exports 指向的对象为准
每个模块内部,module变量代表当前模块。
module变量是一个对像,它的exports属性(即module.exports)是对外的接口。
加载某个模块,其实是加载该模块的module.exports属性。require(0方法用于加载模块
Node.js 中第三方模块又叫做 包
。
包是由第三方个人或团队开发出来,免费开源包共享平台www.npmjs.com
对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。https://registry.npmjs.org
注意:
从https://www.npmjs.com/网站上搜索自己所需要的包
从https://registry.npmjs.org/服务器上下载自己需要的包
案列:npm包的优点
不用npm包:去转时间格式化的话我们需要自己写一个js再引入调用
用npm包:
①使用 npm 包管理工具,在项目中安装格式化时间的包 moment
②使用 require() 导入格式化时间的包
③参考moment 的官方API文档对时间进行格式化
//npm install moment
// 1. 导入需要的包
// 注意:导入的名称,就是装包时候的名称
const moment = require('moment')
//2.参考moment官方API文档调用对应的方法,对时间进行格式化
//调用moment()方法,得到当前时间
//针对当前的时间调用format()方法按照指定格式格式化
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
# 默认安装最新
npm install 包名称
npm i 包名称
# 安装多个
npm i 包名称 包名称
# 指定安装版本
npm install 包名称@x.xx.x // x.xx.x为版本
# 第一个x代表大版本,第二个代表功能版本,第三个代表 Bug 修复版本
下载包速度慢,可以使用镜像服务器
# 查看当前的下包镜像源
npm config get registry
# 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
# 检查
npm config get registry
为了更方便的切换下包的镜像源,我们可以安装 nrm
,可快速查看和切换镜像源
# 通过 npm 包管理器,将 nrm 安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源 *号代表目前使用的镜像源
nrm ls
# 将下包镜像源切换为淘宝镜像
nrm use taobao
初次装包完成后,在项目文件夹下多一个叫做 node modules 的文件夹和 package-lock.json 的配置文件。不要手动修改里面的东西,npm包管理工具会自动维护。
node modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包。
package-lock.json 配置文件用来记录 node modules 目录下的晦一个包的下载信息,例如包的名字、版本号、下载地址等。
(1)dependencies
作用:在 package.json
中记录核心依赖包信息 项目开发阶段和项目上线都需要用到
正常安装默认为核心依赖包
(2) devDependencies
作用:在 package.json
中记录开发依赖包信息
如果某些包只在项目开发阶段用到,在项目上线以后不会用到,建议把这些包记录在 devDependencies
中。
# 安装到 devDependencies
npm i 包名称 -D
npm install 包名称 --save-dev
在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包。全局包会被安装到C:\Users\用户目录\AppData\Roaming\npm\node_modules目录下。
1 npm i 包名 -g //安装全局包
2 npm uninstall 包名 -g //卸载全局包
注意:
①只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。
②判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可。
i5ting_toc是一个可以把md文档转为html页面的小工具,使用步骤如下:
npm install -g i5ting_toc
i5ting_toc -f 要转换额md路径 -o
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合以下3点要求:
①包必须以单独的目录而存在
②包的顶级目录下要必须包含package.json这个包管理配置文件
③package.json中必须包含name,version,main这三个属性,分别代表包的名字、版本号、包的入口。注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:https://yarnpkg.com/zh-Hans/docs/package-json
需要实现的功能 ①格式化日期②转义HTML中的特殊字符③还原HTML中的特殊字符
步骤:
①新建itheima-tools文件夹,作为包的根目录
②在itheima-tools文件夹中,新建如下三个文件:
package.json(包管理配置文件)
index.js(包的入口文件)
README.md(包的说明文档)
----index.js案例
---README案列
注意去npm官网检查下name是否被占用
{
"name": "itheima-tools",
"version": "1.1.0",
"main": "index.js",
"description": "提供了格式化时间、HTMLEscape相关的功能",
"keywords": [
"itheima",
"dateFormat",
"escape"
],
"license": "ISC"
}
关于更多license许可协议相关的内容,可参考https://www.jianshu.com/p/86251523e898
(1)登录npm账号:npm账号注册完成后,可以在终端中执行npm login命令,依次输入用户名、密码、邮箱后,即可登录成功。(nrm ls 可以检查)
注意:在运行npm login命令之前,必须先把下包的服务器地址切换为npm的官方服务器。否则会导致发布包失败!
(2)把包发布到npm上:将终端切换到包的根目录之后,运行npm publish命令,即可将包发布到npm上(注意:包名不能雷同)。
运行npm unpublish包名--force命令,即可从npm删除已发布的包。
注意:①npm unpublish命令只能删除72小时以内发布的包②npm unpublish删除的包,在24小时内不允许重复发布③发布包的时候要慎重,尽量不要往npm上发布没有意义的包!
# 会自动处理 package.json
npm uninstall 包名称
npm uni 包名称
模块在第一次加载后会被缓存。这也意味着多次调用require0不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块加载优先级最高。
加载自定义模块时,应指定 ./ 或者 …/ 开头的路径,如果没有将被认为内置模块或者第三方模块。
如果加载自定义模块时省略扩展名,则会以 .js 、.json 、.node 进行尝试加载,如果没有则加载失败。
加载第三方模块时,会先从父级目录寻找 node_modules ,如果父级没有则依次往上寻找。
把目录当作模块标识符加载时,会在被加载的目录下查找 package.json 并寻找 main 属性,作为加载的入口。如果没有或者无法解析,则会加载目录下的 index.js 文件。
官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
Express 的中文官网: http://www.expressjs.com.cn/
Express可以做什么?
对于前端程序员来说,最常见的两种服务器,分别是:
Web 网站服务器:专门对外提供 Web 网页资源的服务器。
API 接口服务器:专门对外提供 API 接口的服务器。
使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器。
npm i express
npm i [email protected]
// 导入express
const express = require('express')
// 创建 web 服务器
const app = express()
// 调用 app.listen(端口号,启动成功后的回调函数),启动服务器
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
// req:请求对象(包含了与请求相关的属性和方法)
// res:响应对象(包含了与响应相关的属性和方法)
app.get('请求url',function(req,res){
// 处理函数
})
// req:请求对象(包含了与请求相关的属性和方法)
// res:响应对象(包含了与响应相关的属性和方法)
app.post('请求url',function(req,res){
// 处理函数
})
res.send()
app.get('/user',(req,res)=>{
res.send({name:'lhd',age:'19'})
})
req.query
app.get('/',(req,res)=>{
//req.query默认是一个空对象
//客户端使用?name-zs&age=20 这种查询字符串格式,发送到服务器参数
//可以通过req.query对象访问到,例如:
//req.query.name req.query.age
console.log(req.query)
})
通过 req.params 对象,可以访问到 URL 中,通过 :
匹配到的动态参数
// 访问 http://127.0.0.1/user/1874
app.get('/user/:id',(req,res)=>{
res.send(req.params) // 返回 {id:'1874'}
})
// 访问 http://127.0.0.1/user/1874/lhd
app.get('/user/:id/:name',(req,res)=>{
res.send(req.params) // 返回 {id:'1874',name:'lhd'}
})
通过 express.static() 创建静态资源服务器。可将图片、css
文件、js
文件对外开放访问。
// 将 public 目录下资源提供访问。多个目录请多次调用
// 访问地址:http://localhost:端口号/xxx.html
app.use(express.static('./public'))
// 挂载路径前缀
// 访问地址:http://localhost:端口号/public/xxx.html
app.use('/public',express.static('./public'))
类似热更新的工具
编写调试 Node.js 项目时,如果修改了代码则需要重新启动项目才可以使用,使用 nodemon
工具修改代码时可以自动帮我们重启项目,方便开发
(1)安装
// 安装为全局可用工具
npm i nodemon -g
(2)使用
// 原本启动
node app.js
// nodemon 启动
nodemon app.js
在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express 中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数。
// METHOD:请求方式,PATH:请求路径,HANDLER:处理函数
app.METHOD(PATH,HANDLER)
挂载到app上
// 示例:使用 get 请求返回 get,使用 post 返回 post
app.get('/',(req,res)=>{
res.send('This is GET request')
})
app.post('/',(req,res)=>{
res.send('This is POST request')
})
为了方便对路由进行模块化管理,Express 不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
步骤:
1.创建路由模块对应的 .js 文件
2.调用 express.Router() 创建路由对象
3.向路由对象上挂载具体的路由
4.使用 module.exports 向外共享路由对象
5.使用 app.use() 注册路由模块
演示:
// ./router/user.js
var express = require('express') // 导入express
var router = express.Router() // 创建路由对象
// 挂载具体路由
router.get('/user/list',(req,res)=>{
res.send('Get user list')
})
router.post('/user/add',(req,res)=>{
res.send('Add new user')
})
module.exports = router // 向外导出路由对象
// ./app.js
const express = require('express')
const app = express()
const userRouter = require('./router/user.js') // 导入路由模块
//app.use() 函数的作用,就是来注册全局中间件
app.use(userRouter) // 注册路由模块
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
// 访问地址: http://127.0.0.1/user/list
为路由模块添加前缀
// 访问地址: http://127.0.0.1/api/user/list
app.use('/api',userRouter)
请求到达 Express 服务器之后,可连续调用多个中间件,从而对这次请求进行预处理。
和vue的路由守卫一样,next()代表放行
注意事项:
1.路由之前注册中间件
2.客户端发送过来的请求,可以连续调用多个中间件进行处理
3.执行完中间件的业务代码之后,记得next()函数
4.为了防止代码混乱,使用完了next()函数之后不要写其他逻辑代码了
5.连续调用多个中间件时,多个中间件之间,共享res和res对象
本质上就是一个 function 处理函数,格式如下:
中间件函数的形参列表中,必须包含next参数。而路由处理函数中只包含req和res。
next() 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或者路由。
// mw 指向的就是一个中间件函数
const mw = function(req.res,next){
console.log('这是个简单的中间件函数')
next()
}
只要有请求到达服务器,必先经过mw中间件函数处理。相对于vue前置守卫,拦截器
// mw 指向的就是一个中间件函数
const mw = function(req.res,next){
console.log('这是个简单的中间件函数')
next()
}
// 全局生效的中间件
app.use(mw)
//简化
app.use(function(req,res,next){
console.log('这是给简单的中间件函数')
next()
})
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
// 示例:每个请求都需要获取时间
app.use((req, res, next) => {
req.startTime = Date.now()
next()
})
app.get('/user',(req,res)=>{
res.send('time:'+req.startTime)
})
按注册顺序排,且必须在路由上方。多个中间件共享req和res。
// 完整示例
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({ info: '请求了服务器', method: req.method })
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
const mw = function(req,res,next){
console.log('中间件函数')
next()
}
app.get('/',mw,(req,res)=>{
res.send('Hellow World')
})
app.get('/user',(req,res)=>{
res.send('Hellow World')
})
// mw1与mw2 为中间件函数,两种方式等价
app.get('/',mw1,mw2,(req,res)=>{ xxx })
app.get('/',[mw1,mw2],(req,res)=>{ xxx })
(1)应用级别的中间件
通过 app.use() 或者 app.get() 或 app.post() ,绑定到 app 实例上的中间件。
(2)路由级别的中间件
绑定到 express.Router() 实例上的中间件。
用法与应用级别中间件一致。
var app = express()
var router = express.Router()
router.use((req,res,next)=>{
console.log('路由级别中间件')
next()
})
app.use('/',router)
(3)错误级别的中间件
专门用来捕获项目中发生的异常错误,从而防止项目崩溃。
格式:处理函数中必须有4个形参 (err,req,res,next)
特点:必须注册在所有路由之后
// 示例
app.get('/',(req,res)=>{
throw new Error('服务器出错') // 错误发生后,下面的语句无法执行
res.send('Hellow World') // 无法响应
})
app.use((err,req,res,next)=>{
console.log('发生了错误'+err.message)
res.send('Error!'+err.message) // 捕获错误,向客户端响应
})
(4)Express内置中间件
[email protected]版本开始,内置了3个常用的中间件
1、express.static 快速托管静态资源的内置中间件 (无兼容性)
2、express.json 解析 JSON 格式的请求体数据 (仅在4.16.0 +可用)
3、express.urlencoded 解析 URL-encoded 格式的请求体 (仅在4.16.0 +可用)
// 配置解析 application/json 格式数据的内置中间件
app.use(express.json())
// 配置解析 application/x-www-form-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({ extended:false }))
演示:
const express = require('express')
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过express.json()这个中间件,解析表单中的JSON格式的数据
app.use(express.json())
// 通过express.urlencoded()这个中间件,来解析表单中的url-encoded格式的数据
app.use(express.urlencoded({ extended:false }))
app.post('/user',(req,res)=>{
// 在服务器,可以使用req.body这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined
console.log(req.body)
res.send('ok')
})
app.post('/book',(req,res)=>{
// 在服务器端,可以通过req,body来获取JSON格式的表单数据和url-encoded格式的数据
console.log(req.body)
})
(5)第三方中间件
非官方内置,由第三方开发出来的中间件叫第三方中间件,可用按需加载和配置
例如:在 [email protected] 之前的版本中,经常使用 body-parser 这个第三方中间件来解析请求体数据。
使用步骤如下:
使用 npm i body-parser 安装
使用 require 导入中间件
使用 app.use() 注册并使用中间件
演示:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false }))
// 注意区分内置的app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
需求:
自己手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据。实现步骤:
①定义中间件
②监听req的data事件
③监听req的end事件
④使用querystring模块解析请求体数据
⑤将解析出来的数据对象挂载为req.body
⑥将自定义中间件封装为模块
自定义一个解析请求体数据的中间件并使用
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
// ①定义中间件----------------这是解析表单数据的中间件
app.use((req, res, next) => {
/**如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,
每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。 */
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// ②监听req的data事件
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// ③监听req的end事件
// 3. 监听 req 的 end 事件
req.on('end', () => {
//④使用querystring模块解析请求体数据
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
// ⑤将解析出来的数据对象挂载为req.body
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
测试结果:
// test.js
function test(req,res,next){ /*中间件处理函数*/ }
module.exports = test // 暴露出去
// app.js
const test = require('./test') // 导入自定义中间件
app.use(test) // 注册
//15.index.js
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 导入路由模块
const router = require('./16.apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
//16.apiRouter.js
const express = require('express')
const router = express.Router()
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query, // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body,
})
})
// 定义 DELETE 接口
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功',
})
})
module.exports = router
(八、九为了解决接口跨域问题 )
cors:主流解决方案。推荐
JSONP:有缺陷的解决方案,只支持GET请求
安装
npm i cors
使用
// app.js
const cors = require('cors') // 导入
app.use(cors()) // 配置中间件,需在路由之前
响应头部可以携带一个 Access-Control-Allow-Origin
字段,语法如下
// origin 参数的值指定了只允许访问该资源的外域 url
Access-Control-Allow-Origin:|*
其中,origin参数的值指定了允许访问该资源的外域URL。
例如,下面的字段值将只允许来自http://itcast.cn的请求:
如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:
默认情况下,CORS 仅支持客户端向服务器发送如下 9 个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Sava-Data、Viewport-width、Width、Content-Type(值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外请求头进行声明,否则请求失败。
示例:
// 允许客户端额外向服务器发送Content-Type请求头和X-Custom-Header请求头
// 注意:多个请求头之间使用英文的逗号进行分割
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','*')
同时满足以下两大条件的请求,就属于简单请求:
请求方式:GET、POST、HEAD三者之一
HTTP头部信急不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(只有三个值 application/x-www-form-urlencoded 、multipart/form-data、text/plain)
只要符合以下任何一个条件的请求,都需要进行预检请求:
请求方式为GET、POST、HEAD之外的请求Method类型
请求头中包含自定义头部字段
向服务器发送了application./json格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是香允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的情求,并且携带真实数据。
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
概念:浏览器端通过