//引入fs模块
const fs=require("fs")
const fs=require("fs")
fs.readFile("text1.txt", "utf-8", (err, dataStr) => {
if (err) {
// 成功下,err为null
console.log("读取文件失败", err)
} else {
// 失败情况下dataStr为unefined
console.log(dataStr)
}
})
const fs=require("fs")
// 默认utf-8,有文件就会替换文件的所有内容,没有文件则会创建一个新的文件
fs.writeFile("text2.txt", "abcd", "utf-8", (err) => {
// 写入成功err为null
console.log(err)
})
const fs=require('fs')
// 出现路径拼接错误的问题,是因为提供了 ./ 或 ../ 开头的相对路径
// 如果要解决这个问题,可以直接提供一个完整的文件存放路径就行
/* fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
if (err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!' + dataStr)
}) */
// 移植性非常差、不利于维护
/* fs.readFile('C:\\Users\\escook\\Desktop\\Node.js基础\\day1\\code\\files\\1.txt', 'utf8', function(err, dataStr) {
if (err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!' + dataStr)
}) */
// __dirname 表示当前文件所处的目录
fs.readFile(__dirname+'/text1.txt', 'utf8', function (err, dataStr) {
if (err) {
return console.log('读取文件失败!'+err.message)
}
console.log('读取文件成功!'+dataStr)
})
//引入path模块
const path = require("path")
const path=require('path')
const fs=require('fs')
// 注意: ../ 会抵消前面的路径
// const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
// console.log(pathStr) // \a\b\d\e
// fs.readFile(__dirname + '/files/1.txt')
fs.readFile(path.join(__dirname, './text1.txt'), 'utf8', function (err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
const path=require('path')
// 定义文件的存放路径
const fpath='/a/b/c/index.html'
// const fullName = path.basename(fpath)
// console.log(fullName)
//返回文件名,扩展名为.html的
const nameWithoutExt=path.basename(fpath, '.html')
console.log(nameWithoutExt) //index
const path=require('path')
// 这是文件的存放路径
const fpath='/a/b/c/index.html'
//返回文件扩展名
const fext=path.extname(fpath)
console.log(fext) //.html
//引入http模块
const http=require("http")
// 创建一个web服务器实例
const server=http.createServer()
// 为服务器实例绑定request事件
server.on("request", (req, res) => {
// 当客户端访问到此服务器时,触发request事件
// req为请求对象,包含一些客户端的数据和信息
// req.url客户端请求时的url地址,从端口号开始
// req.method客户端请求时所使用的方法
const str=`客户端请求时的url地址:${req.url},客户端请求时所使用的方法:${req.method}`
console.log(str)
// res为响应对象
// 调用 res.end方法,像客户端响应一些内容
// 调用res.end()方法时,想客户端返回数据包含中文时,会产生乱码的情况,需要手动设置编码格式,设置响应头
res.setHeader("Content-Type", "text/html;charset=utf-8")
res.end(str)
})
// 调用服务器实例的listen方法,启动服务器
server.listen(8080, () => {
console.log("http server is running at http://127.0.0.1:8080")
})
const http=require("http")
const server=http.createServer()
server.on("request", (req, res) => {
const url=req.url
let content="404 Not Found
"
if (url=="/index.html"||url=="/") {
content="首页
"
} else if (url=="/about.html") {
content="关于我们
"
}
res.setHeader("Content-Type", "text/html;charset=utf-8")
res.end(content)
})
server.listen("8080", () => {
console.log("http server is running at http://127.0.0.1:8080")
})
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
自定义模块(用户创建的每个 .js 文件,都是自定义模块)
第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
require() 方法(可以省略.js),可以加载需要的内置模块、用户自定义模块、第三方模块进行使
//加载内置的模块
const fs=require("fs")
//加载自定义的模块
const test=require("./test,js")
//加载第三方的模块
const moment=require("moment")
注意:使用 require() 方法加载其它模块时,会执行被加载模块中的代码。
函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。可以防止了全局变量污染的问题
在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息。
Module {
id: '.',
path: 'C:\\Users\\25787\\Desktop\\web\\node\\study\\day2',
exports: {},
filename: 'C:\\Users\\25787\\Desktop\\web\\node\\study\\day2\\4、加载模块.js',
loaded: false,
children: [],
paths: [Array]
}
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。
//导出
const user="张三"
function sayHello () {
console.log("hello")
}
module.exports= { user, sayHello }
//导出
module.exports={
user:"王二",
sayHello(){
console.log("hello")
}
}
//导出
module.exports.user="李四"
module.exports.sayHello=function () {
console.log("hello")
}
//引入
const ob=require("./moduletest")
console.log(ob.user)
外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。
由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。
Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。
CommonJS 规定:
每个模块内部,module 变量代表当前模块。
module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。
加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。
Node.js 中的第三方模块又叫做包,就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同。
不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js 中的包都是免费且开源的,不需要付费即可免费下载使用。
由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系。
注意:
从 https://www.npmjs.com/ 网站上搜索自己所需要的包
从 https://registry.npmjs.org/ 服务器上下载自己需要的包
初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件。
其中:
node_modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包。
package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
默认情况下,使用 npm install 命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本
//npm 包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建 package.json 这个包管理
npm init -y
在项目根目录中,创建一个叫做 package.json 的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除 node_modules 目录之后,在团队成员之间共享项目的源代码。package.json 文件中,有一个 dependencies 节点,专门用来记录您使用 npm install 命令安装了哪些包。
注意:今后在项目开发中,一定要把 node_modules 文件夹,添加到 .gitignore 忽略文件中‘
当拿到一个删除了 node_modules 的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来,可以运行 npm install 命令(或 npm i)一次性安装所有的依赖包
运行 npm uninstall 命令,来卸载指定的包,npm uninstall 命令执行成功后,会把卸载的包,自动从 package.json 的 dependencies 中移除掉。
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中。
npm i 包名 --save-dev
npm i 包名 -D
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中。
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 目录中的包,都是项目包。
项目包又分为两类,分别是:
开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到)
核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)
在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。
全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下。
package.json (包管理配置文件)
index.js (包的入口文件)
README.md (包的说明文档)
一个规范的包,它的组成结构,必须符合以下 3 点要求:
①包必须以单独的目录而存在
②包的顶级目录下要必须包含 package.json 这个包管理配置文件
③package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。
包根目录中的 README.md 文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考。
README 文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。
我们所创建的这个包的 README.md 文档中,会包含以下 6 项内容:
安装方式、导入方式、格式化时间、转义 HTML 中的特殊字符、还原 HTML 中的特殊字符、开源协议
注:需要将镜像切换到npmjs官方而不是淘宝镜像
将终端切换到包的根目录之后,运行 npm publish 命令,即可将包发布到 npm 上(注意:包名不能雷同)。
npm publish
运行 npm unpublish 包名 --force 命令,即可从 npm 删除已发布的包。
npm unpublish 包名 --force
注意:
①npm unpublish 命令只能删除 72 小时以内发布的包
②npm unpublish 删除的包,在 24 小时内不允许重复发布
③发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!
优先从缓存中加载
模块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。
自定义模块的加载机制
使用 require() 加载自定义模块时,必须指定以 ./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
①按照确切的文件名进行加载
②补全 .js 扩展名进行加载
③补全 .json 扩展名进行加载
④补全 .node 扩展名进行加载
⑤加载失败,终端报错
第三方模块的加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘../’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:\Users\itheima\project\foo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:
① C:\Users\itheima\project\node_modules\tools
② C:\Users\itheima\node_modules\tools
③ C:\Users\node_modules\tools
C:\node_modules\tools
目录作为模块
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
①在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
②如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
③如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
不使用 Express 也可以通过nodejs内置模块创建 Web 服务器,但是http 内置模块用起来很复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。
http内置模块和express的关系就好比浏览器WebAPI和jquery的关系,后者是由前者进一步封装出来的
对于前端开发人员来说,常见服务器分为两类
Web 网站服务器:专门对外提供 Web 网页资源的服务器。
API 接口服务器:专门对外提供 API 接口的服务器。
使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器。
安装express
npm i express
//引入express
constexpress=require("express")
// 创建web服务器。app为服务器实例
constapp=express()
// 启动web服务器
app.listen(8080, () => {
console.log('express server is running at http://127.0.0.1:8080')
})
通过 app.get() 方法,可以监听客户端的 GET 请求
app.get("/user", (req, res) => {
// req.query默认是一个空对象
// 客户端可使用?name=zs&age=18这种格式,发送参数到服务器
// 可以使用req.query对象访问到客户端带来的参数 req.query.name req.query.age
console.log(req.query)
// 通过 res.send() 方法,可以把处理好的内容,发送给客户端
// 调用express提供的res.send() 方法,给客户端返回一个JSON字符串
res.send({ name: "zs", age: 18 })
})
通过 app.post() 方法,可以监听客户端的 POST 请求
app.post("/user", (req, res) => {
// 调用express提供的res.send() 方法,给客户端返回一个字符串
res.send("post请求成功")
})
通过 req.params 对象,可以访问到 URL 中,通过 : 匹配到的动态参数
app.get("/:id", (req, res) => {
// url中,可以通过:参数名的形式,匹配动态参数值
// req.params默认是一个空对象,里面存放着通过:动态匹配到的参数值
res.send(req.params)
})
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了
app.use(express.static("public"))
现在就可以访问 public 目录中的所有文件了:
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
注意:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在 URL 中。
如果要托管多个静态资源目录,多次调用express.static()就行了,访问静态资源文件时,函数会根据目录添加顺序查找所需要的文件
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use("/public",express.static("public"))
现在就可以通过带有 /public 前缀地址来访问 public 目录中的文件了:
http://localhost:3000/public/images/kitten.jpg
http://localhost:3000/public/css/style.css
http://localhost:3000/public/js/app.js
在编写调试 Node.js 项目的时候,如果修改了项目的代码,则需要频繁的手动 close 掉,然后再重新启动,非常繁琐。
可以使用 nodemon(https://www.npmjs.com/package/nodemon) 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
安装为全局工具
npm i -g nodemon
当基于 Node.js 编写了一个网站应用的时候,传统的方式,是运行 node app.js 命令,来启动项目。这样做的坏处是:代码被修改之后,需要手动重启项目。
现在可以将 node 命令替换为 nodemon 命令,使用 nodemon app.js 来启动项目。这样做的好处是:代码被修改之后,会被 nodemon 监听到,从而实现自动重启项目的效果。
nodemon app.js
广义上来讲,路由就是映射关系。
在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下:
app.方法(path,handler)
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理。
路由匹配的注意点:
①按照定义的先后顺序进行匹配
②请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
在 Express 中使用路由最简单的方式,就是把路由挂载到 app 上
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:
①创建路由模块对应的 .js 文件
②调用 express.Router() 函数创建路由对象
③向路由对象上挂载具体的路由
④使用 module.exports 向外共享路由对象
⑤使用 app.use() 函数注册路由模块
创建路由模块:
const express=require("express")
// 创建路由对象
const router=express.Router()
// 挂载具体的路由
router.get("/user", (req, res) => {
res.send(req.query)
})
router.post("/user", (req, res) => {
res.send(req.query)
})
module.exports=router
注册路由:
const express=require("express")
const app=express()
// 导入路由模块
const router=require("./router")
// 注册路由模块
app.use(router)
app.listen('8080', () => {
console.log('express server is running at http://127.0.0.1:8080')
})
注意:app.use()的作用是注册全局中间件
express.static(‘public’)和router都属于中间件
类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单
//使用app.use()注册路由模块,并且添加统一的访问前缀
app.use("/api",router)
中间件(Middleware ),特指业务流程的中间处理环节。
当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:
注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。
next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
const express=require("express")
const app=express()
//定义中间件函数
const mw= (req, res, next) => {
console.log("这是最简单的中间件")
// 把流转关系交给下一个中间件或路由
next()
}
app.listen('8080', () => {
console.log('express server is running at http://127.0.0.1:8080')
})
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
通过调用 app.use(中间件函数),即可定义一个全局生效的中间件,示例代码如下:
const express=require("express")
const app=express()
//定义中间件函数
const mw1= (req, res, next) => {
console.log("这是中间件1")
// 把流转关系交给下一个中间件或路由
next()
}
//注册全局生效的中间件
app.use(mw1)
app.use((req, res, next) => {
console.log("这是中间件2")
// 把流转关系交给下一个中间件或路由
next()
})
app.get("/", (req, res) => {
console.log("get请求")
res.send("get请求")
})
app.listen('8080', () => {
console.log('express server is running at http://127.0.0.1:8080')
})
可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用
const mw1= (req, res, next) => {
console.log("这是中间件1")
// 把流转关系交给下一个中间件或路由
next()
}
app.use(mw1)
app.get("/", (req, res, next) => {
console.log("这是路由中间件")
next()
})
app.get("/", (req, res) => {
console.log("get请求")
res.send("get请求")
})
多个中间件之间,共享同一份 req 和 res。基于这样的特性,可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
不使用 app.use() 定义的中间件,叫做局部生效的中间件
const mw1= (req, res, next) => {
console.log("这是局部中间件")
// 把流转关系交给下一个中间件或路由
next()
}
app.get("/", mw1, (req, res) => {
console.log("这是路由中间件")
next()
})
app.get("/user", (req, res) => {
console.log("get请求")
res.send("get请求")
})
//可同时定义多个中间件,以下两种方式等价:
//app.get("/",mw1,mw2,(req,res)=>{})
//app.get("/",[mw1,mw2],(req,res)=>{})
①一定要在路由之前注册中间件
②客户端发送过来的请求,可以连续调用多个中间件进行处理
③执行完中间件的业务代码之后,不要忘记调用 next() 函数
④为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
⑤连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
① 应用级别的中间件
通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件
② 路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上
router.use((req, res, next) => {
console.log("路由级别中间件")
next()
})
express().use("/",router)
③ 错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
app.get("/", (req, res) => {
thrownewError("服务器内部发生错误")
res.send("Home page")
})
// 错误中间件
app.use((err, req, res, next) => {
console.log("发生了错误"+err.message)
res.send("Error!"+err.message)
})
注意:错误级别的中间件,必须注册在所有路由之后!
④ Express 内置的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
//配置解析application/json格式数据的内置中间件
//在服务器,可以使用req.body,来接收客户端发送过来的请求体数据
//当客户端请求携带body数据为json字符串时,服务器访问req.body结果为undefined,需要注册内置中间件app.use(express.json)
app.use(express.json)
express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
//配置解析application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended:false}))
//false表示不使用第三方解析方式,使用自身解析的解析方式
⑤ 第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在 [email protected] 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
运行 npm install body-parser 安装中间件
使用 require 导入中间件
调用 app.use() 注册并使用中间件
// 内置中间件
// app.use(express.urlencoded({ extended: false }))
// 第三方中间件
const parse=require("body-parser")
app.use(parse.urlencoded({ extended: false }))
app.get("/", (req, res) => {
console.log(req.body)
res.send("OK")
})
注意:Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发
data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
当请求体数据接收完毕之后,会自动触发 req 的 end 事件。因此,可以在 req 的 end 事件中,拿到并处理完整的请求体数据。
Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式
为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块
//middleware.js
// 导入内置模块querystring ,可以将查询字符串转换为对象
const qs=require("querystring")
let str=""
function bodyParse (req, res, next) {
// 监听客户端发送过来的请求体数据,req的data事件
req.on("data", (s) => {
str+=s
})
let obj= {}
req.on("end", () => {
// 使用内置模块querystring的parse方法解析字符串
obj=qs.parse(str)
// 原生
// str.split("&").forEach(item => {
// itemValue = item.split("=")
// obj[itemValue[0]] = itemValue[1]
// })
req.body=obj
next()
})
}
module.exports=bodyParse
//index.js
// 引入express
const express=require('express')
// 创建web服务器。app为服务器实例
const app=express()
// 导入自定义的中间件
const bodyParse=require("./middleware")
app.use(bodyParse)
app.post("/", (req, res) => {
console.log(req.body)
res.send(req.body)
})
// 启动web服务器
app.listen('8080', () => {
console.log('express server is running at http://127.0.0.1:8080')
})
注意事项:
//router可以想app一样,注册中间件或者当路由接口使用
const router=express.Router()
所谓跨域就是从a 向 b发请求,如果他们的地址协议、域名、端口都不相同,直接访问就会造成跨域问题
解决接口跨域问题的方案主要有两种:
① CORS(主流的解决方案,推荐使用)
② JSONP(有缺陷的解决方案:只支持 GET 请求)
cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题。
使用步骤分为如下 3 步:
①运行 npm install cors 安装中间件
②使用 const cors = require('cors') 导入中间件
③在路由之前调用 app.use(cors()) 配置中间件
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的HTTP 响应头,就可以解除浏览器端的跨域访问限制。
注意:服务器会响应资源,但是会被浏览器阻止
CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
Access-Control-Allow-Origin
响应头部中可以携带一个 Access-Control-Allow-Origin 字段其中,origin 参数的值指定了允许访问该资源的外域 URL。
例如,下面的字段值将只允许来自http://itcast.cn的请求,如果指定了 Access-Control-Allow-Origin 字段的值为通配符 *,表示允许来自任何域的请求
//Access-Control-Allow-Origin:|*
res.setHeader("Access-Control-Allow-Origin","http://itcast.cn")Access-Control-Allow-Headers
Access-Control-Allow-Headers
默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:
Accept、Accept-Language、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 对额外的请求头进行声明,否则这次请求会失败!
//允许客户端额外像服务器发送Content-Type请求和X-Custom-Header请求,多个请求头之间使用逗号
res.setHeader("Access-Cpntrol-Allow-Headers","Content-Type","X-Custom-Header")
Accexx-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法。
//只允许"POST","GET","DELETE","HEAD"请求方法
res.setHeader("Access-Cpntrol-Allow-Methods","POST","GET","DELETE","HEAD")
//允许所有请求方法
res.setHeader("Access-Cpntrol-Allow-Methods","*")
客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类,分别是:
简单请求
① 请求方式: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 预检请求成功之后,才会发起真正的请求。
概念:浏览器端通过