nodejs
-
nodejs中代码由V8引擎解析,内置fs,http等api
不包含dom和bom,不可以在nodejs中使用bom和dom
-
nodejs可以做什么
- 基于Express框架,可以快速构架web应用
- 基于Electron框架,构建跨平台的桌面应用
- 基于restify框架,构架api接口项目
- 读写操作数据库…
环境安装
-
官网下载,双击安装(lts版)
lts为长期稳定版
current为新特性尝鲜版(测试版)
-
Node.js 安装配置 | 菜鸟教程 (runoob.com)
配置的时候如果更改了安装路径,需要配置一下环境变量
-
运行nodejs,在终端中输入 node demo.js
-
遇到文件命比较长时,输入文件开头,再敲tab键可以自动补充
-
cls可以清空终端,ecs可以清除当前命令
内置模块
Index | Node.js v17.9.1 Documentation (nodejs.org)官方文档
Node.js 教程 | 菜鸟教程 (runoob.com)菜鸟文档
1.fs模块
-
fs.readFile()方法,用来读取指定文件内容
fs.readFile(path[, options], callback)
path:必选参数,字符串,表示文件路径
option:可选参数,表示什么编码格式来读取文件
callback:必选参数,文件读取完成后,通过回调函数拿到结果
var fs = require("fs"); fs.readFile('study.md','utf8',function(err,data){ // console.log(err); console.log(data); if(err){ return console.log('读取文件失败'+err.message); } else{ console.log('文件读取成功') } })
-
fs.writeFilr()方法,向指定文件写内容(写入时没有指定文件会自动创建文件,但不会创建文件夹,默认会覆盖之前文件的数据)
fs.writeFile(file, data[, options], callback)
file:必选参数,需要指定文件路径字符串,表示文件存放路径
data:必选参数,表示要写入内容
option:可选参数,默认utf8
callback:必选参数,回调函数
var fs = require("fs"); fs.writeFile('2.txt','abcd',function(err){ //err为空时,就表示没有错误 if(err){ console.log('失败'+err.message); } else{ console.log('成功'); } })
fs模块路径拼接问题
会根据当前路径拼接成path
__dirname:表示当前文件所处的目录,可以解决路径拼接问题
path路径模块
使用方法
const path = require('path') path.jion([多个路径片段]) //用于拼接多个路径片段 path.jion('/a','/b','.../') //../会抵消前面的一层路径 path.jion(__dirnaeme,'/a/1.txt')
const fpath = '/a/b/1.txt' var filename = path.basename(fpath) //获取文件命(包含扩展命) var name = path.basename(fpath,'.txt') //去除扩展名
//获取扩展名 const fpath = '/a/b/1.txt' var fext = path.extname(fpath)
//案例 //拆分html中的style部分 const fs = require('fs') // const path = require('path') // 正则表达式 // \s:匹配空白字符 // \S:匹配非空白字符 const restyle = /<style>[\s\S]*<\/style>/ const rescript = /<script>[\s\S]*<\/script>/ const html = fs.readFile('index.html','utf-8',function(err,data){ if(err){ return console.log('读取失败'+err.message); } // console.log(data) resolveCss(data) }) // 处理css函数 function resolveCss(htmlStr){ const r1 = restyle.exec(htmlStr) const newcss = r1[0].replace('<style>','').replace('</style>','') fs.writeFile('index.css',newcss,function(err){ if(err){ return console.log('写入失败'+err.message); } console.log('写入成功') }) }
2.http模块
-
费资源的电脑叫客户端,负责对外提供网络资源的电脑叫服务器(简单理解)
普通电脑安装web服务器软件,例如apache等,可以将电脑变成服务器
-
调用
const http = require('http')
在nodejs中不用安装web服务器,利用
http
模块 -
ip地址:每台计算机的唯一地址,具有唯一性
互联网中每台web服务器都有自己的ip地址
ping www.baidu.com //可以查看百度的ip地址 127.0.0.1 //自己的IP地址
域名服务器(DNS)用于将域名解析为ip地址
端口号:在同一台电脑中,会有很多服务,端口号用来标识各个服务,同一个端口不可以被多个web服务占用,url中只有80端口可以省略
-
创建web服务器基本步骤
// 导入模块 const http = require('http') //创建web服务器实例 const serve = http.createServer() // 为服务器绑定事件 // 当有服务请求的时候,就会触发request事件 serve.on('request',(req,res)=>{ // 触发request后,会执行回调函数 //获取url和访问方式 console.log(req.url); console.log(req.method); console.log('request'); const str = req.url+':'+req.method+'你好啊' // 调用res.end(),响应一些数据 //设置编码,解决中文乱码 res.setHeader('Content-Type','text/html;charset=utf-8') res.end(str) }) // 启动服务器 serve.listen(80,()=>{ // 启动成功之后会调用回调函数 console.log('start'); })
-
req请求对象
只要服务器接收到了客户端的请求,就会调用server.on()中为服务器绑定的request事件处理函数,也就是其中的回调函数
req为请求对象,可以拿到请求端的数据
-
res响应对象
访问服务器相关的数据和属性
-
解决中文乱码问题
需要手动设置编码格式
-
模块化概念
1. 模块化基本概念
自顶向下把系统分成若干个模块的过程,模块是可组合,分解和更换的单元
生活中的例子,如小霸王游戏机,可分为卡带,手柄,主体三部分,三部分都是可以替换组合的
编程领域中的模块化,遵守固定的规则,把一个大文件拆分成独立并且相互依赖的多个小模块
2. 模块化规范
-
nodejs中模块分类
- 内置模块:nodejs自带的模块。如http,fs
- 自定义模块:用户创建的,每个js文件都是自定义模块
- 第三方模块:使用前需要先下载
-
加载模块
使用
require()
来加载模块//内置模块 const http = require('http') //加载用户自定义模块,需要写路径 const http = require('./http.js') //加载第三方模块,需要先下载 const http = require('demohttp')
注意:使用
require()
加载模块时,会执行加载模块中的代码 -
模块作用域(模块级别的访问限制)
在自定义模块中定义的方法,变量等成员,只能在当前模块被访问
防止全局变量污染的问题
-
如何向外共享模块中的成员(变量,方法等)
每个.js自定义模块中,都有一个
module
对象//在任意js文件中都有这个对象 console.log(module)
Module {
id: '.',
path: 'D:\\0.vue\\0.nodejs',
exports: {},
filename: 'D:\\0.vue\\0.nodejs\\4.module测试.js',
loaded: false,
children: [
Module {
id: 'D:\\0.vue\\0.nodejs\\test.js',
path: 'D:\\0.vue\\0.nodejs',
exports: {},
filename: 'D:\\0.vue\\0.nodejs\\test.js',
loaded: true,
children: [],
paths: [Array]
}
],
paths: [
'D:\\0.vue\\0.nodejs\\node_modules',
'D:\\0.vue\\node_modules',
'D:\\node_modules'
]
}
-
module.exports对象
在自定义模块中,可以使用module.exports对象,将模块内成员共享出去
外界用
require()
方法导入的就是module.exports对象该对象默认为空
test.js
//共享成员 module.exports.name='zhangsan' module.exports.sayHello = function(){ console.log('你好啊'); } 或者 module.exports = { 一个json对象 }
执行代码
const test = require('./test.js') console.log(test); test.sayHello()
注:导入的对象以module.exports对象为准
-
exports对象
nodejs提供exports对象,与module.exports指向同一个对象,但最终结果还是以module.exports指向的对象为准
//共享成员
exports.name='zhangsan'
exports.sayHello = function(){
console.log('你好啊');
}
-
exports和module.exports的使用误区
####require()得到的一直都是module.exports所指对象
将对象赋值给module.exports时,会开辟新的空间,增加属性则不会
对象的赋值都会开辟新的空间?
###3. Commonjs规范
nodejs遵循了commonjs模块化规范
commonjs规定
1. 每个模块内部。moudule变量代表当前模块
2. modoule变量是一个对象,该对象的exports(module.exports)属性是对外的接口
- 加载模块,使用require()
4. npm与包
npm是包管理器npm (npmjs.com)
网站用于搜索包,下包在另外一个服务器地址
包是基于内置模块封装出来的,可以极大提高开发效率
-
如何下载包
下包管理工具,在安装nodejs时会同时安装此工具
npm -v 查看当前npm版本 npm install 包名 简写 npm i 包名
npm install --save mybatis-mapper
-
初次装包后多了一些文件
node_modules:用来存放包
package-lock.json:记录下载信息
npm i 包名
会安装最新的版本
npm i moment@2.22.2
安装指定的包版本(不用先卸载旧版本,会自动替换)
-
快速创建package.json
npm init -y 项目目录不能有中文
npm i
会根据package.json的内容,一次安装所有的包
npm uninstall 包名
卸载包
devDependencies节点
如果有些包只在项目开发的时候用到,上线之后不会用到 npm i 包名 -D 如果开发和上线都需要用到就正常安装
-
解决下包速度慢
查看当前下载源 npm config get registry 使用淘宝服务器下载 npm config set registry=https://registry.npm.taobao.org/
-
nrm 方便切换下载源
npm i nrm -g
-g为全局安装
nrm ls 查看所有可用下载源 nrm use taobao 使用淘宝下载源
-
包的分类
项目包
开发依赖包(包只在项目开发的时候用到,上线之后不会用到)
npm i 包名 -D
核心依赖包(开发和上线都需要用到)
npm i 包名
全局包
提供-g参数,会把包安装为全局包
npm i 包名 -g
npm uninstall 包名 -g
卸载全局包 全局包默认会被安装到(打开隐藏文件)
C:\User\用户\AppData\Roaming\npm\node_modules
一般而言,只有工具包才有全局安装必要,如nrm
也可以参考官网,看是否建议全局安装
npm install -g i5ting_toc
:把.文档装换成html页面的小工具使用
i5ting_toc -f .\study.md
-
规范包的结构
-
包必须以单独的目录存在
-
顶级目录下需要有package.json包管理配置文件
-
package.json必须包含
name
,version
,main
这三个属性分别为名字,版本号,包的入口
-
-
发布自己的包
-
新建文件夹作为包的根目录,(命名为包名)
-
在文件夹中,新建如下三个文件
· package.json (包管理配置文件)
· index.js (包的入口文件)
· README.md (包的说明文档)
-
初始化package.js
name为包的名字,命名前需在npm官网上面查询一下,命名不允许重复
mian用来指定导包的入口
{ "name": "mypackage", "version": "1.0.0", "main": "index.js", "description": "提供了什么什么功能", "keywords": ["关键字","第二个关键字"], //开源许可协议 "license": "ISC" }
-
index.js (编写实现功能代码)
// 定义格式化时间函数 function dataFormat(dataStr){ const dt = new Date(dataStr) const y = padZero(dt.getFullYear()) const m = padZero(dt.getMonth() +1) const d = padZero(dt.getDate()) return `${y}-${m}-${d}` } function padZero(n){ return n > 9 ? n : '0'+ n } module.exports= { dataFormat }
使用
const dt = require('./mypackage/index') console.log(dt) const dt1 = dt.dataFormat(new Date()) console.log(dt1)
-
如果index.js中的功能很多,可以进行模块化拆分
-
提取某个函数,拆分到
src
(新建目录)下的功能名.js
文件当中 -
在index.js中,导入各个模块(就是src下的各个
功能名.js
文件) -
在index.js中,使用module.exports把对应的方法暴露出去
-
-
发布自己的包
在运行npm login之前,必须将下包的服务器切换为官方服务器
nrm ls nrm use npm
-
注册账号,在官网注册
-
登陆npm账号
在终端输入npm login命令
依次输入用户名、密码、邮箱
-
登陆成功之后,切换到包的根目录
运行
npm publish
命令,就可以发布包(包名不能有已经存在的) -
删除已发布的包
npm unpublish 包名 --force
该命令只能删除72小时以内的包,72小时之后将永远不能删除
删除的包,24小时内不准重复发布
-
尽量不要往npm中发布没有意义的包
-
-
5. 模块加载机制
-
模块优先从缓存中加载
所有的模块在第一次加载后,会被缓存,使用的时候优先从缓存中加载
-
内置模块加载优先级最高,第三方模块与内置模块相同时,以内置模块为准
-
自定义模块(本地自己写的)需要以
./
或者../
开头,不然会被当成内置模块或者第三方模块 -
使用require()导入自定义模块时,如果省略扩展名
会按照如下顺序寻找文件
- 确切文件
- 补全.js
- 补全.json
- 补全.node
-
第三方模块加载机制
调用require()时
会从当前目录查找node_modules文件夹,到里面查找包
如果没有node_modules文件夹,就会往上退一级目录,继续找
直到为根目录为止
Express
1. 初步认识Express
Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网 (expressjs.com.cn)
基于nodejs平台,快速,开发,极简的web开发框架
类似于nodejs中的http模块,专门用来创建web服务器的
本质上就是一个npm的第三方包
基于http模块封装出来的
2. 基本使用
-
安装
npm i express@4.17.1
- 创建最基本的服务器
// 导入包 const express = require('express') // 创建web服务器 const app = expres() // 调用app.listen(端口号,启动成功后的回调函数) app.listen(80,()=>{ console.log('express server running'); })
- 监听get请求
// 导入包 const express = require('express') // 创建web服务器 const app = express() // 调用app.listen(端口号,启动成功后的回调函数) app.listen(80,()=>{ console.log('express server running'); }) // get第一个参数为url // 第二个参数:请求对应的函数 // req:请求对象,包含请求的属性和方法 // res:响应对象,包含响应的属性和方法 app.get('/', (req, res) => { // app.send()向客户端发送响应数据 res.send('Hello World!') })
post请求:
与get请求相同 // post第一个参数为url // 第二个参数:请求对应的函数 // req:请求对象,包含请求的属性和方法 // res:响应对象,包含响应的属性和方法 app.post('/', (req, res) => { // app.send()向客户端发送响应数据 res.send('post请求成功') })
- 获取url中携带的查询参数
通过
req.query
对象,可以访问到客户端通过查询字符串的形式发送到服务器的参数(默认为空对象)app.get('/', (req, res) => { // 客户端使用?name=zs&age=20这种查询字符串的形式发送到服务器的参数 // 可以通过req.query对象访问到 console.log(req.query) res.send(req.query) })
获取url中的动态参数
通过
req.params
对象,可以访问到url中,通过:
匹配的动态参数//多个参数时url为 // /user/:id/:name app.get('/user/:id', (req, res) => { // req.params 默认是一个空对象 // 里面存放动态匹配到的参数 console.log(req.params) res.send(req.params) })
- express.static()
通过该函数,可以非常方便的创建一个静态资源服务器
app.use(express.static('public')) //之后可以访问public目录下所有的文件了,不用加public前缀 //如果需要加上public前缀
app.use(‘/public’,express.static(‘public’))
```
访问[demo.jpg (1196×1982)](http://localhost/demo.jpg)
url不用包含public
托管多个静态资源目录,多次调用`app.use(express.static('public'))`就行
3. nodemon的使用
nodemon会自动帮我们重启项目,方便开发和调试
安装 npm i -g nodemon
使用nodemon
之前是使用
node demo.js
来运行代码,需要手动重启
使用
nodemon demo.js
会自动监听代码改动
保存之后会自动重启
4. Express中路由的学习
Express中的路由指的是客户处理请求与服务器处理函数之间的关系
Express中的路由分三部分组成,请求类型,请求url地址,处理函数
格式如下:
app.METHOD(PATH,HANDLER)
定义路由(挂载路由)
app.get('/', (req, res) => {
// app.send()向客户端发送响应数据
res.send('Hello World!')
})
-
路由的匹配过程
按照代码中定义的先后顺序
并且需要请求类型和请求url地址都匹配
-
路由的模块化
不建议将路由直接挂载到app上
将路由分离为单独模块的步骤
- 创建路由模块的.js文件
- 调用express.Router()函数创建路由对象
- 向路由对象上挂载具体路由
- 使用module.exports()向外共享路由对象
- 使用app.use()函数注册路由模块
创建router.js
文件(作为路由模块)
const express = require('express')
// 调用express.Router()函数创建路由对象
const router = express.Router()
// 向路由对象上挂载具体路由
router.get('/user',(req,res)=>{
res.send('get user')
})
router.get('/user2',(req,res)=>{
res.send('get user2')
})
// 使用module.exports向外共享路由对象
module.exports = router
执行文件
const express = require('express')
//导入自定义的路由模块
const router = require('./router')
const app = express()
// 注册路由模块
app.use(router)
app.listen(80,()=>{
console.log('server start')
})
注意:app.use()函数的作用,是用来注册全局中间件的
-
为路由模块添加前缀
//之后要访问router中所有的路由,都需要加api前缀 app.use('/api',router)
5. Express中间件
业务处理过程中的中间处理环节
都有输入和输出
例子:污水处理
一级处理 二级处理 三级处理
前一个的输出作为后一个的输入
处理污水的这三个环节,就可以叫作中间件
-
中间件调用流程
浏览器发起一次请求之后,可能需要多个中间件介入处理
在请求经过多个中间件处理完成之后
最后通过路由来响应这次请求
当一个请求到达express服务器之后,可以连续调用多个中间件,对这次请求进行预处理
-
Express中间件的格式
本质是一个
function
处理函数app.get('/', (req, res,next) => { next() })
注意:中间件的形参列表中,必须包含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('server start') })
-
全局生效的中间件
任何请求,到达服务器之后,都会触发的中间件,叫全局生效的中间件
// 通过app.use(中间件函数),来注册一个全局生效的中间件 //全局生效的中间件,发起任何请求都会被执行 //接上面 app.use(mw) app.get('/user',(req,res)=>{ res.send('get user') }) app.get('/user2',(req,res)=>{ res.send('get user2') })
多个中间件和路由之间共享同一份req和res,在上游添加的属性可以在下游被使用
const express = require('express') const app = express() //定义一个的中间件函数 // 需要获取每次响应时间 const mw1 = function(req,res,next){ next() } app.listen(80,()=>{ console.log('server start') }) app.use(mw) app.get('/user',(req,res)=>{ res.send('get user'+req.startTime) }) app.get('/user2',(req,res)=>{ res.send('get user2'+req.startTime) })
连续定义多个中间件(按照先后顺序执行)
const express = require('express') const app = express() //定义第一个的中间件函数 const mw1 = function(req,res,next){ console.log('调用第一个中间件'); next() } const mw2 = function(req,res,next){ console.log('调用第二个中间件'); next() } app.listen(80,()=>{ console.log('server start') }) app.use(mw1) app.use(mw2) app.get('/user',(req,res)=>{ res.send('get user') }) app.get('/user2',(req,res)=>{ res.send('get user2') })
局部生效的中间件
const express = require('express') const app = express() //定义局部中间件 const mw1 = function(req,res,next){ console.log('调用一个中间件'); next() } app.listen(80,()=>{ console.log('server start') }) // 创建路由 // 让中间件只在第一个路由里面生效 app.get('/user',mw1,(req,res)=>{ //当请求地址为/user时,中间件生效 res.send('get user') }) app.get('/user2',(req,res)=>{ //当请求地址为/user2时,中间件不会生效 res.send('get user2') })
定义多个中间件
方式1: app.get('/user2',mw1,mw2,mw3,(req,res)=>{ //当请求地址为/user2时,中间件不会生效 res.send('get user2') }) 方式2: app.get('/user2',[mw1,mw2,mw3],(req,res)=>{ //当请求地址为/user2时,中间件不会生效 res.send('get user2') })
中间件的五个注意事项
- 一定要在路由之前注册中间件
- 执行完中间件业务代码后,一定要调用next()函数
- 多个中间件之间,共享req和res
6. 中间件分类
-
应用级别中间件
通过
app.use()
或者app.get()
或者app.post()
绑定到app实例身上的中间件- 路由级别的中间件
绑定到
express.Router()
实例上的中间件router.use(中间件)
- 错误级别的中间件
专门捕获整个项目中发生的异常错误
错误级别的中间件function处理函数中,必须有四个参数
//错误级别中间件可以用来捕获错误
app.use(function(err,req,res,next){
console.log('发生了错误'+err.message)
res.send('error'+err.message)
})
错误发生时,服务器会崩溃
const express = require('express')
const app = express()
app.listen(80,()=>{
console.log('server start')
})
app.get('/user',(req,res)=>{
// 人为制造错误
throw new Error('服务器内部发生了错误')
res.send('get user')
})
app.get('/user2',(req,res)=>{
res.send('get user2')
})
捕获错误:(错误级别中间件要写在路由后面)
错误级别的中间件,必须注册在所有中间件之后
const express = require('express')
const app = express()
app.get('/user',(req,res)=>{
// 人为制造错误
throw new Error('服务器内部发生了错误')
res.send('get user')
})
//定义错误级别中间件
app.use((err,req,res,next)=>{
console.log('发生了错误'+err.message)
res.send('error'+err.message)
})
app.listen(80,()=>{
console.log('server start')
})
-
Express内置的中间件
-
express.static 快速托管静态资源的内置中间件,(无兼容性)
-
express.json 解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
app.use(express.json())
const express = require('express') const app = express() app.listen(80,()=>{ console.log('server start') }) // 配置josn解析中间件 app.use(express.json()) app.post('/user',(req,res)=>{ // 在服务器,可以使用req.body这个属性,接收客户端发送的数据 // 默认情况下,如果配置解析表单数据的中间件 //body默认等于underfind console.log(req.body) res.send('get user') })
- express.urlencoded解析URL-encoded格式的请求数据(有兼容性,4.16.0+)
app.use(express.urlencoded({extended: false}))
const express = require('express') const app = express() app.listen(80,()=>{ console.log('server start') }) // 配置josn解析中间件 app.use(express.json()) //配置url-encoded解析中间件 //解析url-encoded格式数据 app.use(express.urlencoded({extended: false})) app.post('/user',(req,res)=>{ // 在服务器,可以使用req.body这个属性,接收客户端发送的数据 // 默认情况下,如果配置解析表单数据的中间件 //req.body默认等于underfind console.log(req.body) res.send('get user') }) app.post('/user2',(req,res)=>{ // 在服务器,可以使用req.body这个属性,接收客户端发送的数据 // 默认情况下,如果配置解析表单数据的中间件 //body默认等于underfind console.log(req.body) res.send('get user') })
-
-
第三方中间件
非官方内置的,由第三方开发出来的中间件
举例:
body-parser
使用步骤如下运行
npm i body-parser
安装使用require导入
app.use()调用
注意:Express.urlencoded中间件,就是基于body-parser这个第三方中间件封装出来的
7. 案例,手动模拟express.urlencoded中间件
const express = require('express')
// 内置模块querystring,专门用来处理查询字符串
// 该模块的parse()函数可以将查询字符串解析成对象的格式
const qs = require('querystring')
const app = express()
app.listen(80,()=>{
console.log('server start')
})
//解析中间件
app.use((req,res,next)=>{
let str = ''
// 监听req的data事件
//只要有数据到达服务器,就会触发data事件
req.on('data',(chunk)=>{
str += chunk
})
// 监听req的end事件
// 当请求体数据接收完毕之后,会自动触发req的end事件
req.on('end',()=>{
// 打印完整的请求体
console.log(str)
//解析请求体
console.log(qs.parse(str))
// 将解析出来的数据,挂载到自定义属性body里面去
req.body=qs.parse(str)
next()
})
})
app.post('/user',(req,res)=>{
console.log(req.body)
res.send(req.body)
})
将自定义中间件封装为模块
// 内置模块querystring,专门用来处理查询字符串
// 该模块的parse()函数可以将查询字符串解析成对象的格式
const qs = require('querystring')
//解析中间件
const fun = (req,res,next)=>{
let str = ''
// 监听req的data事件
//只要有数据到达服务器,就会触发data事件
req.on('data',(chunk)=>{
str += chunk
})
// 监听req的end事件
// 当请求体数据接收完毕之后,会自动触发req的end事件
req.on('end',()=>{
// 打印完整的请求体
console.log(str)
//解析请求体
console.log(qs.parse(str))
// 将解析出来的数据,挂载到自定义属性body里面去
req.body=qs.parse(str)
next()
})
}
module.exports = fun
测试
const fun = require('./模块化自定义函数.js')
const express = require('express')
const app = express()
app.listen(80,()=>{
console.log('server start')
})
//自定义中间件函数,注册为全局中间件
app.use(fun)
app.post('/user',(req,res)=>{
console.log(req.body)
res.send(req.body)
})
8. 使用Express写接口
编写get接口
router.js
const express = require('express')
// 调用express.Router()函数创建路由对象
const router = express.Router()
router.get('/get',(req,res)=>{
//获取查询字符串
const query =req.query
//响应结果
res.send({
statu:0,
msg:'请求成功',
data:query
})
})
module.exports = router
test.js
const express = require('express')
const router = require('./router')
const app = express()
//配置解析表单中间件
app.use(express.urlencoded({extended: false}))
app.use('/api',router)
//启动服务器
app.listen(80,()=>{
console.log('server start')
})
编写post接口
app.use(express.urlencoded({extended: false}))
使用post需要解析请求体时,要加入上面代码才能显示body
在router.js中添加
router.post('/post',(req,res)=>{
//获取请求体url-encoded格式的数据
const body =req.body
//响应结果
res.send({
statu:0,
msg:'请求成功',
data:body
})
})
9. 接口的跨域问题
协议,域名,端口号都需要相同才能进行访问
访问端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button id="get">get</button>
<button id="post">post</button>
</body>
<script>
$(function(){
//测试get接口
$('#get').on('click',function(){
$.ajax({
type:'GET',
url:'http://localhost:80/api/get',
data:{name:'zs',age:'20'},
//成功的回调函数
success:function(res){
console.log(res)
}
})
})
//测试post接口
$('#post').on('click',function(){
$.ajax({
type:'POST',
url:'http://localhost:80/api/post',
data:{name:'zs',age:'20'},
//成功的回调函数
success:function(res){
console.log(res)
}
})
})
})
</script>
</html>
服务器端用上面写的接口
解决跨域问题的方案主要有两种:
1. CORS(主流的解决方案,推荐使用)
2. JSONP(只支持GET请求)
使用CORS中间件解决跨域问题
1.安装中间件
npm i cors
2.使用require('cors')导入中间件
const cors= require('cors')
3.在路由之前调用中间件
app.use(cors())
只需要在上面服务端代码加上
const express = require('express')
const router = require('./apiRouter')
// 2.使用require('cors')导入中间件
const cors= require('cors')
const app = express()
app.use(express.urlencoded({extended: false}))
//解决跨域问题
app.use(cors())
app.use('/api',router)
//启动服务器
app.listen(80,()=>{
console.log('server start')
})
10.CORS(三个请求头)
CORS跨域资源共享
什么是CORS
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。
CORS的注意事项
CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。
CORS在浏览器中有兼容性。只有支持XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
CORS响应头部:
CORS响应头部– Access-Control-Allow-Origin
响应头部中可以携带一个Access-Control-Allow-Origin字段,其语法如下:
Access-Control-Allow-origin: <origin> |*
其中,origin参数的值指定了允许访问该资源的外域URL。例如,下面的字段值将只允许来自http://itcast.cn的请求:
res.setHeader( 'Access-Control-Allow-Origin','htp://itcast.cn')
默认情况下,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-Control-Allow-Headers ' ,'Content-Type,x-Custom-Header')
CORS响应头部–Access-Control-Allow-Methods
默认情况下,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 ', '*')
11. CORS请求的分类
-
简单请求
同时满足两个条件
请求方式为:get,post,head三者之一
并且头部信息不超过九个请求体,(就是无自定义头部字段)
-
预检请求
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
不满足简单请求条件的,都是预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求:
一、请求方式为GET、POST、HEAD之外的请求Method类型
二、请求头中包含自定义头部字段
三、向服务器发送了application/json格式的数据 -
简单请求和预检请求的区别
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点∶客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。测试时,不能用chome浏览器
//预检请求代码 $(function(){ //测试get接口 $('#get').on('click',function(){ $.ajax({ type:'GET', url:'http://localhost:80/api/get', data:{name:'zs',age:'20'}, //成功的回调函数 success:function(res){ console.log(res) } }) }) //测试post接口 $('#post').on('click',function(){ $.ajax({ type:'POST', url:'http://localhost:80/api/post', data:{name:'zs',age:'20'}, //成功的回调函数 success:function(res){ console.log(res) } }) }) //为删除按钮绑定点击事件处理函数 $('#delete').on('click',function(){ $.ajax({ type:'DELETE', url:'http://localhost:80/api/delete', //成功的回调函数 success:function(res){ console.log(res) } }) }) })
12.jsonp接口
1.回顾JSONP的概念与特点
概念:浏览器端通过
JSONP仅支持GET请求,不支持POST、PUT、DELETE 等请求。
创建jsonp接口的注意事项:
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS 中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS的接口。示例代码如下:
//优先创建JSONP接口【这个接口不会被处理成CORS接口】
app.get('/api/jsonp',(req,res) =>{ })
//再配置 CORS中间件【后续的所有接口,都会被处理成CORS接口】
app.use(cors())
//这是一个开启了CORS的接口
app.get( '/api/get', (req,res) =>{ })
实现JSONP 接口的步骤
-
获取客户端发送过来的回调函数的名字
-
得到要通过JSONP形式发送给客户端的数据
-
根据前两步得到的数据,拼接出一个函数调用的字符串
-
把上一步拼接得到的字符串,响应给客户端的
在网页中使用jQuery发起JSONP请求
//调用ajax函数,提供JSONP的配置选项,从而发起JSONP请求,示例代码如下:
$('#btnJSONP').on('click',function(){
$.ajax({
method:'GET',
url:'http://localhost/api/jsonp',
dataType:'jsonp',
success:function(res){
console.log(res)
}
})
})
13.完整代码
apiRouter.js
const express = require('express')
// 调用express.Router()函数创建路由对象
const router = express.Router()
router.get('/get',(req,res)=>{
//获取查询字符串
const query =req.query
//响应结果
res.send({
statu:0,
msg:'请求成功',
data:query
})
})
router.post('/post',(req,res)=>{
//获取请求体
const body =req.body
//响应结果
res.send({
statu:0,
msg:'请求成功',
data:body
})
})
router.delete('/delete',(req,res)=>{
res.send({
status:0,
message:'delete请求成功'
})
})
module.exports = router
apiRun.js
const express = require('express')
const router = require('./apiRouter')
// 2.使用require('cors')导入中间件
const cors= require('cors')
const app = express()
app.use(express.urlencoded({extended: false}))
//优先创建JSONP接口【这个接口不会被处理成CORS接口】
app.get('/api/jsonp',(req,res) =>{
//1.获取客户端发送过来的回调函数的名字
const funcName = req.query.callback
//得到要通过JSONP形式发送给客户端的数据
const data = {name: 'zs ', age: 22 }
//根据前两步得到的数据,拼接出一个函数调用的字符串
const scriptstr =`${funcName}(${JSON.stringify(data)})`
//把上一步拼接得到的字符串,响应给客户端的<script>标签进行解析执行
res.send(scriptstr)
})
//再配置 CORS中间件【后续的所有接口,都会被处理成CORS接口】
app.use(cors())
app.use('/api',router)
//启动服务器
app.listen(80,()=>{
console.log('server start')
})
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button id="get">get</button>
<button id="post">post</button>
<button id="delete">delete</button>
<button id="jsonp">jsonp</button>
</body>
<script>
$(function(){
//测试get接口
$('#get').on('click',function(){
$.ajax({
type:'GET',
url:'http://localhost:80/api/get',
data:{name:'zs',age:'20'},
//成功的回调函数
success:function(res){
console.log(res)
}
})
})
//测试post接口
$('#post').on('click',function(){
$.ajax({
type:'POST',
url:'http://localhost:80/api/post',
data:{name:'zs',age:'20'},
//成功的回调函数
success:function(res){
console.log(res)
}
})
})
//为删除按钮绑定点击事件处理函数
$('#delete').on('click',function(){
$.ajax({
type:'POST',
url:'http://localhost:80/api/delete',
//成功的回调函数
success:function(res){
console.log(res)
}
})
})
//
//调用ajax函数,提供JSONP的配置选项,从而发起JSONP请求,示例代码如下:
$('#jsonp').on('click',function(){
$.ajax({
method:'GET',
url:'http://localhost/api/jsonp',
dataType:'jsonp',
success:function(res){
console.log(res)
}
})
})
})
</script>
</html>