先引入几张基础图片
应用层:为应用软件提供了很多服务,构建于TCP、UDP等协议之上,屏蔽网络相关传输细节;
传输层:向用户提供可靠的端到端的END-TO-END服务,传输层向高层屏蔽了下层数据通信的细节;(tcp面向连接,可靠;udp面向无连接,不可靠)
网路层:为数据在节点之间传输创建逻辑链路;
数据链路层:在通信实体之间建立数据链路连接;
物理层:定义物理设备如何传输数据;
历史
http 0.9 是发布的第一版 ,请求方式只支持get方式,且不支持header等描述信息,请求发送完毕,就关闭TCP链接,即用即销,所以每次建立和销毁都需要经历三次握手和四次挥手的过程,大大增加了通讯开销;
http 1.0 增加了header status code ,可手动设置keep-alive(这个很晚的时候才支持,甚至可以认为是从1.1开始的),请求结束后不再关闭TCP连接,即将短连接转为长连接;
http 1.1 默认开启keep-alive,即默认建立长连接,且增加了pipeline策略(管线化)(在一个TCP连接中发送多个请求,但这些请求依然是有先后顺序的,串行,不是并发);
即是串行,在一个请求没处理完之前,其他请求都需要排队等待,这就是传说的http队头阻塞HOLB(Head of line blocking);
因为TCP连接不再是即用即销,为了控制资源占用,浏览器必须限制同时存在的TCP连接个数,如,chrome支持最多6个TCP连接,其他浏览器一般也为4-8个;
另外keep-alive可以设置timeout,来指定TCP连接的过期时间,默认情况下,默认15s内没有请求,TCP连接自动关闭;
http 2.0 所有数据都以二进制传输,请求头压缩,
信道复用,也叫多路复用,同一个域名下,仅建立一个TCP连接,所有请求都复用这一个,且可以并发请求;
分帧传输,同一条数据可以被拆为不同帧,且保持其上下文关系,并发发送,无论哪一部分先到,最终都会依据上下文关系,将其还原为源数据
server-push,服务端消息推送(如果当前https的证书是不被信任的,那么后台推送的数据浏览器会拒绝接收)
注意,http2只有在开启https后才能使用,因为http2是有谷歌之前开发的SPDY协议演化而来,SPDY协议要求必须使用https才能使用,导致现在http2也是如此。
名词解析
URI 统一资源标识符 包括URL+URN 用来唯一标识互联网上的资源信息
URL 统一资源定位器 即常见的访问地址 schema://host:port/path?query#hash
URN 永久统一资源定位符 为了实现在资源移动位置后还能被找到(这个目前还没有非常成熟的方案)
User-Agent
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36
Mozilla/5.0 因为浏览器最早是由网景公司出的,默认头为Mozilla/5.0,很多老的http服务器只支持这个头,所以现在很多浏览器为了兼容老的服务器,都默认给他加上这个头;
Windows NT 10.0; Win64; x64 本机环境,windows环境和系统位数;
AppleWebKit/537.36 浏览器内核,目前chrome和safari内核都是webkit,苹果公司研发的,所以都已AppWebKit声明;
KHTML, like Gecko 渲染引擎版本,gecko是火狐的渲染引擎;
Chrome/84.0.4147.135 当前chrome版本号;
Safari/537.36 因为用的是苹果公司的内核,所以强制加上这个声明,没啥原因
CORS预请求
浏览器对跨域的拦截,其实是请求已正常发送,并且response内容已正常响应,但在浏览器解析返回的数据时,发现不满足服务端设置的Access-Control-Allow-Origin,才抛出跨域错误信息;
可在服务端通过Access-Control-Allow-Origin设置白名单,甚至可以通过程序来动态设置;
可在服务端通过Access-Control-Allow-Methods属性设置允许的请求方法,如put\delete等,因为默认情况下跨域只允许get/head/post三种请求方式;
可在服务端通过Access-Control-Allow-Headers属性设置允许的自定义请求头,如X-Test-Cors等;
可在服务端通过Access-Control-Max-Age 属性设置预请求的过期时间。
跨域只支持三种数据类型text/plain、multipart/form-data、application/x-www-form-urlencoded 三种content-type
multipart/form-data 为“拆分上送”或“部分上送”,就是把数据拆为各个部分上送,上送file类型的数据时,必须选此格式,因为file类型禁止字符串的形式上传,必须以二进制上送。
注意,header里面时间单位都是秒,一个key对应多个值时,以逗号分隔,如Access-Control-Allow-Methods:"put,delete"等
缓存
public 请求经过的所有节点如浏览器、代理服务器等都可缓存;
private 仅允许请求发起的终端使用缓存,如浏览器;
no-store 表示直接禁用缓存;
no-cache 表示不直接使用缓存,而是发起服务端验证,配合last-modified或etag使用;
max-age 请求终端(浏览器)缓存失效时间,单位是秒;
s-maxage 代理服务器缓存时间;
代理服务器的缓存即为代理缓存,可在多个终端共享;
服务器缓存验证:
last-modified 服务端最后一次修改的时间,配合if-modified-since或者if-unmodified-since使用,后者很少用;
对比上次修改的时间,验证缓存是否需要更新;
etag 数据签名,最后一次修改服务端资源是,经过特定算法得出的hash值,配合if-match或者if-non-match使用;
对比资源的签名判断是否一致,验证缓存是否需要更新;
另外,服务端response的header中可以设置vary字段,表示只有在请求头中携带的vary字段值相同时,才能使用缓存,如下
response的header设置{vary:"user-agent"},那么所有的请求头中只有request中的header里有user-agent字段,且其value相同,才可以共用缓存,
这个很常见,同一个应用程序用来区分移动端和PC端的缓存。
为什么页面中的meta中的cache-control不生效?(这个好多人都在问)
很多人发现,页面中的cache-control如何配置,对我们的缓存策略丝毫不产生作用,那么既然没作用为什么好多项目的页面中还要配置呢?
其实,造成这一问题的原因大致可为分两个维度,浏览器原因和http协议原因
从浏览器来讲,很简单,这个只是IE时代的私有属性,在IE9以前支持的,而现在主流的chrome\firefox\safari,包括IE9-11都不再支持了,如果应用需要兼容低版本的IE浏览器(如银行、zf等),可以加上这个东西,否则就完全没有必要了;
从http来讲,这东西是http1.0时代的产物,因为http1.0里关于缓存可设定的内容太少(要知道,http0.9压根就不支持服务端的response.header,http1.0虽是添加了header,但除了status code,也没多少可以设置的),而且http1.1发布早期,并不是所有浏览器都支持,所以把控制缓存的cache-control放到了前端html的页面中。
现在http1.1已经普及,几乎找不到还支持1.0的web服务器了,而且随着https的普及,很快就能全面实现http2了。
另外,https://stackoverflow.com/questions/49547/how-do-we-control-web-page-caching-across-all-browsers 看这篇的文章的意思,应该是说目前网页中的cache-control仅在file://协议才对此生效。(自己没有验证)
HTTP请求的数据协商
request
Accept 接受的数据格式,如(*,type/text,application/json等);
Accept-Encoding 接受的数据压缩格式,如(*,gzip,deflate,br等);
Accept-Language 接受的语言类型,如(zh-CN,en,zh-TW等);
response
Content-Type 返回的数据格式;
Content-Encoding 返回的压缩格式;
Content-Language 返回的语言类型;
重定向
302 重定向跳转,每次都经过服务器转发;
301 永久重定向,只有第一次请求有服务器转发,后面依赖终端缓存,直接转发,清除缓存后,会再次经过服务器。(这种很少用)
CSP(content-security-policy) 内容安全策略
这个值得讲的是,可以在前端meta中设置,也可以在后台的reponse.header中设置;
前后端同时设置,按最高安全的级别执行,取前后端规则的交集,如下:
后台设置 "Content-Security-Policy": 'default-src \'self\'; ' 仅支持同域名下的连接
前端设置<meta http-equiv="Content-Security-Policy" Content="default-src https:" /> 仅支持https的连接
最终执行策略:只允许即是同域名,又是https连接(这也就要求了当前域名必须是https,否则不会同域名)
https的握手过程
证书即公钥会用于传输,私钥永远只放在服务器,不进行传输,所以也不会被获取,所以更安全。
1.客户端向服务端发送 一个随机数 和 支持的加密套件(算法),可以是一种或多种;
2.服务端向客户端返回 一个随机数 和 选用的一种加密套件,同时,服务端向客户端发送 证书 即公钥;
3.客户端使用证书(公钥)对一个新的随机数加密,生成预主密钥,然后将预主密钥发送至服务端;
4.服务端利用私钥对预主密钥解密,得到客户端加密前的随机串, 至此前后端各有三个相同的随机串
5.利用加密套件对三个随机串加密 生成最终的主密钥。
nginx配置https
1.先生成证书和密钥 (在nginx根目录下执行git bash)
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout localhost-privkey.pem -out localhost-cert.pem
此命令在windows下默认不支持,可在git bash中执行,git bash已集成openssl,一路回车即可;
2.执行后在nginx根目录下生成两个文件localhost-privkey.pem localhost-cert.pem;
3.配置nginx.conf
listen 443;//这个是默认的https端口
ssl on; //nginx1.5以下需要这么开启https 1.5+不需要这句
ssl_certificate_key ../localhost-privkey.pem;
ssl_certificate ../localhost-cert.pem;
4.如何实现浏览器输入域名或localhost时自动跳转https
server{
listen 80 default_server
listen [::]:80 default_server
server_name localhost
return 302 https://$server_name$request_uri
}
注意:nginx 1.5以上 开启https 和http2可以在端口号后面直接添加 ssl http2,如下:
listen 443 ssl http2;
另外nginx 开启http2后,采用 ALPN 的兼容策略与服务端协商,服务端支持H2就用http2 ,不支持就用1.1。 ALPN 兼容策略优先使用http2
这点很重要,现在http2之所以没有流行,
一是受https的限制,
二是多数公司现有项目或库,都是不支持http2的,要推行http2的成本太高。
那么有了nginx这种代理服务器后,是不是可以解决第二个问题,即,
在原服务器的前面设置一层代理服务器,并开启http2,如此以来,浏览器和nginx之间建立http2连接进行通讯,什么分帧传输、信道复用、头部压缩等h2的好处都能使用;请求到达nginx后,nginx执行ALPN策略,判断后台是否支持h2,支持最好,直接原样发送,若不支持,再转为http1.1与后台服务器通讯。看到此处,可能有人会问,这样一来,节省掉的tcp建立连接(http1.1中浏览器与后台服务器)的开销不一样又补回来了吗,并且请求无故多了一次中转,我们大费周章岂不止做了无用功,还延迟了请求的时间线?我不这么认为,因为代理服务器和后台服务在同一局域网内,建立连接的开销比通过外网时的要小太多了,而且他们之间可以同时建立非常多的连接,连接也都可以keep-alive,并且他们之间的数据通信远远高于我们浏览器-服务器。所以总体来看这套方案是把原来“浏览器-服务器的http1.1的请求”拆分为“浏览器-nginx h2请求”和“nginx-服务器 http1.1请求”,而后者的效率是不足以让我们担心的。如下图:
前提是服务端支持https,且所提的nginx是部署在后台服务器的,而非我们前端配置nginx。
上述策略,仅仅是我个人猜想,自己服务器相关技术能力有限,并未验证,如果真的具有可行性,即能体验h2所带来的快感,提高前后端的通讯效率,又能避免维护后台库所带来的的高额成本。有条件的老铁,可以帮忙验一下。
以上总结,不对之处,还请批评指正。