- https 4次握手
- c端请求,s端响应并提供证书(包括公钥、证书信息、颁发机构、过期时间等,s端私钥加密生成);
- c端校验接收数据(主要是校验公钥确实是s端发的,见数字证书部分)后,生成pre-master-secret并使用s端发过来的公钥加密;
- s端接收到后使用私钥解密,并最终通过某种算法生成master-secret;
- 后续的通信中s和c端均使用这个master-secret生成的密钥。
(其中两个secret是为了提高随机性,减少随机数被猜出的风险)
- 数字证书发布过程
- 服务器生成一对密钥,私钥自己留着,公钥交给数字证书认证机构(CA) 。
- CA进行审核,并用自己的私钥对服务器的公钥进行签名。
- 客户端访问服务器时,从CA获取证书(包括服务器端公钥),用CA的公钥对签名的证书进行验证,一致则说明该服务器公钥确实是CA颁发的。
- https涉及的加密算法
- 非对称加密(非对称加密算法用于握手过程中的加密生成的密码)
RSA,DSA/DSS - 对称加密(对真正传输的数据进行加密):AES,RC4,3DES
- HASH(用于验证数据的完整性,是否被篡改等):MD5,SHA1,SHA256
非对称加密生成密码是整个加密过程的关键,非对称加密会生成公钥和私钥,公钥负责数据加密,可以随意传输。私钥负责解密。
- https如何优化
- http使用TCP 三次握手建立连接,客户端和服务器需要交换3个包,https除了 TCP 的三个包,还要加上 SSL握手需要的9个包,所以一共是12个包。
- https的开销主要是SSL部分(尤其是session建立阶段)。主要是网络延时和SSL本身加解密的开销(服务器根据客户端的信息确定是否需要生成新的主密钥;服务器回复该主密钥,并返回给客户端一个用主密钥认证的信息;服务器向客户端请求数字签名和公开密钥)。
- 可以通过负载均衡的SSL termination proxy来优化,Web 服务放在SSL termination proxy之后。SSL termination proxy可以是基于硬件的,譬如F5;也可以软件的,如Nginx(维基百科用的)。
- 另外也可以通过SSL session或SSL ticket复用来优化。session ID就是每一次对话都有一个编号(session ID)。只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的"对话密钥",但session ID 往往只保留在一台服务器上;而session ticket类似,但是加密的,只有服务器才能解密,其中包括本次对话的主要信息,比如对话密钥和加密方法。当服务器收到 session ticket 以后,解密后就不必重新生成对话密钥了,支持负载均衡。
- (Heartbleed就是RFC 在2012年提出了 RFC6520 TLS 的心跳扩展用于优化SSL所引起的漏洞。原本希望通过在客户端和服务器之间来回发送心跳的请求和应答,保活 TLS session,减少重建 TLS的session的性能开销,但未对心跳请求进行长度检查,导致客户端可以读服务器内存的数据。)
- http2特性
- http的语义、头部、状态码、方法等不受影响
- 服务器端推送消息
- 传输二进制帧而非文本(乱序发帧并收到后重组)
- 仅在一个TCP连接中并行处理(通过请求优先级控制)
- 压缩头部信息减小开销
- 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送
- 从输入url到渲染的整个过程
- 客户端DNS寻址(浏览器DNS缓存、操作系统DNS缓存、本地域名服务器、向根域名服务器发送迭代请求)获得目标IP(有可能是CDN的IP)。
- TCP或UDP连接建立。
- 数据经过路由器到运营商网络,经过层层转发到达服务器。
- 数据经过负载均衡(F5,LVS,反向代理)
- 数据到达web server,经过后端业务逻辑
- 如果采用SSR(服务器端渲染),则后端调用数据(数据库等)生成应用代码,如果是前后端分离架构,则数据和网页文件分别返回。
- 数据通过网络回到客户端(内网NAT穿透)
- 数据到达浏览器,如有gzip压缩先解压,开始编码(这里要注意是utf-8还是gbk)
- html被解析(DOM树、CSSDOM树、呈现树等,见上面问题),加载外部资源,文字、图片渲染至屏幕。
- TCP三次握手,四次挥手过程
-
三次握手
三次握手 -
四次挥手
四次挥手
- CDN加速原理
- 用户向浏览器输入www.web.com这个域名,浏览器第一次发现本地没有DNS缓存,则向根域名服务器发起请求。
- 根域名服务器设置了CNAME,指向了www.cdn.com,即CDN网络中的智能DNS负载均衡系统。
- 智能DNS负载均衡系统解析域名,把对用户响应速度最快的IP节点返回给用户。
- 用户向该IP节点(CDN服务器)发出请求。
- 由于是第一次访问,CDN服务器会向原web站点请求,并缓存内容。
- 请求结果发给用户。
- 静态资源优化
- 配置超长时间的本地缓存(cache-control/expires) —— 节省带宽,提高性能
- 采用hash值作为缓存更新依据 —— 精确的缓存控制
- 静态资源CDN部署 —— 优化网络请求
- 更新资源发布路径实现非覆盖式发布 —— 平滑升级
- 为何很多网站静态资源会使用独立的域名
- 静态资源的http请求中不会携带无用的cookie。
- 动静分离更有利于CDN。
- http对同一个域名的同时下载线程数是有限制的,只要是为了优化下载速度,防止一个域名下太多下载线程,每个浏览器的限制不同。
- 方便复用,放在另一个服务器上,可以方便全局内其他产品的使用,比如说taobao.com的js tmall也可以用,且客户端可缓存。
- 跨域解决
- JSONP:仅支持get方法。原理是通过标签的src属性引用的文件可以跨域,那么传回一个脚本,将数据包在脚本中回调函数里并动态执行就可以拿到函数中的数据。
- CORS:后端配置Access-Control-Allow-Origin(IE10+)。注意包括简单请求和复杂请求。
- 代理:正向反向都可(nginx)
- CORS的简单请求和复杂请求
方法为HEAD,GET,POST之一,且headers只包括Accept, Accept-Language, Content-Language, Last-Event-ID, Content-Type(值为application/x-www-form-urlencoded, multipart/form-data, text/plain之一)的请求为简单请求。
- 简单请求浏览器直接发出,只添加一个origin头,服务器若允许该origin,则在响应头加上Access-Control-Allow-Origin: origin的值表示允许跨域。
Access-Control-Allow-Credentials控制服务器是否接受Cookies,如果不为true但是传了Cookies,则前端会报异常(注意,如果接受Cookies则origin的值不能为星号)。
Access-Control-Expose-Headers用来控制前端可以拿哪些响应头的header。 - 复杂请求浏览器会先发一个OPTION方法的preflight请求。预检请求中Access-Control-Request-Method用来标志此次复杂请求的方法,Access-Control-Request-Headers表明复杂请求额外发送的header字段。服务器检查origin及method、header后,如果接受则类似简单请求发送响应,仅多一个Access-Control-Max-Age来标志预检的有效期。
- 自己写一个jsonp的实现
var JSONP = {
now: function() { // 获取当前时间戳
return (new Date()).getTime();
},
rand: function() { // 获取随机数
return Math.random().toString().substr(2);
},
removeElem: function(elem) {
var parent = elem.parentNode;
if(parent && parent.nodeType !== 11) {
parent.removeChild(elem);
}
},
parseData: function(data) { // url组装
var ret = "";
if(typeof data === "string") {
ret = data;
}else if(typeof data === "object") {
for(var key in data) {
ret += "&" + key + "=" + encodeURIComponent(data[key]);
}
}
ret += "&_time=" + this.now();// 加个时间戳,防止缓存
ret = ret.substr(1);
return ret;
},
getJSON: function(url, data, func) { // 函数名称
var name;
url = url + (url.indexOf("?") === -1 ? "?" : "&") + this.parseData(data); // 拼装url
var match = /callback=(\w+)/.exec(url); // 检测callback的函数名是否已经定义
if(match && match[1]) {
name = match[1];
} else {
// 如果未定义函数名的话随机成一个函数名
// 随机生成的函数名通过时间戳拼16位随机数的方式,重名的概率基本为0
// 如:jsonp_1355750852040_8260732076596469
name = "jsonp_" + this.now() + '_' + this.rand();
url = url.replace("callback=?", "callback="+name);// 把callback中的?替换成函数名
url = url.replace("callback=%3F", "callback="+name);// 处理?被encode的情况
}
var script = document.createElement("script"); // 创建一个script元素
script.type = "text/javascript";
script.src = url; // 设置要远程的url
script.id = "id_" + name; // 设置id,为了后面可以删除这个元素
// 把传进来的函数重新组装,并把它设置为全局函数,远程就是调用这个函数
window[name] = function(json) {
window[name] = undefined; // 执行这个函数后,要销毁这个函数
var elem = document.getElementById("id_" + name); // 获取这个script的元素
JSONP.removeElem(elem); // 删除head里面插入的script,这三步都是为了不影响污染整个DOM啊
func(json); // 执行传入的的函数
};
var head = document.getElementsByTagName("head"); // 在head里面插入script元素
if(head && head[0]) {
head[0].appendChild(script);
}
}
};
//调用的方法跟jQuery基本一样。如:
var data = {
from: "北京",
count: 27,
output: "json",
callback: "?"
}
JSONP.getJSON("http://api.qunar.com/cdnWebservices.jcp", data, function(json) {console.log(json)});
- 原生ajax
<script>
// 1.获得ajax
if (window.XMLHttpRequest) { //查看当前浏览器XMLHttpRequest是否是全局变量
var oAjax = new XMLHttpResquest();
} else {
var oAjax = new ActiveXObject('Microsoft.XMLHTTP'); //IE6,传入微软参数
}
// 2.打开地址
switch (json.type.toLowerCase()) {
case 'get':
oAjax.open('GET', json.url + '?' + jsonToURL(json.data), true); // 提交方式(大写),url,是否异步
// 3.发送GET数据
oAjax.send();
break;
case 'post':
oAjax.open('POST', json.url, true);
oAjax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
oAjax.send(jsonToURL(json.data));
// 3.发送POST数据
break;
}
// 4.接收数据
oAjax.onreadystatechange = function() { //监控状态
if (oAjax.readyState == 4) {
json.complete && json.complete();
if (oAjax.status >= 200 && oAjax.status < 300 ||
oAjax.status == 304) {
json.success && json.success(oAjax.responseText); //执行成功的回调函数, responseText为响应内容
} else {
json.error && json.error(oAjax.status); //执行失败的回调函数
}
}
};
</script>
网友评论