likes
comments
collection
share

Express核心

作者站长头像
站长
· 阅读数 13

一、 Web框架的功能

HTTP协议 - 万维网的基石

curl-s-v 网址

  • -s是silent,用于隐藏进度条
  • -v 是 verbose,用于打印全部header
  • -L 是重定向
  • -o nul 是为了隐藏HTML文本,内容太多不方便展示
    • 开头是注释
  • >开头是HTTP请求
  • < 开头是HTTP响应

Express核心

备注

  • “>”号开头的部分是请求
  • “<”号开头的部分是响应
  • 最后是响应体

Express核心

举例

  • curl -s -v http://www.baidu,com

请求和响应

请求

Express核心

分为四部分

  1. 请求行
  2. 请求头
  3. 回车
  4. 请求体/消息体

备注

如下所示

Express核心

Express核心

Express核心

Express核心

响应

Express核心

分为四部分

  1. 状态行
  2. 响应头
  3. 回车
  4. 响应体/消息体

备注

  • 如果响应体的内容为JSON
  • 那么响应头就要有Content-Type:application.json

HTTP的复杂性

  • HTTP复杂就复杂在他有很多请求头
  • 每个请求头或响应头功能各不相同,我们需要记住

Web框架

功能

  • 更方便地处理HTTP请求与响应
  • 更方便的连接数据库、Redis
  • 更方便的路由
  • 其他: HTML模板

理念

  • web框架的主流思想是MVC
  • Model处理数据相关逻辑
  • View处理视图相关逻辑,前后分离之后,view不重要(后端基本不管,交给前端去做)
  • Controller负责其他逻辑

架构示意

Express核心

备注

  • 比如爬虫的时候,爬百度或某个网站的搜索结果,这样就是使用你的代码去请求其他的web服务器
  • 我们的web服务器可能大部分时间都是在处理HTTP请求

二、Hello Express

处理HTTP请求与响应

最简单的封装

  • 将请求封装为[['get','/xxx'],{请求头},'请求体']
  • 将响应封装为[status,{请求头},'响应体']

Node.js的封装

  • 封装在http模块中;额
  • 使用request对象( Incoming Message的实例) 读取请求
  • 使用response对象 (ServerResponse的实例) 设置响应

Express的封装

  • 封装级别高一点点,只需理解 Express 的编程模型即可
  • 中文文档

express-demo-1

创建项目

  • 创建目录,用WebStorm或者VSCode打开
  • yarn init -y;git init
  • 添加.gitignore 文件
  • 提交到git,推送至Github

开始学习express

CRM学习法: Copy - Run - Modify

备注

  • 这是一个比较“古老的”Node.js的web框架,Node.js从v5版本已经不使用--save将包安装到依赖里

安装express

  • yarn add express 或者 npm i express
  • 上面两个命令二选一,不要混用

创建app.js

  • 内容Copy自文档中的Hello World 部分
  • Run 一下app.js,命令为node app.js
  • 打开http://localhost:3000或者使用curl http://localhost:3000
  • 最好 MOdify 几处代码,比如改内容、路径和端口等

Express核心

Express核心

如果修改了代码,在命令行中是需要使用Ctrl + C 中断,再重新运行node app.js,如果嫌麻烦,就使用node -dev app.js,如果没有安装node -dev就安装一下,这样修改完代码,就不用每次要重新运行文件了

const express = require('express')
const app = express()
const port = 8000

app.get('/xxx', (req, res) => {
    res.send('你好,Express!')
})

app.listen(port, () => {
    console.log(`现在监听的端口是 ${port}`)
})

Express核心

Express核心

Express核心

用CRM学到了什么?

app = express()

  • 这个app 应该是核心
  • app.get('/xxx',(req,res)=>{})用于对 GET /xxx 路径的请求做出响应,res.end()可以响应数据
  • app.listen(3000,fn) 开启端口监听

三、Hello TypeScript

使用TypeScript

准备工作

  • yarn global add typescript ts-node ts-node-dev 全局安装工具
  • yarn add @types/express 安装类型支持
  • 新建app2.ts将之前app.js中的代码粘贴过来
  • tsc --init,tsc是我们安装typescript后自带的一个命令,运行完就会创建一个tsconfig.json的文件
  • 修改tsconfigtarget(使用ES6) 和 nolmplicitAny(没有隐式的any,意思是不允许默认有any,如果想加自己手动加,用any你就不要用TS)
  • 将require改为 import (import才是导入导出的规范写法)

备注

express中完全没有TS的类型支持

Express核心

安装好了类型支持后,我们发现在@types文件件下就有了express

Express核心

如果不想一直结束的时候打分号,就要在webstorm中做如下配置,永远使用分号和单引号,然后使用快捷键ctrl + L 格式化一下代码

Express核心

运行

ts-node-dev app2.ts (加上-dev 修改完代码,支持自动刷新)

不管是ts-node 还是ts-node-dev都不能用于生产环境,只适合用于开发环境!!生产环境还是提前要把TS变成JS!!

四、Express的app对象是什么

使用IDE查看类型

  • 使用VSCode或WebStorm可以查看app对象的类型(使用TS写代码的好处就是可以通过单击,知道对象继承自哪些类)
  • 类型为Express接口
  • Express extends Application
  • Application extends EventEmitter,IRuter... ...(IRuter是最重要的!!)
  • 其中IRuter 包含了 get/post/put等方法
  • 有了TypeScript,都不用看文档了

Express核心

看下IRouter下有哪些方法:

Express核心

从上图可以看出IRouter中有很多我们需要的方法

如果要看源代码,需要去express/application.js下面看

Express核心

有了TS的类型声明之后,写代码就会有提示,如下图提示第二个参数就是一个RequestHandler,白色高亮部分就是当前参数的类型

Express核心

如果我们不知道RequestHandler是什么也没关系,我们可以通过单击post找到RequestHandler再单击RequestHandler,去看它的类型

Express核心

我们发现它是一个函数,有三个参数,分别是req,res,next

我们怎么知道req,res,next它们是什么类型呢,将鼠标悬上去

Express核心

我们发现,req是一个Request对象

怎么知道req下有哪些方法呢?

我们在webstorm中写入express.request,单击它去找它继承了哪些类,有哪些方法即可

避免重复创建

目前我们的代码实现了一个app.js和一个app2.ts,以后我们在新建目录的时候,会重复上面的事情,要创建tsconfig,package.json,gitignore和入口文件app.js或者app2.ts

为了避免每次新建项目,都要重复创建

我们不如新建一个仓库,将它放到仓库中,因为这些代码我们之前已经保存在一个仓库了,现在像想把它保存到另外一个仓库(本地一个仓库上传到远程的2个仓库中)

  1. git add .
  2. git commit -m 'xxx'
  3. github中新建一个仓库
  4. 复制git开头的路径

Express核心

  1. git remote add starter git@github.com:keepBlank/express-starter.git
  2. git push starter main
  3. 修改并提交README
  4. 以后如果有什么是每个项目要做的,就添加到这个仓库中来
  5. 弄好了之后,记得删除掉这个远程仓库(不能同时关联2个远程仓库)git remote remove starter

Express核心

这就是一个开源项目了

Express核心

五、Express脚手架

一键搞定项目目录

express-generator

安装

npm install -g express-generator

如果报错尝试

npm cache clean --force

使用

  • express -h 查看帮助
  • express --view-ejs . 注意有一个点 (使用ejs后端模板引擎)
  • 这句话用于创建文件, 点表示当前目录
  • 由于它会覆盖文件,所以要重新安装@types/express

Express核心

CRM学习法

为了防止每次修改代码都要重启服务,修改下package.json,使用node-dev运行文件

Express核心

  • yarn install; yarn start
  • 分析app.js,主要API为app.setapp.use
  • app.set用于改配置,app.use用于使用中间件
  • 记得提交可运行代码,防止后面改出问题

Express核心

Express核心

Express核心

六、用TypeScript开发Express引用

改写

  • 把app.js代码复制到app2.ts
  • yarn add @types/node --dev 这样才能使用require
  • yarn add @types/express --dev 再安装下express的类型声明文件
  • 你也可以用import代替require
  • 你也可以用const 代替var
  • RequestHandlerErrorRequestHandler断言
  • bin/www中的入口改为 app.ts
  • 修改package.json中的脚本。将node改为ts-node,因为node不认识ts的语法,所以不能使用node dev ./bin/www,而是改为ts-node-dev ./bin/www
  • 最后运行,yarn start

备注

Express核心

Express核心

在webstorm中使用Ctrl + R 匹配大小写,匹配全词,将var替换为const

Express核心

为什么TS不知道下面参数的类型呢?我们不是已经下载了类型声明文件了吗

Express核心

因为use接收不同类型的函数,它既可以接收三个参数的函数又可以使用4个参数的函数,因此它不知道当前的use的类型

我们可以使用as关键字,这个语法叫断言

Express核心

Express核心

目前只有app文件是ts,其他js文件能不能改成ts?

当然可以只要将js的后缀改为ts,消除代码中的警告和错误即可

改成TS并不难,就是得知道类型是什么,方法就是单击对象,跳转到ts类型声明文件中,看它是什么类型,再使用as加上断言即可

疑问

  • 为什么ts-node 可以运行JS
  • 答:本来就可以啊,只是添加了对TS的支持
  • 注意:不要在生产环境这样用

七、app.use 与 Express 编程模型

app.use()

use是使用的意思,它到底使用了什么?

理解app.use

创建新目录 express-demo-2

  • 使用express-starter-1
  • 尝试使用req.urlres.send
  • 多次使用会怎样?会报错,为什么?(不能两次以上send)第一个请求完成之后,要使用next(),并且响应只能响应一次,即只能写一次response.end()
  • 改成res.write,还记得流吗

使用浏览器发请求,只能得到一个响应(这是浏览器的问题,加上res.end就显示正常了)

使用curl发送请求收到了两个响应

Express核心

  • 为什么不会关闭呢?告诉Node.js我们写完了,加上res.end()试试,这样浏览器就不会一直在等服务端了
  • next什么时候可以省略?只有最后一个可以省略next(),第一个第二个不能省,因为省了就走不到下一个去了,最后一个没有下一个,因此可以不用写next()
const express = require('express');
const {request} = require('express');

const app = express();

app.use((request, response, next) => {
    console.log(request.url);
    response.write('Hi,I am Server Client')
    next()
});

app.use((request, response, next) => {
    console.log(2);
    response.write('Hi,I am Server Client 2')
    next()
});

app.use((request, response, next) => {
   response.end()
});

app.listen(3000,()=>{
    console.log('正在监听3000端口');
});

备注

有点像顺序执行的感觉,第一个执行完,就调用next(),执行第二个,执行完,再调用next(),最后执行response.end() 表示数据写完了,结束了

最后一个,虽然没必须要再加next(),但是习惯上还是加上,也避免webstorm警告报错

express的编程模型

理解并记住下面的图,就明白了Expressd的核心了

Express核心

图中的fn就是下图中的函数

Express核心

八、中间件与路由

fn就是中间件,因为它是被插入到启动和结束中间的物件

除了我们自己写的中间件外,还提供了现成的中间件

Express核心

上图中,每个app.use()里面的函数调用返回的结果都是函数

那为什么每个函数都要调用一下再返回新的函数,而不是直接使用新的函数呢?

因为使用函数调用的方式,方便我们传选项,如

app.use(cookieParser({...}));

一般来说,都是给我们一个函数,我们调用一下这个函数,然后这个参数会根据我们传的参数,返回新的函数,这个新的函数就是中间件fn

优点

由于Express拥有上面所说的编程模型,所以它有以下的优点:

模块化

  • 这种模型使得每个功能都能通过一个函数实现,每个函数就相当于一个模块
  • 然后通过app.use 将这个函数整合起来
  • 如果把函数放到文件或发布到npm,它就变成了单独的包或者说模块了,这样也就实现了模块化

比如:我们现在实现一个独立的模块logger:“任何人访问的时候,就打印它的访问路径”

// logger.js
const logger = function (request,response,next){  
console.log(request.url);  
    next()  
}  
module.exports = logger
// app.js
const logger = require('./logger.js');   
app.use(logger);

运行node-dev app.js

Express核心

那如果现在想在返回的路径前加上其他的前缀呢?

此时,logger就不应该是一个函数了,而是返回这个函数的函数,是它在调用的时候支持传参

// logger.js
const logger = prefix => {  
    return (request,response,next)=>{  
        console.log(`${prefix}:${request.url}`);  
        next()  
    }  
}  
module.exports = logger
// app.js
const logger = require('./logger.js'); 
const fn = logger('dev')
app.use(logger);

运行node-dev app.js

Express核心

函数fn就是我们返回的新的函数,新的函数会在log的时候,在路径前加上dev

其中

const fn = logger('dev')
app.use(logger);

可以简写成app.use(logger('dev'))

这样logger的功能就完全的独立于代码主逻辑之外

这就是中间件的好处,只要我们把它放在中间,它就自动的去做它自己该做的事,我们无需知道它具体是怎么做到的,我们只需要知道它的功能,使用的时候给它传个参数就可以了

现在我们也能看明白了之前看到的代码是什么意思了

Express核心

以 express-demo-1举例

  • app.use(logger('dev'))
  • logger('dev')会返回一个函数
  • 这个函数会在每次请求到达的时候,打印出信息
  • 我们根本就不用去了解它是怎么做到的
  • 我们也可以很快做出类似的模块

路由

假如现在想获取用户在输入三个不同的路径的时候,得到三个不同的字符串

webstorm的代码模板功能

如果觉得每次都要写下面这段代码

app.use((request,response,next)=>{  
  
})

我们可以借助webstorm中的模板功能,将常用的重复的代码,添加到模板中

在设置中搜索live template

Express核心

Express核心

其中$END$的意思就是光标在这个位置自动的停住

当我们在webstorm中敲app.use的时候就会自动的补全代码

使用app.use如何实现路由

格式:

app.use((request,response,next)=>{  
    if(request.path === '/' && request.method === 'get' ){ 
        response.write('home')  
    }  
    next()  
}) 

示例代码

app.use((request,response,next)=>{  
    if(request.path === '/' && request.method === 'get' ){  
        response.send('根目录')  
    }  
    next()  
})  
  
app.use((request,response,next)=>{  
    if(request.path === '/xxx' && request.method === 'get'){  
        response.send('这是xxx')  
    }  
    next()  
})  
  
app.use((request,response,next)=>{  
    if(request.path === '/yyy' && request.method === 'get'){  
        response.send('这是yyy')  
    }  
    next()  
})

备注

  • url是包括?xxx后面的查询字符串的,如果不想要?后面的查询字符串应该使用path
  • 每个app.use()都相当于独立的模块
  • Express的模型有一个非常好的好处就是,我们可以把上面三个模块中国的每个模块单独的拎出来,分别放到index.jsxxx.jsyyy.js
  • 所以与其说Express是一个Web框架,不如说它是一个专注于做中间件的框架,它使得我们可以把所有的功能都以中间件的形式插入到app中

Express核心

Express核心

Express核心

更方便的写法

  • app.use('/xxx',fn),当然路径也可以改为正则
  • app.get('/xxx',fn)
  • app.post('/xxx',fn)
  • app.route('/xxx').all(f1).get(f2).post(f3)
  • 这些都是API糖

备注

  • app.route('/xxx').all(f1).get(f2).post(f3)解释下这句话的意思
  • .all(fn)的意思是不管请求的方法使用的是get还是post都会执行函数fn
  • .all后面的.get.post它们之间是或的关系,如果是get请求就走get的逻辑,如果是post请求就走post的逻辑,如果都不是就走all的逻辑

九、错误处理

Express核心

为什么要错误处理?有的时候在中间的时候就想要它停下来,如上图所示,在执行完fn1和fn2后发现它不是一个登录用户,那我们就不能给它展示页面内容了,我们应该提示他去另外一个地方或者说这个用户不是一个管理员我们应该显示一个错误页面,你没有权限

也就是说我们需要在中间中断,不要再执行后面的了

如何让它中断呢?next()就可以做到

app.use((request,response,next)=>{  
    response.write('1')  
    next()  
})  
  
app.use((request,response,next)=>{  
    response.write('2')  
    if(true){  
        next('Not Login')  
    }else{  
        next()  
    }  
})  

app.use((request,response,next)=>{  
    response.write('3')  
    next()  
})  

app.use((error,request,response,next)=>{  
    response.write(error)  
    response.end()  
    next()  
})

备注

  • next('Not Login')中如果传了参数,就表示当前出了错误,就不要走后面的其他的函数了,直接走到错误处理函数
  • 那么它就会进入带error的四个参数的回调函数中

Express核心

next()能传参数吗?

  • 可以看文档,也可以看Typescript定义
  • 推荐后者

在webstorm中通过点击use进行跳转,一层层查找到了next函数中的err,其中的?表示可有可无,类型是any,返回值是空。因此我们可以在error中传字符串next('Not Login'),字符串也可以当error

即next()接收一个任意类型的错误,返回值为空

Express核心

Express核心

next(error)

  • 会直接进入errorHandler,不执行后面的中间件
  • errorHandler的默认实现 见文档

有时候想看对应的中文文档,文档的下方一般有支持的语言。但是一般情况下建议看英文的文档,因为中文的文档有时候翻译的不全或者翻译的不准确

Express核心

用官方的代码,实现以下错误

app.use((request,response,next)=>{  
    console.log('1');  
    next()  
})  
  
app.use((request,response,next)=>{  
    console.log('2');  
    if(true){  
        next('Not Login')  
    }else{  
        next()  
    }  
})  

app.use((request,response,next)=>{  
    console.log('3');  
    next()  
})  

app.use((error,request,response,next)=>{  
    if (response.headersSent) {  
        return next(error)  
    }  
    response.status(500)  
    response.send('未登录')  
})

Express核心

如何定义errorHandler

  • 还是看文档,文档说一般在最后定义
  • app.use((err,req,res,next)=>{}),定义错误中间件的时候,我们只需要在use的时候,多加一个err即可
  • 可以定义多个这样的中间件

备注: 错误处理中间件

  • 同普通的中间件一样,只不过它是专门用来处理错误的

  • 多个错误处理程序就像中间件一样,一字排开,逐个执行,先执行第一个,再执行第二个...。这就是为什么错误处理程序中要有next,因为你可以对错误进行多次处理。

  • 注意在使用多个error处理程序时候,要错误放进next中传给下一个错误处理程序next(error),否则后面的错误处理程序就不知道错误是什么

app.use((request,response,next)=>{  
    console.log('1');  
    next()  
})  
  
app.use((request,response,next)=>{  
    console.log('2');  
    if(true){  
        next('Not Login')  
    }else{  
        next()  
    }  
})  

app.use((request,response,next)=>{  
    console.log('3');  
    next()  
})  

app.use((error,request,response,next)=>{  
    console.log(error);  
    next(error)  
})  

let count = 0  
app.use((error,request,response,next)=>{  
    count += 1  
    console.log(`目前有${count}个错误`);  
    next(error)  
})

每次刷新页面发起请求,count + 1

Express核心

这就是多个错误处理的写法,一定要将错误传给后面next(error),否则后面的错误处理不知道,就像800米接力跑一样,这里的error相当于接力棒,我们得把接力棒传给下一位

next('route')

这是一个特殊的参数

  • 见源码中的next函数的定义
  • 很少用到
  • 可以看看文档中的例子
  • 主要要学会如何看源码

一般什么时候去看源码?

如果是为了看源码而看源码就是一个非常低效的方法

当我们在遇到某一个具体的问题的时候,就会产生应用场景,这时候如果带着问题去查文档,效果会好很多。遇到问题就去翻源码,长此以往,源码就渐渐熟悉了

查源码的方法就是,一个个去找,使用ctrl + f 模糊搜索关键字,查找自己想查找的方法,看源代码是如何定义的

文档中是这么说的

Express核心

转载自:https://juejin.cn/post/7253287098063716408
评论
请登录