0822 NOTE
资料参考:
nodejs的中间件
1、中间件就是一种功能的封装方式,就是封装在程序中处理http请求的功能,
2、中间件是在管道中执行
3、中间件有一个next()函数,如果不调用next函数,请求就在这个中间件中终止
4、中间件和路由处理器的参数中都有回调函数,这个函数有2,3,4个参数
如果有两个参数就是req和res;
如果有三个参数就是req,res和next
如果有四个参数就是err,req,res,next
5、如果不调用next ,管道就会终止,不会再有处理器做后续响应,应该向客户端发送一个响应
6、如果调用了next,不应该发送响应到客户端,如果发送了,则后面发送的响应都会被忽略
7、中间件的第一个参数可以是路径,如果忽略则全部都匹配
中间件的书写方法
function middleware(req,res,next){next()}
在Express框架中,使用应用程序实例对象的use方法调用中间件
app.use([path],function)
use方法中,使用两个参数,其中path参数为可选参数,function参数为必须指定参数。path参数值为一个字符串,用于指定何种路径应用中间件,默认参数值为“/”。function参数值为一个函数,用于指定我们所要调用的中间件函数。
中间件的基本使用
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
app.get('/request', (req, res, next) => {
req.name = 'zhangsan';
next(); // 向下执行
})
app.get('/request', (req, res)=> {
res.send(req.name);
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
app.use中间件用法
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
// 接收所有的请求的中间件
app.use((req, res, next) => {
console.log('请求走了 app.use 中间件');
//next();
})
// 当客户端访问 /request 请求的时候走当前中间件
app.use('/request', (req, res, next) => {
console.log('请求走了 app.use /request 中间件 ');
//next();
})
app.get('/request', (req, res, next) => {
req.name = 'zhangsan';
next();
})
app.get('/request', (req, res)=> {
res.send(req.name);
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
中间件应用
路由保护
客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
app.use('/admin', (req, res, next) => {
// 用户没有登录
let isLogin = false;
// 如果用户登录,true则登录
if (isLogin) {
// 让请求继续向下执行
next();
} else {
// 如果用户没有登录,直接对客户端作出响应
res.send('您还没有登录,无法访问当前页面');
}
})
app.get('/admin', (req, res) => {
res.send('您已登录 可以访问当前页面')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
网站维护公告
在所有路由最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
app.use((req, res, next) => {
res.send('当前网站正在维护...');
})
app.use('/admin', (req, res, next) => {
// 用户没有登录
let isLogin = true;
// 如果用户登录
if (isLogin) {
// 让请求继续向下执行
next();
} else {
// 如果用户没有登录,直接对客户端作出响应
res.send('您还没有登录,无法访问当前页面');
}
})
app.get('/admin', (req, res) => {
res.send('您已登录 可以访问当前页面')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
自定义404页面
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
app.use('/admin', (req, res, next) => {
// 用户没有登录
let isLogin = true;
// 如果用户登录
if (isLogin) {
// 让请求继续向下执行
next();
} else {
// 如果用户没有登录,直接对客户端作出响应
res.send('您还没有登录,无法访问当前页面');
}
})
app.get('/admin', (req, res) => {
res.send('您已登录 可以访问当前页面')
})
app.use((req, res, next) => {
// 为客户端响应404状态码以及提示信息
res.status(404).send('当前访问的页面是不存在的');
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
错误中间件的处理
在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如读取文件失败,数据库连接失败。
错误处理中间件是一个集中处理错误的地方。
显示服务器错误
app.use((err, req, res, next) => {
res.status(500).send('服务器发生错误');
})
举例
// 引入 express 框架
const express = require('express')
// 创建网站服务器
const app = express();
app.get('/index', (req, res) => {
// 创建一个错误实例并抛出
throw new Error('程序发生了未知错误');
})
// 错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message);
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
当程序出现错误时,调用 next()方法,并且将错误信息通过参数的形式传递给 next()方法,即可触发错误处理中间件。
// 引入 express 框架
const express = require('express')
// 引入文件模块
const fs = require('fs');
// 创建网站服务器
const app = express();
app.get('/index', (req, res, next) => {
// 创建一个错误实例并抛出
// throw new Error('程序发生了未知错误');
fs.readFile('./demo.txt', 'utf8', (err, result) => {
if (err != null) {
// 文件读取失败 向下传递错误对象
next(err);
} else {
// 文件读取成功
res.send(result);
}
})
// res.send('程序正常')
})
// 错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message);
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
//demo.txt 的文件时不存在的,读取失败后会将错误对象通过 next ()方法传递给错误处理中间件,显示相应的错误信息。
捕获错误
在 node.js中,异步API的错误信息都是通过回调函数获取的,支持 Promise 对象的异步API发生错误可以通过catch方法捕获。
try catch 可以捕获异步函数以及其他同步代码执行过程中发生的错误,但是不能捕获其他类型的错误(回调函数的错误、Promise对象的错误)。
// 引入 express 框架
const express = require('express')
// 引入文件模块
const fs = require('fs');
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
// 创建网站服务器
const app = express();
app.get('/index', async (req, res, next) => {
try {
await readFile('./aaa.js');
} catch (ex) {
// 传递错误信息
next(ex);
}
})
// 错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message);
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
express请求处理
构建模块化路由
基础语法:
// 引入 express 框架
const express = require('express');
// 创建网站服务器
const app = express();
// 创建路由对象
const home = express.Router();
// 为路由对象匹配请求路径
app.use('/home', home);
// 创建二级路由
home.get('/index', (req, res) => {
res.send('欢迎来到博客首页');
})
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
模块化路由构建案例
在 入口文件 app.js 文件的同级目录新建 route 文件夹在 route 文件夹下 新建 home.js 文件 以及 admin.js 文件
- 在 route 文件夹中构建不同的路由模块,放在不同的文件中
- 通过module.exports 将不同路由模块的路由对象导出
- 在 app.js 文件中通过 require 将不同路由模块的路由对象导入,同时对导入的路由进行路由匹配
//home.js
const express = require('express');
const home = express.Router();
home.get('/index', (req, res) => {
res.send('欢迎来到博客首页页面');
});
// 导出 home 这个路由对象
module.exports = home;
//admin.js
const express = require('express');
const admin = express.Router();
admin.get('/index', (req, res) => {
res.send('欢迎来到博客管理页面');
});
// 导出 admin 这个路由对象
module.exports = admin;
//app.js
// 引入 express 框架
const express = require('express');
// 创建网站服务器
const app = express();
const home = require('./route/home');
const admin = require('./route/admin');
app.use('/home', home);
app.use('/admin', admin);
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
GET参数的获取
Express 框架中使用 req.query
即可 获取GET参数
,框架内部会将GET参数转换为对象
并返回。
// 引入 express 框架
const express = require('express');
// 创建网站服务器
const app = express();
app.get('/index', (req, res) => {
// req.query 获取请求参数
res.send(req.query)
})
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
// http://localhost:3000/index?name=kaige&age=20&gender=男
POST参数的请求
Express 中接收 POST请求参数
需要借助第三方包 body-parser
// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();
// 拦截所有请求
// extended: false 方法内部使用 querystring 模块处理请求参数的格式
// extended: true 方法内部使用第三方模块 qs 来处理请求参数的格式
app.use(bodyParser.urlencoded({extended: false}));
app.post('/add', (req, res) => {
res.send(req.body);
})
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
<!--post.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="http://localhost:3000/add" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
</body>
</html>
app.use()的进一步理解
app.use()调用外部定义函数:
// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();
// 拦截所有的请求
app.use(fn ());
function fn () {
return function (req, res, next) {
console.log(req.method);
next();
}
}
app.get('/', (req, res) => {
// 接收 post 请求参数
res.send('ok');
})
app.post('/add', (req, res) => {
res.send(req.body);
})
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
优点:
可以在调用 fn 函数的同时,向 fn 内部传递一些额外的参数,在请求处理函数内部可以根据这个参数改变请求处理函数的行为。
// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();
app.use(fn ({a: 1}));
function fn (obj) {
return function (req, res, next) {
if (obj.a == 1) {
console.log(req.url);
} else {
console.log(req.method);
}
next();
}
}
app.get('/', (req, res) => {
// 接收 post 请求参数
res.send('ok');
})
app.post('/add', (req, res) => {
res.send(req.body);
})
// 监听端口
app.listen(3000);
console.log('服务器开启成功');
SSR
(Server Side Rendering) :传统的渲染方式,由服务端把渲染的完整的页面吐给客户端。这样减少了一次客户端到服务端的一次http请求,加快相应速度,一般用于首屏的性能优化。
SSR优点
例如SEO–因为访问一个请求,返回的就是页面全部的HTML结构,包含所需要呈现的所有数据,于是例如搜索引擎或者爬虫的数据抓取;
目前使用MV*架构的项目,大都是前后端分离,数据都是动态生成,不利于SEO优化 利于首屏渲染性能高–首屏的页面加载来自于服务器,不依赖与服务端的接口请求再数据处理;
SSR缺点
性能全都依赖于服务器 前端界面开发可操作性不高
CSR
CSR(Client Side Rendering):是一种目前流行的渲染方式,它依赖的是运行在客户端的JS,用户首次发送请求只能得到小部分的指引性HTML代码。第二次请求将会请求更多包含HTML字符串的JS文件。
CSR优点
FP最快 客户端体验较好,因为在数据没更新之前,页面框架和元素是可以在dom生成的
FP:仅有一个 div 根节点。以VUE为例,div#app 注册一个空的div FCP:包含页面的基本框架,但没有数据内容。以VUE为例,每个template中的div框架,对应VUE生命周期的mounted FMP:包含页面所有元素及数据。以VUE为例,通过接口更新到页面的数据后完整的页面展示;对应VUE的生命周期中的updated
简而言之,就是数据拼接HTML字符串这件事放在服务端还是客户端造成了两者区别。
CSR缺点
不利于SEO–爬虫数据不好爬呀~~ 整体加载完速度慢
两者有何不同
服务器端渲染的优势在于首屏渲染速度块,简单来讲它不需要来回多次往返于客户端和服务端。但是其性能等众多因素会影响用户体验,比如说:网速,在线活跃人数,服务器的物理位置等等。而客户端渲染则和服务端渲染相反,因为多次和服务器的交互导致首屏加载速度慢。但一旦这些请求完成之后,用户和页面之间的交互时用户体验就会好很多。
用一个现实生活的例子来看:假如从超市买东西吃,以SSR的角度来看,你每次在超市买完随即吃完再走,每次饿了都需要出发去超市。而从CSR的角度来看,就是你从超市购买许多原材料再拿回家去自己煮,多了能放冰箱,这样每次肚子饿了就不需要每次都往超市跑,唯一麻烦一点在于你得花时间挑选食材。
优化方案
1、优化首屏加载,减少白屏时间,提升加载性能
2、加速或减少HTTP请求损耗:使用CDN加载公用库,使用强缓存和协商缓存,使用域名收敛,小图片使用Base64代替,使用Get请求代替Post请求,设置 Access-Control-Max-Age 减少预检请求,页面内跳转其他域名或请求其他域名的资源时使用浏览器prefetch预解析等;
(CDN:CDN(Content Delivery Network)是指内容分发网络,也称为内容传送网络)
3、延迟加载:非重要的库、非首屏图片延迟加载,SPA的组件懒加载等;
4、减少请求内容的体积:开启服务器Gzip压缩,JS、CSS文件压缩合并,减少cookies大小,SSR直接输出渲染后的HTML等;
5、浏览器渲染原理:优化关键渲染路径,尽可能减少阻塞渲染的JS、CSS;
6、优化用户等待体验:白屏使用加载进度条、loading图、骨架屏代替等;
强缓存和协商缓存
强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间
协商缓存:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间;当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存;其中Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识
Access-Control-Max-Age:缓存可以被缓存的时间
SPA 只有一张Web页面的应用,是加载单个HTML页面并在用户与应用程序交互时动态更新该页面的Web应用程序,是指在浏览器中运行的应用,在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理Web服务器”
域名发散
域名发散就是为了突破浏览器对于同一域名并发请求数的限制,使用域名发散为同一个服务申请多个域名,从而可以一定程度上提高并发量;当然,由于建立新的请求需要一定的代价,因此需要在域名发散与域名收敛间进行trade off,通常发散的域名个数为2-4个;
域名收敛
域名收敛就是将静态资源放在一个域名下不进行发散,这主要是为了适应移动端的发展需求;通常DNS是一个开销较大的操作,而移动端由于网络带宽和实时性、资源等的限制,这些开销对移动端的用户体验是致命的,因此需要进行域名收敛;
域名发散是pc端为了利用浏览器的多线程并行下载能力。而域名收敛多用与移动端,提高性能,因为dns解析是是从后向前迭代解析,如果域名过多性能会下降,增加DNS的解析开销。
DNS 预解析:浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。
<link rel="dns-prefetch" href="www.ytuwlg.iteye.com" />
Gzip页面压缩,像服务器发送压缩文件,同时服务器需要设置解析
简而言之,SSR强在首屏渲染。而CSR强在用户和页面多交互的场景
nodejs - artTemplate模板
参考资料:
1. 模板引擎的基础概念
1.1 模板引擎
模板引擎是第三方模块。
让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护。
// 未使用模板引擎的写法
var ary = [{ name: '张三', age: 20 }];
var str = '<ul>';
for (var i = 0; i < ary.length; i++) {
str += '<li>\
<span>'+ ary[i].name +'</span>\
<span>'+ ary[i].age +'</span>\
</li>';
}
str += '</ul>';
<!-- 使用模板引擎的写法 -->
<ul>
{{each ary}}
<li>{{$value.name}}</li>
<li>{{$value.age}}</li>
{{/each}}
</ul>
1.2 art-template模板引擎
1.在命令行工具中使用 npm install art-template 命令进行下载
2.使用const template = require**(‘art-template’)**引入模板引擎
3.告诉模板引擎要拼接的数据和模板在哪 const html = template(‘模板路径’, 数据);
4.使用模板语法告诉模板引擎,模板与数据应该如何进行拼接
1.3 art-template代码示例
const template = require('art-template');
const path = require('path');
const views = path.join(__dirname, 'views', '01.art');
const html = template(views, {
name:'张三',
age:'18'
});
console.log(html);
<div>
<span>{{data.name}}</span>
<span>{{data.age}}</span>
</div>
2. 模板引擎的语法
2.1 模板语法
- art-template同时支持两种模板语法:标准语法和原始语法。
- 标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力。
标准语法: {{ 数据 }}
原始语法:<%=数据 %>
2.2 输出
将某项数据输出在模板中,标准语法和原始语法如下:
- 标准语法:{{ 数据 }}
- 原始语法:<%=数据 %>
<!-- 标准语法 -->
<h2>{{value}}</h2>
<h2>{{a ? b : c}}</h2>
<h2>{{a + b}}</h2>
<!-- 原始语法 -->
<h2><%= value %></h2>
<h2><%= a ? b : c %></h2>
<h2><%= a + b %></h2>
2.3 原文输出
如果数据中携带HTML标签,默认模板引擎不会解析标签,会将其转义后输出。
- 标准语法:{{@ 数据 }}
- 原始语法:<%-数据 %>
<!-- 标准语法 -->
<h2>{{@ value }}</h2>
<!-- 原始语法 -->
<h2><%- value %></h2>
2.4 条件判断
<!-- 标准语法 -->
{{if 条件}} ... {{/if}}
{{if v1}} ... {{else if v2}} ... {{/if}}
<!-- 原始语法 -->
<% if (value) { %> ... <% } %>
<% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>
2.5 循环
- 标准语法:{{each 数据}} {{/each}}
- 原始语法:<% for() { %> <% } %>
<!-- 标准语法 -->
{{each target}}
{{$index}} {{$value}}
{{/each}}
<!-- 原始语法 -->
<% for(var i = 0; i < target.length; i++){ %>
<%= i %> <%= target[i] %>
<% } %>
示例:
const template = require('art-template');
const path = require('path');
const views = path.join(__dirname, 'views', '03.art');
const html = template(views, {
users:[
{
name:'li',
age:18
},{
name:'wang',
age:20
},{
name:'zhang',
age:30
}
]
});
console.log(html);
<ul>
{{each users}}
<li>
{{$index}}
{{$value.name}}
{{$value.age}}
</li>
{{/each}}
</ul>
2.6 子模版
使用子模板可以将网站公共区块(头部、底部)抽离到单独的文件中。
- 标准语法:{{include ‘模板’}}
- 原始语法:<%include(‘模板’) %>
<!-- 标准语法 -->
{{include './header.art'}}
<!-- 原始语法 -->
<% include('./header.art') %>
2.7 模板继承
使用模板继承可以将网站HTML骨架抽离到单独的文件中,其他页面模板可以继承骨架文件。
模板继承示例
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HTML骨架模板</title>
{{block 'head'}}{{/block}}
</head>
<body>
{{block 'content'}}{{/block}}
</body>
</html>
<!--index.art 首页模板-->
{{extend './layout.art'}}
{{block 'head'}} <link rel="stylesheet" href="custom.css"> {{/block}}
{{block 'content'}} <p>This is just an awesome page.</p> {{/block}}
2.8 模板配置
- 向模板中导入变量 template.defaults.imports.变量名 = 变量值;
- 设置模板根目录 template.defaults.root = 模板目录
- 设置模板默认后缀 template.defaults.extname = ‘.art’
CMS案例
<!--client-->
<!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>
</head>
<body>
<form>
<div>
<label>title:</label><input type="text" name="title">
</div>
<div>
<label>icon:</label><input type="file" name="file">
</div>
<div>
<label>price:</label><input type="text" name="price">
</div>
<div>
<label>num:</label><input type="text" name="num">
</div>
<div>
<label>production:</label><input type="text" name="production">
</div>
<button type="submit">提交</button>
</form>
<script>
var form;
init();
function init(){
form=document.querySelector("form");
form.addEventListener("submit",submitHandler);
}
function submitHandler(e){
e.preventDefault();
var xhr=new XMLHttpRequest()
xhr.open("POST","http://10.9.23.91:4010");
xhr.addEventListener("load",loadHandler);
xhr.send(new FormData(form));
}
function loadHandler(e){
}
</script>
</body>
</html>
//app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var multer=require("multer")();
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.engine('art', require('express-art-template'));
app.set("views",path.join(__dirname,"./views"));
app.set("view engine","art");
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
//package.json
{
"name": "server",
"version": "0.0.0",
"private": true,
"main": "./bin/www",
"scripts": {
"start": "cross-env PORT=4010 nodemon"
},
"devDependencies": {
"cross-env": "~7.0.3",
"nodemon": "~2.0.12"
},
"dependencies": {
"art-template": "~4.13.2",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"express-art-template": "~1.0.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"multer": "^1.4.3",
"multiparty": "^4.2.2"
}
}