渲染优化
渲染优化是前端优化中一个很重要的部分,一个好的首屏时间能给用户带来很好的体验,这里要说的一点是关于首屏时间的定义,不同的团队对首屏时间定义不一样,有的团队认为首屏时间就是白屏时间,是从页面加载到第一个画面出现的时间。但是当我们说到用户体验的时候,仅仅是这样还达不到效果,所以有的前端团队认为,首屏时间应该是从页面加载到用户可以进行正常的页面操作时间,那么我们就依照后者来进行说明
js css 加载顺序
说渲染优化之前,我们还需要说一个小插曲,就是比较经典的一道问题
浏览器地址栏输入url发生了什么?
理解了这个我们才可以更清楚js,css加载顺序对渲染的影响
地址栏输入url 发生了什么
这个问题经常被人提起(前面会有更加详细的文章)这里再来简述一下
- 首先会进行 url 解析,根据 dns 系统进行 ip 查找,根据 ip 就可以找到服务器
- 然后浏览器和服务器会进行 TCP 三次握手建立连接
- 如果此时是 https 的话,还会建立 TLS 连接以及协商加密算法,这里就会出现另一个需要注意的问题"https 和 http 的区别"(翻翻看有详细说明)
- 连接建立之后浏览器开始发送请求获取文件
- (可以忽略)此时这里还会出现一种情况就是缓存,建立连接后是走缓存还是直接重新获取,需要看后台设置,所以这里会有一个关注的问题"浏览器缓存机制",缓存我们等会在讲,现在我们就当没有缓存,直接去获取文件
- 首先获取 html 文件,构建 DOM 树,这个过程是边下载边解析,并不是等 html 文件全部下载完了,再去解析 html,这样比较浪费时间,而是下载一点解析一点
好了解析到 html 头部时候,又会出现一种问题,css,js 放到哪里了?不同的位置会造成渲染的不同,此时就会出现另一个需要关注的问题"css,js 位置应该放哪里?为什么",我们先按照正确的位置来说明(css 放头部,js 放尾部) - 解析到了 html 头部发现有 css 文件,此时下载 css 文件,css 文件也是一边下载一边解析的,构建的是 CSSOM 树,当 DOM 树和 CSSOM 树全部构建完之后,浏览器会把 DOM 树和 CSSOM 树构建成渲染树。
- 样式计算, 上面最后一句"DOM 树和 CSSOM 树会一起构建成渲染树"说的有点笼统,其实还有更细一点的操作,但是一般回答到上面应该就可以了,我们现在接上面说一下构造渲染树的时候还做了哪些事情。第一个就是样式计算,DOM树 和 CSSOM树有了之后,浏览器开始样式计算,主要是为 DOM 树上的节点找到对应的样式
- 构建布局树,样式计算完之后就开始构建布局树。主要是为 DOM 树上的节点找到页面上对应位置以及一些"display:none"元素的隐藏。
- 构建分层树,布局树完成后浏览器还需要建立分层树,主要是为了满足滚动条,z-index,position 这些复杂的分层操作
- 将分层树图块化,利用光栅找到视图窗口下的对应的位图。主要是因为一个页面可能有几屏那么长,一下渲染出来比较浪费,所以浏览器会找到视图窗口对应的图块,将这部分的图块进行渲染
最终渲染进程将整个页面渲染出来,在渲染的过程中会还出现重排和重绘,这也是比较爱问的问题
重排重绘为什么会影响渲染,如何避免?
以上过程大概讲解了一下从 url 到页面渲染的整个过程,其实涉及到了几个需要关注的问题,下面来具体讲讲
js css 顺序对前端优化影响
- 上面我们说到了整个渲染流程,但是没有说到 css 和 js 对渲染的影响。渲染树的构成必须要 DOM 树和 CSSOM 树的,所以尽快的构建 CSSOM 树是一个重要的优化手段,如果 css 文件放在尾部,那么整个过程就是一个串行的过程先解析了 dom,再去解析 css。所以 css 我们一般都是放在头部,这样 DOM 树和 CSSOM 树的构建是同步进行的。
- 再来看 js,因为 js 的运行会阻止 DOM 树的渲染的,所以一旦我们的 js 放在了头部,而且也没有异步加载这些操作的话,js 一旦一直在运行,DOM 树就一直构建不出来,那么页面就会一直出现白屏界面,所以一般我们会把 js 文件放在尾部。当然放到尾部也不是就没有问题了,只是问题相对较小,放到尾部的 js 文件如果过大,运行时间长,代码加载时,就会有大量耗时的操作造成页面不可点击,这就是另一个问题,但这肯定比白屏要好,白屏是什么页面都没有,这种是页面有了只是操作不流畅。
- js 脚本放在尾部还有一个原因,有时候 js 代码会有操作 dom 节点的情况,如果放在头部执行,DOM树还没有构建,拿不到 DOM 节点但是你又去使用就会出现报错情况,错误没处理好的话页面会直接崩掉
重排重绘为什么会影响渲染,如何避免?
重排和重绘为什么会影响渲染,哪个影响更大,如何避免是经常被问到的一道题目,我们先来说一下重绘
4. 重绘
重绘指的是不影响界面布局的操作,比如更改颜色,那么根据上面的渲染讲解我们知道,重绘之后我们只需要在重复进行一下样式计算,就可以直接渲染了,对浏览器渲染的影响相对较小
5. 重排
重排指的是影响界面布局的操作,比如改变宽高,隐藏节点等。对于重排就不是一个重新计算样式那么简单了,因为改变了布局,根据上面的渲染流程来看涉及到的阶段有样式计算,布局树重新生成,分层树重新生成,所以重排对浏览器的渲染影响是比较高的
避免方法
- js 尽量减少对样式的操作,能用 css 完成的就用 css
- 对 dom 操作尽量少,能用 createDocumentFragment 的地方尽量用
如果必须要用 js 操作样式,能合并尽量合并不要分多次操作 - resize 事件 最好加上防抖,能尽量少触发就少触发
- 加载图片的时候,提前写好宽高
浏览器缓存机制
浏览器缓存是比较常见的问题,我会从浏览器缓存的方式,缓存的实现, 缓存在哪里这几个点来说明
缓存方式
我们经常说的浏览器缓存有两种,一种是强制缓存,一种是协商缓存,因为下面有具体实现讲解,所以这里就说一下概念
- 协商缓存
协商缓存意思是文件已经被缓存了,但是否从缓存中读取是需要和服务器进行协商,具体如何协商要看请求头/响应头的字段设置,下面会说到。需要注意的是协商缓存还是发了请求的 - 强制缓存
强制缓存就是文件直接从缓存中获取,不需要发送请求
缓存实现
- 强制缓存
强制缓存在 http1.0 的时候用的是 Expires,是响应头里面的一个字段表示的是文件过期时间。是一个绝对时间,正因为是绝对时间所以在某些情况下,服务器的时区和浏览器时区不一致的时候就会导致缓存失效。为了解决这个问题,HTPP1.1 引入了一个新的响应头 cache-control 它的可选值如下
cache-control
max-age: 缓存过期时间,是一个相对时间
public: 表示客户端和代理服务器都会缓存
private: 表示只在客户端缓存
no-cache: 协商缓存标识符,表示文件会被缓存但是需要和服务器协商
no-store: 表示文件不会被缓存
HTTP1.1 利用的就是 max-age:600 来强制缓存,因为是相对时间,所以不会出现 Expires 问题
- 协商缓存
协商缓存是利用
Last-Modified/if-Modified-Since
Etag/if-None-Match
这两对请求、响应头。
Last-Modified/if-Modified-Since
Etag/If-None-Match
由于 Last-Modified 的时间粒度是秒,有的文件在 1s 内可能被改动多次。
这种方式在这种特殊情况下还是会失效,所以HTTP1.1又引入了 Etag 字段。这个字段是根据文件内容生成一个标记符比如"W/“5f9583bd-10a8"”,然后再和 If-None-Match 进行对比就能更准确的知道文件有没有被改动过
浏览器第一次发送请求获取文件缓存下来,服务器响应头返回一个 if-Modified-Since,记录被改动的时间
浏览器第二次发送请求的时候会带上一个 Last-Modified 请求头,时间就是 if-Modified-Since 返回的值。然后服务器拿到这个字段和自己内部设置的时间进行对比,时间相同表示没有修改,就直接返回 304 从缓存里面获取文件
缓存在哪里?
知道了缓存方式和实现,再来说一下缓存存在哪个地方,我们打开掘金可以看到如下的信息 。缓存的来源有两个地方
- from dist cache,
- from memeory cache
from memeory cache:
这个是缓存在内存里面,优点是快速,但是具有时效性,当关闭 tab 时候缓存就会失效。
from dist cache
这个是缓存在磁盘里面,虽然慢但是还是比请求快,优点是缓存可以一直被保留,即使关闭 tab 页,也会一直存在
何时缓存在memory,合适缓存在dist?
这个问题网上很少找的到标准答案,大家一致的说法是js,图片文件浏览器会自动保存在memory中,css文件因为不常修改保存在dist里面,我们可以打开掘金网站,很大一部分文件都是按照这个规则来的,但是也有少数js文件也是缓存在dist里面。所以他的存放机制到底是什么样了?我带着这个疑问查了好多文章,虽然最后没有确切找到答案,但是一个知乎的回答可以给我们提供思路,下面引用一个知乎回答者一段话
第一个现象(以图片为例):访问-> 200 -> 退出浏览器再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)。
总结: 或许chrome很聪明的判断既然已经从disk拿来了, 第二次就内存拿吧 快
第二个现象(以图片为例):只要图片是base64 我看都是from memroy cache。
总结: 解析渲染图片这么费劲的事情,还是做一次然后放到内存吧。用的时候直接拿
第三个现象(以js css为例):个人在做静态测试的发现,大型的js css文件都是直接disk cache。
总结: chrome会不会说 我去 你这么大太占地方了。你就去硬盘里呆着吧。慢就慢点吧。
第四个现象:隐私模式下,几乎都是 from memroy cache。
总结: 隐私模式 是吧。我不能暴露你东西,还是放到内存吧。你关,我死。
总结的形象吗?哈哈
但是,还有种说法,这个有点晦涩,但是如果你懂计算机内存工作原理应该秒懂:
浏览器运行的时候也是由多进程协作的,所以操作系统为了节省内存,会把一部分内存里的资源交换回磁盘的交换区,当然交换是有策略的,比如最常用的就是LRU。
什么时候存dist,什么时候存memoey都是在浏览器控制下的,memory不够了可能就会考虑去存dist了。
总结:
1. 大一点的文件会缓存在dist里面,因为内存也是有限的,磁盘的空间更大
2. 小一点文件js,图片存的是memory
css文件一般存在dist
特殊情况memory大小是有限制的,浏览器也会根据自己的内置算法,把一部分js文件存到dist里面
请求优化
讲请求优化的之前先来总结下上面说到的js, css文件顺序优化,为了让渲染更快,我们需要把js放到尾部,css放到头部,然后还要注意在书写js的时候**尽量减少重排,重绘。书写html,css的时候尽量简洁,不要冗余,目的是为了更快的构建DOM树和CSSOM树。**好了下面我们在来说说请求优化,请求优化可以从请求数量和请求时间两方面入手
- 减少请求数量
- 将小图片打包成base64
- 利用雪碧图融合多个小图片
- 利用缓存上面已经说到过
- 减少请求时间
- 将js,css,html等文件能压缩的尽量压缩,减少文件大小,加快下载速度
- 利用webpack打包根据路由进行懒加载,不要初始就加载全部,那样文件会很大
能升级到高版本的http就升级到高版本,为什么高版本能提高速度具体看下面"当堂补充" - 建立内部CDN能更快速的获取文件
- webpack优化
介绍了渲染优化,现在来看看webpack优化,自己平常写demo给团队做培训的时候都是自己手写webpack配置,虽然也就几十行,但每次都能让我巩固webpack的基本配置,下面直接说一下webpack优化手段有哪些
基础配置优化
extensions 这个配置是属于resolve里面的,经常用来对文件后缀进行扩展,写法如下
resolve: {
extensions: ['.ts', '.tsx', '.js']
}
这个配置表示webpack会根据extensions去寻找文件后缀名,所以如果我们的项目主要用ts写的话,那我们就可以.tsx和.ts写前面,目的是为了让webpack能够快速解析
alias 这个配置也是属于resolve里面的,是用来映射路劲,能减少打包时间的主要原因是能够让webpack快速的解析文件路径,找到对应的文件,配置如下
resolve: {
alias: {
Components: path.resolve(__dirname, './src/components')
}
}
noParse
noParse表示不需要解析的文件,有的文件可能是来自第三方的文件,被 providePlugin引入作为windows上的变量来使用,这样的文件相对比较大,并且已经是被打包过的,所以把这种文件排除在外是很有必要的,配置如下
module: {
noParse: [/proj4\.js/]
}
exclude
某些loader会有这样一个属性,目的是指定loader作用的范围,exclude表示排除某些文件不需要babel-loader处理,loader的作用范围小了,打包速度自然就快了,用> babel-loader举一个简单例子
{
test: /\.js$/,
loader: "babel-loader",
exclude: path.resolve(__dirname, 'node_modules')
}
devtool
这个配置是一个调试项,不同的配置展示效果不一样,打包大小和打包速度也不一样,比如开发环境下cheap-source-map肯定比source-map快
{
devtool: 'cheap-source-map'
}
.eslintignore
这个虽不是webpack配置但是对打包速度优化还是很有用的,在我的实践中eslint检查对打包的速度影响很大,但是很多情况我们不能没有这个eslint检查,eslint检查如果仅仅在vs里面开启的话,可能不怎么保险。
因为有可能你vs中的eslint插件突然关闭了或者某些原因vs不能检查了,只能靠webpack构建去帮你拦住错误代码的提交,即使这样还不能确保万无一失,因为你可能某一次提交代码很急没有启动服务,直接盲改提交上去了。这个时候只能通过最后一道屏障给你保护,就是在CI的时候。比如我们也会是在jenkins构建的时候帮你进行eslint检查,三道屏障确保了我们最终出的镜像是不会有问题的。
所以eslint是很重要的,不能删掉,在不能删掉的情况下怎么让检查的时间更少了,我们就可以通过忽略文件,让不必要的文件禁止eslint,只对需要的文件eslint可以很大程度提高打包速度
loader,plugins优化
上述说了几个基础配置优化,应该还有其他的基础配置,今后遇到了再继续添加,现在在来讲讲利用某些loader,plugins来提高打包速度的例子
cache-loader
这个loader就是在第一次打包的时候会缓存打包的结果,在第二次打包的时候就会直接读取缓存的内容,从而提高打包效率。但是也需要合理利用,我们要记住一点你加的每一个loader,plugins都会带来额外的打包时间。这个额外时间比他带来的减少时间多,那么一味的增加这个loader就没意义,所以cache-loader最好用在耗时比较大的loader上,配置如下
{
rules: [
{
test: /\.vue$/,
use: [
'cache-loader',
'vue-loader'
],
include: path.resolve(__dirname, './src')
}
]
}
1. webpack-parallel-uglify-plugin
2. uglifyjs-webpack-plugin
3. terser-webpack-plugin
在上面的渲染优化中我们已经知道,文件越小渲染的速度是越快的。所以我们在配置webpack时候经常会用到压缩,但是压缩也是需要消耗时间的,所以我们我们经常会用到上面三个插件之一来开启并行压缩,减少压缩时间,我们用webpack4推荐使用的terse-webpack-plugin做例子来说明
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
cache: true
})
],
}
1. happypack
2. parallel-webpack
3. thread-loader
这几个loader/plugin和上面一样也是开启并行的,只不过是开启并行构建。由于happypack的作者说自己的兴趣已经不再js上了,所以已经没有维护了,并推荐如果使用的是webpack4的话,就去使用thread-loader。基本配置如下
{
test: /.js$/,
use: [
{
loader: “thread-loader”,
options: threadLoaderOptions
},
“babel-loader”,
],
exclude: /node_modules/,
}
1. DllPlugin
2. webpack
3. DllReferencePlugin
上面说的几个并行插件理论上是可以增加构建速度,网上很多文章都是这么说的,但是我在实际的过程中使用,发现有时候不仅没提升反而还降低了打包速度,网速查阅给的理由是可能你的电脑核数本来就低,或者当时你CPU运行已经很高了,再去开启多进程导致构建速度降低。
上面说的几个并行插件可能在某些情况下达不到你想要的效果,然而在我们团队优化webpack性能经验来看,这次所说的两个插件是很明显并且每次都能提高打包速度的。原理就是先把第三方依赖先打包一次生成一个js文件,然后真正打包项目代码时候,会根据映射文件直接从打包出来的js文件获取所需要的对象,而不用再去打包第三方文件。只不过这种情况打包配置稍微麻烦点,需要写一个webpack.dll.js。大致如下
webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
library: ["vue", "moment"]
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'json-dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
path: './json-dll/library.json',
name: '[name].json'
})
]
}
webpack.dev.js
new AddAssetHtmlWebpack({
filepath: path.resolve(__dirname, './json-dll/library.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: require("./json-dll/library.json")
})
其他优化配置
webpack-bundle-analyzer
这个插件可以用可视化帮我们分析打包体积,从而来采用合适的优化方式去改进我们的webpack配置
speed-measure-webpack-plugin
这个插件可以告诉我们打包时候每一个loader或者plugin花费了多少时间,从而对耗时比较长的plugin和loader做优化
friendly-errors-webpack-plugin
这个插件可以帮我们优化打包日志,我们打包时候经常看到很长一个日志信息,有的时候是不需要的,也不会去看所以可以用这个插件来简化
代码优化
这是最后一部分代码优化了,这里的代码性能优化我只说我在工作中感受到的,至于其他的比较小的优化点比如createDocumentFragment使用可以查查其他文章
能不操作dom不要操作dom,哪怕有时候需要改设计
很多情况下我们都能用css还原设计稿,但是有些时候单单从css没法还原,尤其组件还不是你写的时候,比如我们团队用的就是antd,有时候的产品设计单从css上没法实现,只能动用js,删除,增加节点在配合样式才能完成。
比如大数据两公司,这个时候就会出现性能问题,最开始写代码时候,产品说什么就是什么,说什么我都会想办法搞出来,不管用什么方法。后来到客户现场大数据请况下,性能缺点立马暴露的出来。
所以代码优化的原则之一我认为是能不写的代码就不写,当然这是要从性能角度出发,通过性能分析给产品说出理由,并且最好还能提供更好的解决方案,这个才是我们需要考虑的。
如果用的是react 一定用写shouldComponentUpdate这个生命周期函数,不然打印的时候你会发现,你自己都迷糊为什么执行了这么多遍
将复杂的比对,变成简单比对
这句话是什么意思了?我们就拿shouldComponentUpdate举例子,用这个函数没问题,但是可以做的更好,我们在工作中经常这么写
shouldComponentUpdate(nextPrpops) {
return JSON.stringify(nextPrpops.data) !== JSON.stringify(this.props.data)
}
// 如果这是一个分页表格,data是每一页数据,数据改变了重新渲染,在小数据场景下这本身是没有问题。但是如果在大数据的场景下可能会有问题,可能有人有疑问,既然做了分页怎么还会有大数据了,因为我们的产品是做大数据分析日志的,一页十条日志,有的日志可能非常的长,也就是说就算是10条数据比对起来也是很耗时,所以当时想法能不能找到其他的替代变量来表示数据变了?比如下面这样
shouldComponentUpdate(nextPrpops) {
return nextPrpops.data[0].id !== this.props.data[0].id
}
第一条的id不一样就表示数据变化了行不行,显然在某种情况下是存在的,也有人会说可能会出现id一样,那如果换成下面这种了?
shouldComponentUpdate(nextPrpops) {
return nextPrpops.current !== this.props.current
}
将data的比对转换成了current的比对,因为页数变了,数据基本都是变了,对于我们自己日志的展示来说基本不存在两页数据是一模一样的,如果有那可能是后台问题。然后在好好思考这个问题,即使存在了两页数据一摸一样,顶多就是这个表格不重新渲染,但是两页数据一摸一样不重新渲染是不是也没有问题,因为数据是一样的。或者如果你还是不放心,那下面这种会不会好点
this.setState({
data,
requestId: guid()
})
shouldComponentUpdate(nextPrpops) {
return nextPrpops.requestId !== this.props.requestId
}
给一个requestId跟宗data,后面就只比对requestId。上面的写法可能都有问题,但是主要是想说的是我们在写代码时候可以想想是不是可以"将复杂的比对,变成简单比对"
学习数据结构和算法,一定会在你的工作中派上用场
我们经常会听到学习数据结构和算法没有什么大的用处,因为工作基本用不上。这句话我之前觉得没错,现在看来错的很严重。我们所学的每一样技能,都会在将来的人生中派上用场。之前写完代码就丢了不去优化所以我觉得算法没意义,又难又容易忘记。但现在要求自己做完需求,开启mock,打开perfermance进行大数据量的测试,看着那些标红的火焰图和肉眼可见的卡顿,就明白了算法和数据结构的重要性,因为此时你只能从它身上获取优化,平时你很排斥它,到优化的时候你是那么想拥有它。我拿自己之前写的代码举例,由于公司代码是保密的我就把变量换一下,伪代码如下
data.filter(({id}) => {
return selectedIds.includes(id);
})
就是这样几行代码,逻辑就是筛选出data里面已经被勾选的数据。基本上很多人都可能这么写,因为我看我们团队里面都是这么写的。产品当时已经限制data最多200数据,所以写完完全没压力,性能没影响。但是秉着对性能优化的原则(主要是被现场环境搞怕了~~~),我开启了mock服务,将数据调到了2万条再去测试,代码弊端就暴露出来了,界面进入卡顿,重新选择的时候也会卡顿。然后就开始了优化,当时具体的思路如下
按照现在的代码来看,这是一个两层循环的暴力搜索时间复杂度为O(n^2)。所以想着能不能降一下复杂度至少是O(nlogn),看了一下代码只能从selectedIds.includes(id)这句入手,于是想着可不可以用二分,但是立马被否定因为二分是需要有序的,我这数组都是字符串怎么二分。
安静了一下之后,回想起看过的算法课程和书籍以及做的算法题,改变暴力搜索的方法基本都是
1:上指针
2:数组升维
3:利用hash表
前两者被我否定了因为我觉得还没那么复杂,于是利用hash表思想解决这个问题,因为js里面有一个天然的hash表结构就是对象。我们知道hash表的查询是O(1)的,所以我将代码改写如下
const ids = {};
selectedIds.forEach(id => ids[id] = 1);
data.filter(({id}) => {
return !!ids[id];
})
将从selectedIds查询变成从ids查询,这样时间复杂度就从O(n^2)变成了O(n)了,这段代码增加了
const ids = {};
selectedIds.forEach(id => ids[id] = 1);
其实增加了一个selectedIds遍历也是一个O(n)的复杂度,总来说复杂度是O(2n),但是从时间复杂度长期期望来看还是一个O(n)的时间复杂度,只不过额外增加了一个对象,所以这也是一个典型的空间换时间的例子,但是也不要担心,ids用完之后垃圾回收机制会把他回收的。
最后思考:是不是只有这些??具体我们当前针对项目具体可实操,优化的还有吗?
https 和 http 的区别(当堂补充)
先来再次普及下?
版本 | 时间 | 发展现状 |
---|---|---|
HTTP/0.9 | 1991年 | 不涉及数据包传输,规定客户端和服务器之间通信格式,只能GET请求 没有作为正式的标准 |
HTTP/1.0 | 1996年 | 传输内容格式不限制,增加PUT、PATCH、HEAD、 OPTIONS、DELETE命令 正式作为标准 |
HTTP/1.1 | 1997年 | 持久连接(长连接)、节约带宽、HOST域、管道机制、分块传输编码 2015年前使用最广泛 |
HTTP/2 | 2015年 | 多路复用、服务器推送、头信息压缩、二进制协议等 逐渐覆盖市场 |
这个Akamai公司建立的一个官方的演示,使用HTTP/1.1和HTTP/2同时请求379张图片,观察请求的时间,明显看出HTTP/2性能占优势。
多路复用:通过单一的HTTP/2连接请求发起多重的请求-响应消息,多个请求stream共享一个TCP连接,实现多留并行而不是依赖建立多个TCP连接。
请求优化|
什么是HTTPS?
HTTPS是身披SSL外壳的HTTP。HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。
PS:TLS是传输层加密协议,前身是SSL协议,由网景公司1995年发布,有时候两者不区分。
HTTP VS HTTPS
HTTP特点:
- 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
- 无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
- 基于请求和响应:基本的特性,由客户端发起请求,服务端响应
- 简单快速、灵活
通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性
抓包比对
HTTP:
- HTTP协议传输数据以明文形式显示
- 针对无状态的一些解决策略:
- 场景:逛电商商场用户需要使用的时间比较长,需要对用户一段时间的HTTP通信状态进行保存,比如执行一次登陆操作,在30分钟内所有的请求都不需要再次登陆。
- 通过Cookie/Session技术
HTTP/1.1持久连接(HTTP keep-alive)方法,只要任意一端没有明确提出断开连接,则保持TCP连接状态,在请求首部字段中的Connection: keep-alive即为表明使用了持久连接
HTTPS:
通过抓包可以看到数据不是明文传输,而且HTTPS有如下特点:
- 内容加密:采用混合加密技术,中间者无法直接查看明文内容
- 验证身份:通过证书认证客户端访问的是自己的服务器
- 保护数据完整性:防止传输的内容被中间人冒充或者篡
混合加密: 结合非对称加密和对称加密技术。客户端使用对称加密生成密钥对传输数据进行加密,然后使用非对称加密的公钥再对秘钥进行加密,所以网络上传输的数据是被秘钥加密的密文和用公钥加密后的秘密秘钥,因此即使被黑客截取,由于没有私钥,无法获取到加密明文的秘钥,便无法获取到明文数据。
数字摘要:通过单向hash函数对原文进行哈希,将需加密的明文“摘要”成一串固定长度(如128bit)的密文,不同的明文摘要成的密文其结果总是不相同,同样的明文其摘要必定一致,并且即使知道了摘要也不能反推出明文。
数字签名技术: 数字签名建立在公钥加密体制基础上,是公钥加密技术的另一类应用。它把公钥加密技术和数字摘要结合起来,形成了实用的数字签名技术。
- 收方能够证实发送方的真实身份;
- 发送方事后不能否认所发送过的报文;
- 收方或非法者不能伪造、篡改报文。
非对称加密过程需要用到公钥进行加密,那么公钥从何而来?
其实公钥就被包含在数字证书中,数字证书通常来说是由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,证书中包含了一个密钥对(公钥和私钥)和所有者识别信息。数字证书被放到服务端,具有服务器身份验证和数据传输加密功能。
HTTPS 实现原理
SSL 建立连接过程
- client向server发送请求https://baidu.com,然后连接到server的443端口,发送的信息主要是随机值1和客户端支持的加密算法。
- server接收到信息之后给予client响应握手信息,包括随机值2和匹配好的协商加密算法,这个加密算法一定是client发送给server加密算法的子集。
随即server给client发送第二个响应报文是数字证书。服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。传送证书,这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。 - 客户端解析证书,这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(预主秘钥)。
客户端认证证书通过之后,接下来是通过随机值1、随机值2和预主秘钥组装会话秘钥。然后通过证书的公钥加密会话秘钥。 - 传送加密信息,这部分传送的是用证书加密后的会话秘钥,目的就是让服务端使
- 用秘钥解密得到随机值1、随机值2和预主秘钥。
- 服务端解密得到随机值1、随机值2和预主秘钥,然后组装会话秘钥,跟客户端会话秘钥相同。
- 客户端通过会话秘钥加密一条消息发送给服务端,主要验证服务端是否正常接受客户端加密的消息。
- 同样服务端也会通过会话秘钥加密一条消息回传给客户端,如果客户端能够正常接受的话表明SSL层连接建立完成了。
.怎么保证保证服务器给客户端下发的公钥是真正的公钥,而不是中间人伪造的公钥呢?
证书如何安全传输,被掉包了怎么办?
http和https应用总结
安全性考虑:
- HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用
- SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行
- 中间人攻击(MITM攻击)是指,黑客拦截并篡改网络中的通信数据。又分为被动MITM和主动MITM,被动MITM只窃取通信数据而不修改,而主动MITM不但能窃取数据,还会篡改通信数据。最常见的中间人攻击常常发生在公共wifi或者公共路由上。
成本考虑:
- SSL证书需要购买申请,功能越强大的证书费用越高
- SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗(SSL有扩展可以部分解决这个问题,但是比较麻烦,而且要求浏览器、操作系统支持,Windows XP就不支持这个扩展,考虑到XP的装机量,这个特性几乎没用)。
- 根据ACM CoNEXT数据显示,使用HTTPS协议会使页面的加载时间延长近50%,增加10%到20%的耗电。
- HTTPS连接缓存不如HTTP高效,流量成本高。
- HTTPS连接服务器端资源占用高很多,支持访客多的网站需要投入更大的成本。
- HTTPS协议握手阶段比较费时,对网站的响应速度有影响,影响用户体验。比较好的方式是采用分而治之,类似12306网站的主页使用HTTP协议,有关于用户信息等方面使用HTTPS。