客户端发送请求的方式
客户端传递到服务器参数常见的5
种方法
通过get
请求中的URL
的params
通过get请求中的URL的query
通过post
请求中的body
的json
格式
通过post请求中的body的x-www-form-urlencoded
格式
通过post请求中的form-data
格式
传递参数params和query
params
如果请求路径是http://localhost:3000/user/123132
,那么我们就可以通过req.params
的方式获取到用户传递过来的参数信息
const express = require('express')const app = express()app.get('/user/:id', (req, res, next) => { console.log(req.params); // { id: '123132' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})
用户在传递的时候可能也会传递多个参数,但这种情况比较少;请求路径为http://localhost:3000/user/haha/18
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})
query
如果请求路径是http://localhost:3000/user?name=haha&age=18
,那么我们就可以通过req.params
的方式获取到用户传递过来的参数信息
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})
响应数据
end方法
类似于http
中的response.end
方法,用法是一致的;但是其只能返回字符串或者是buffer
数据
const express = require('express')const app = express()app.post('/login', (req, res, next) => { // 下面这行代码会报错 res.end({ name: 'chris' }) // The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object})app.listen(3000, () => { console.log('express框架初体验');})
如果我们想返回一个json
数据,则需要先用res.type
方法设置响应头中的content-type
为application/json
,然后再利用JSON.stringify
将我们要返回的对象转换为json格式的数据之后再使用res.end
方法返回
const express = require('express')const app = express()app.post('/login', (req, res, next) => { // 指定响应数据的数据类型,这样客户端才知道以什么形式的数据进行解析 res.type('application/json') // 将返回的对象先转为json格式的字符串 res.end(JSON.stringify({ name: 'chris' }))})app.listen(3000, () => { console.log('express框架初体验');})
json方法
json
方法中可以传入很多的类型:object
、array
、string
、boolean
、number
、null
等,它们最终都会被转换成json格式返回;json方法相当于帮我们做了两件事情,先设置好响应头中的content-type
属性,再将数据转化为json格式响应回去
const express = require('express')const app = express()app.post('/login', (req, res, next) => { res.json({ name: 'chris' })})app.listen(3000, () => { console.log('express框架初体验');})
status方法
用于设置状态码
const express = require('express')const app = express()app.post('/login', (req, res, next) => { res.status(204) res.json([1, 2])})app.listen(3000, () => { console.log('express框架初体验');})
Express的路由
如果我们将所有的代码逻辑都写在app
中,那么app会变得越来越复杂
一方面完整的Web
服务器包含非常多的处理逻辑
另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对用户user
相关的处理
获取用户列表
获取某一个用户信息
创建一个新的用户
删除一个用户
更新一个用户
我们可以使用express.Router
来创建一个路由处理程序
一个Router
实例拥有完整的中间件和路由系统
因此,它也被称为迷你应用程序(mini-app
)
// app.jsconst express = require('express')const userRouter = require('./routers/user')const app = express()// 注册一个普通中间件来解析请求体中的json数据app.use(express.json())// 将有关用户的请求全部引入到注册好的用户路由中去执行app.use('/user', userRouter)app.listen(3000, () => { console.log('express框架初体验');})
一个路由实例就相当于是一个小的mini-app
,其拥有完整的中间件,所以其也可以像app
一样注册其它的中间件
// routers/user.jsconst express = require('express')// 使用express.Router函数注册一个路由const router = express.Router()// 一个路由实例拥有完整的中间件功能,所以其也可以像app一样注册其它的中间件router.get('/:id', (req, res, next) => { res.json({nickname: 'chris', age: 18})})router.post('/', (req, res, next) => { console.log(req.body); res.end('注册了一个新用户')})router.delete('/:id', (req, res, next) => { res.end(`删除了用户${req.params.id}`)})// 将注册号的路由导出module.exports = router
静态资源服务器
在node
里面,如果想把某一个文件夹作为静态的服务器让客户端可以进行访问的话是非常简单的
express
内置了一个static
方法,其返回值是一个函数,可以作为我们的中间件
如果我们通过http
来做的话,需要自己读取里面的文件然后再返回回去
但是在express里面,我们只需要注册一个普通中间件就可以了,只指定文件夹作为我们的静态资源文件夹,这样当我们去请求资源的时候就会去该文件夹里面寻找
dist
文件夹是之前自己做的后台管理系统项目的打包文件,我们可以通过express
来部署项目到本机;express内置了一个static方法,其返回值是一个函数,可以作为我们的中间件,需要传递一个路径作为静态资源文件夹
const express = require('express')const app = express()// express内置了一个static方法,其返回值是一个函数,可以作为我们的中间件app.use(express.static('./dist'))app.listen(3000, () => { console.log('express框架初体验');})
express的错误处理
next
函数中如果传入了参数,执行的并不是下一个匹配上的中间件,而是专门用来匹配错误的中间件;这样我们所有的错误处理就可以集中在一个中间件中进行管理,代码更加整洁且利于维护
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})0
下面是自己用postman
进行的测试
源码解读
调用express()到底创建的是什么?
express()
创建的是一个名为app
的函数,在这个函数上面集成了很多的方法,最经典的就是可以用app来创建中间件
我们从express
的index.js
文件中发现其是从lib
文件夹下面的express.js
文件中导入的
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})1
来到express.js
中发现其导出的其实是一个函数createApplication
,也就是说const express = require('express')
其实就是将createApplication函数的引用赋值给了变量express
而已
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})2
我们来到createApplication
函数后发现,在其里面定义了一个名为app
的函数并导出。我们现在应该明白const app = express()
的时候函数的返回值是什么了吧?其实就是在createApplication函数里面声明的一个名为app的函数而已
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})3
app.listen()是如何启动服务器的?
app.listen()
其实本质上用的还是http模块搭建服务器的方式,相当于在外面封装了一层而已
仔细查看createApplication
函数之后,没有发现有给app
函数添加过listen
属性,那我们为什么可以直接调用app.listen方法呢?
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})4
其实基础比较好的同学肯定已经想到了,一个对象上面没有对应的属性就会到其原型中寻找,我们能调用listen
方法就说明该方法肯定是在app
函数的原型上的,而mixin(app, proto, false)
这行代码做的就是将proto
对象上的属性混合到app上
我们去proto
对象中寻找之后发现,其身上的确具有listen
方法,所以我们调用app.listen
时实际上调用的是下面的代码;不难看出app.listen实际上是使用了原生http
模块的createServer
方法来进行搭建服务器的,监听端口号的方法用的也是server.listen
而已,相当于是做了一层封装。所以本质上express就是http模块的封装这句话一点也不假
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})5
传入http.createServer
函数的this
就是app
;这个函数会在客户端发送请求的时候执行,所以在express
框架中,用户发送请求的时候执行的函数其实是app
app.use(中间件)内部到底发生了什么?
其实app.use
的本质是调用了router.use
方法,在内部将我们传递进去的中间件函数都放入到了一个stack
数组中,以这种方法帮我们注册了中间件
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})6
从Layer
构造函数中可以知道,其在创建实例的时候将对应的中间件函数的引用赋值给了layer.handle
属性
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})7
下面是lazyrouter
函数,主要做的事情就是注册了一个路由并赋值给了app._router
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})8
下面是router.use
函数,前面的操作跟app.use
的操作很类似,所以现在大家应该可以理解之前在创建路由当中为什么可以用router.use来创建中间件了吧,并不是router.use模仿了app.use,而是app.use基于router.use
app.get('/user/:name/:age', (req, res, next) => { console.log(req.params); // { name: 'haha', age: '18' } res.end('Hello')})9
用户发送了网络请求,中间件是如何被回调的?
刚刚我们探索了app.use
帮助我们注册中间件的本质其实就是把中间件函数和layer
绑定到了一起放到了stack
数组中去。其实中间件是如何被执行的也很简单,express
会在用户发送请求的时候遍历stack数组,找到第一个匹配的layer
之后,就去执行绑定在它身上的中间件函数layer.handle
,这样就实现了用户发送请求回调中间件的功能
首先我们要知道,当用户发送了网络请求之后,传入http.createServer
的参数会被执行,我们从app.listen
的源码中可以看到,我们其实是把app传入给了http.createServer函数,所以当用户发送请求的时候,http
模块会帮助我们执行app
函数
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})0
那app
函数里面到底有什么内容呢?不难发现,其本质上执行的是app.handle
函数
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})1
于是我们又找到了app.handle
函数,发现其调用的又是router
实例上的handle
方法
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})2
沿着代码的轨迹,我们来到了router
文件夹的index.js
文件中寻找router.handle
方法;为了让代码看起来更加简洁,我删除掉我们这次不讨论的源码
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})3
为了知道最终执行的layer.handle_request
函数到底执行了哪些操作,我们来到了router
文件夹下面的layer.js
文件;仔细阅读后发现,最终调用的就是我们绑定在layer
上面的中间件
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})4
至此,用户发送网络请求是怎么执行中间件的这个过程我们就已经探索完了
调用next为什么会执行下一个中间件?
从上面的代码中,可以看到在执行fn
函数的时候将req
、res
以及next
作为参数传递了进去,传递进去的next其实就是router.handle
中的next函数,所以我们在中间件函数中调用next,实际上调用的是router.handle中的next函数,这里涉及到了js
的闭包——可以访问到其他函数作用域中的变量的函数
因为闭包特性,所以next在执行的时候是可以访问到作用域链中的idx
变量的,这个变量记录了上一次执行的中间件函数在数组中的索引,所以当我们在中间件函数里面执行next函数的时候,会从idx开始寻找下一个匹配的中间件并执行
const express = require('express')const app = express()app.get('/user', (req, res, next) => { console.log(req.query); // { name: 'haha', age: '18' } res.end('Hello')})app.listen(3000, () => { console.log('express框架初体验');})5原文:https://juejin.cn/post/7103806298029817892