目录
前言
本文围绕 HTTP1.1 进行讲解,篇幅略长;内容以《图解HTTP》为蓝本,其余资料为辅助,并佐以笔者愚见所成;下面就跟随笔者一起走进 HTTP 的世界吧。
初识HTTP协议
HTTP 全称超文本传输协议,是一个简单的请求-响应协议,指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
无连接
限制每次连接只处理一个请求,服务器处理完客户端的请求并收到应答后即 断开连接,以此节省传输时间。
若想保持连接可采用:Keep-Alive、WebSocket
无状态
协议对事务处理没有记忆能力,意味后续处理若需要先前信息则必须 重传,导致每次连接需要的数据量增大。
若想保存状态可采用:Session、Cookie
HTTP报文
用于HTTP协议交互的信息即为HTTP报文,分为请求报文和响应报文
报文由【报文首部、空行、报文主体】构成
我们使用 curl -v 来看下报文结构
报文首部由【请求行/状态行、首部字段】构成
请求报文的起始行为请求行,其包括【请求方法、请求URI、HTTP版本】响应报文的起始行为状态行,其包括【HTTP版本、状态码、原因短语】首部字段通用首部字段:请求报文和响应报文都会用到的首部请求首部字段响应首部字段实体首部字段:用于补充与实体相关的信息其它:RFC中未定义的首部,如为 Cookie 服务的首部字段 (Cookie、Set-Cookie)空行用于分割报文首部与报文主体
报文主体即具体数据,请求报文对应请求体,响应报文对应响应体
HTTP请求方法
GET & POST
GET用于获取资源,POST用于上传资源
从 缓存 角度:GET 请求会被浏览器主动缓存,POST 不会;从 编码 角度:GET 对 URL 进行编码,POST 无限制;从 参数 角度:GET 参数暴露在 URL 中,仅接受 ASCII 字符;POST 放在请求体中且参数无限制,更适合传输敏感信息;从 幂等性 角度:GET 是幂等的,POST 不是;tips:什么是幂等性?
在相同条件下,一次请求和重复多次请求对资源的影响是一致的,则该操作幂等。
HEAD
与 GET 请求相同,区别在于不会返回请求的实体数据,仅返回响应头。
可用于确认资源是否存在或检查资源是否更新,超链接探测器很多都基于 HEAD 方法进行实现。
OPTIONS
用于查询服务器支持哪些方法
❓ 浏览器为什么会自动发起 OPTIONS 请求
当发起 跨域请求 时,如果为非简单请求,浏览器会帮我们自动触发 预检请求(即 OPTIONS 请求),用于确认目标资源是否支持跨域;若为简单请求,则不触发预检,正常请求。
也就是一旦触发预检,则发送两次请求,影响性能,可做如下优化:
同域则不触发尽量避免 OPTIONS 请求:上图为 POST 请求设置 Content-Type: application/json 后的预检请求,可将其设置成 application/x-www-form-urlencoded| multipart/form-data | text/plain 之一来规避触发预检。设置 Access-Control-Max-Age,单位秒,一般设置为10分钟。谈谈跨域
浏览器遵守同源策略,协议、域名、端口三者均相同视为同源;当发起请求时,若不同源,则产生跨域;跨域是浏览器对 JavaScript 施加的安全策略。
CORS:跨域资源共享
CORS 是服务端在响应头中设置一些特定的字段来解决跨域问题,其会发送上文所说的预检请求。
Access-Control-Allow-Origin:用于告诉客户端允许跨域的源,可设置为 * 表任意源Access-Control-Allow-Methods:用于告诉客户端允许跨域请求的方法Access-Control-Allow-Headers:用于告诉客户端允许跨域的自定义请求头Access-Control-Allow-Credentials:是否允许携带 Cookie 信息若想操作 Cookie,需满足下列条件:服务端响应头 Access-Control-Allow-Credentials 设置为 true服务端响应头 Access-Control-Allow-Origin 不能设置为 *,需指定具体的源,可通过获取请求头中 origin 字段动态指定客户端发起请求时,需指定 withCredentials 为 trueAccess-Control-Max-Age:发送预检请求的有效期,在此期间,不在发送预检请求JSONP
JSONP 利用 script 标签不受同源限制来进行跨域请求,其通过 script 标签的 src 属性实现,故仅能处理 GET 请求。
其原理为前端定义一个方法,该方法接收一个参数;后端返回该方法并将数据作为参数传递;当加载 script 内容时,即调用了该方法,前端成功接收数据。
Nginx
用 Nginx 做代理解决跨域问题
PUT & DELETE
PUT用于更新资源,DELETE用于删除资源
RESTful 风格会使用 PUT & DELETE;实际开发中大家可自行评估选用 PUT & DELETE 还是 POST。
CONNECT
创建隧道,用于代理服务器
HTTP状态码
1xx ~ 请求正在处理中
101 Switching Protocols:用于切换协议,如从 HTTP 升级为 WebSocket,若服务器同意变更,则发送此状态码
2xx ~ 请求被正常处理
200 OK:请求成功
204 No Content:请求成功,但无内容返回
206 Partial Content:客户端进行范围请求,响应报文中返回由 Content-Range 指定范围的实体内容
3xx ~ 浏览器需执行某些特殊的处理
301 Moved Permanently:永久重定向,如域名迁移等
302 Found:临时重定向
304 Not Modified:服务器端资源未改变,可直接使用客户端未过期的缓存
4xx ~ 客户端发生错误
400 Bad Request:客户端请求的语法错误,服务器无法理解
403 Forbidden:服务器拒绝客户端的请求
404 Not Found:服务器上没有该资源
408 Request Timeout:请求超时
429 Too Many Request:短时间内发送的请求过于频繁
5xx ~ 服务器发生错误
500 Internal Server Error:服务器内部错误
503 Service Unavailable:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
HTTP缓存
强缓存和协商缓存
两者的区别在于是否需要向服务器验证本地缓存是否有效;强缓存直接使用本地缓存,协商缓存则需与服务器进行协商后确定是否使用本地缓存。
强缓存
设置 Cache-Control: max-age=xxx,public,表示在xxx秒内再次访问该资源,均使用本地缓存;public 表示允许客户端及代理服务器(CDN)缓存;
tips:测试时记得勾选掉 Disable cache
存在问题:感知不到资源更新,缓存不过期则不会重新获取
❓ 那是不是不该用强缓存,而选用协商缓存呢
并不是,协商缓存最大的问题就是每次都要向服务器验证一下资源的有效性。而缓存的意义在于减少请求,更多的使用本地资源,给用户更好的体验同时减轻服务器压力。所以最佳实践应尽可能命中强缓存,同时在更新资源时让客户端的缓存失效。
故可在打包静态资源文件时,根据内容进行 hash 计算,以此来生成文件名;此时请求资源URL改变,浏览器重新加载资源。
tips:这里浏览器会自动分配存储到内存(memory cache)中还是磁盘(disk cache)中。
协商缓存
Last-Modified 与 If-Modified-Since
如图,首次请求体积为 8.7KB,命中缓存后,体积为 225B。
首次请求资源时,服务器返回的首部字段中会携带 Last-Modified;当再次请求资源时,浏览器会自动带上 If-Modified-Since,该值与 Last-Modified 做对比。
若相同,则返回304,命中缓存;若不同则返回200,发送最新资源。
缺点:Last-Modified 记录的是精确到秒的时间格式,若在 1s 内资源发生变化,则 Last-Modified 感知不到,无法返回新资源。
Etag 与 If-None-Match
首次请求资源时,服务器返回的首部字段中会携带 Etag;当再次请求资源时,浏览器会自动带上 If-None-Match,该值与 Etag 做对比。
若相同,则返回304,命中缓存;若不同则返回200,发送最新资源。
与 Last-Modified 相比,Etag 是根据文件内容生成的唯一标识,当文件变化,则 Etag 变化。两者同时存在时,Etag 优先级高于 Last-Modified。
CDN缓存
CDN 全称内容分发网络,其可类比为火车票代售点;服务器将内容分发至全国各地的 CDN 节点,用户就近获取所需内容,减少请求时间;同时也起到了分流的作用,为服务器减轻负载压力。CDN 的关键技术为内容存储和内容分发。
浏览器缓存提升了页面的二次访问速度,而 CDN 缓存则是网络层面的优化,帮我们提升了首次访问速度;CDN 服务商会提供强刷功能,我们可手动刷新 CDN 缓存;前文概念,HTTP 是请求-响应协议;在 CDN 和服务器中,CDN为客户端是请求方,服务器是响应方;在 CDN 和浏览器中,浏览器为客户端是请求方,CDN为服务器是响应方;既然都遵循 HTTP 协议,则缓存方式也相同,通过 Cache-control: s-maxage 进行缓存;其中 s-maxage 专门用于代理服务器,概念同 max-age 用于设置缓存时间,优先级大于 max-age;上图为配置 CDN 后,分别在本地和服务器 ping chat.deeruby.com 的结果,ping 出的 IP 不同,证明走了不同的 CDN 节点。
HTTP状态管理
HTTP 是无状态协议,每次请求都是独立且无关的;但实际应用中,我们需要对一些状态进行保存,如登录状态等,为此引入了 Cookie。
Cookie 是服务器通过 Set-Cookie 字段携带并发送给浏览器的 key & value 形式的数据;浏览器将其 保存至本地,在下次请求 相同服务器 时,通过 Cookie 字段发送至服务器。
Cookie 存储在客户端是 key & value 形式的数据,以字符串形式存储,且存储数据量较小 -> eg:k1=v1;k2=v2;k3=v3;跨域不共享可在浏览器的 Application -> Cookies 中查看Path:指定 CooKie 在哪个路径下生效,“/” 表示当前域下所有路径都生效
domain:domain属性指定的域名可做到与结尾匹配一致,如,当指定 example.com 后,除 example.com 外,v1.example.com 或 v2.example.com 等都可以发送Cookie
Expires & Max-Age:用于设置 Cookie 有效期,若其过期,则此 Cookie 会被删除,不会发送给服务端
HttpOnly:使 JavaScript 脚本无法获取 Cookie
当设置 HttpOnly 后,document.cookie 获取的值为空
Secure:只能通过 HTTPS 传输 Cookie
SameSite:用于判断是否可以跨站传递 Cookie
跨站:有效顶级域名+二级域名相同,我们则称之为同站,如 a.deeruby.com 和 b.deeruby.com 同站。
SameSite = None:允许跨站携带 Cookie,如果想要设置 SameSite=None 的属性,Cookie 必须设置 Secure,表示只有在 HTTPS 协议下该 Cookie 才会被发送
SameSite = Strict:禁止跨站携带 Cookie
SameSite = Lax:只能在 GET 方法提交表单 或 a 标签发送 GET 请求 的情况下可以携带 Cookie
tips:Cookie 存储在客户端,易被截获;可将信息存储到服务器的 Session 中,通过 Cookie 传输 SessionId 字段来验证用户信息。
HTTP持久连接
HTTP 协议是无连接的,每次请求并收到应答后会断开连接,造成了大量的资源浪费,为此使用持久连接做资源复用。
持久连接也叫长连接,用 Connection: Keep-Alive 来开启;而 HTTP 是应用层协议,TCP 才是传输层协议,故其本质是 TCP 的持久连接。
鉴于 HTTP 是请求响应协议,只能通过客户端发起,故引入了 WebSocket 协议,用于客户端与服务端的双向通讯,感兴趣的小伙伴可跳转至笔者的另一篇文章 WebSocket 搭建简易聊天室
升级到 WebSocket 协议需要 Upgrade 字段,且需指定 Connection: Upgrade;下图表示这是一个升级的请求,升级协议为 WebSocket
代理、网关、隧道
代理
代理是位于服务器和客户端之间的中间人角色,接收由客户端发送的请求并转发给服务器,同时也接收服务器返回的响应并转发给客户端。
前文 HTTP缓存 中所说的 CDN 就是代理的一种
代理服务器需要通过 Via 字段标明身份,Via 中的代理顺序即为在 HTTP 传输中报文的传达顺序
正向代理:代理服务器代理了客户端去和服务端交互,此时服务器收到的是代理服务器的请求,可用于隐藏真实的客户端或解决访问限制问题。
反向代理:与正向代理相反,代理服务器代理了服务端去和客户端交互,客户端收到的响应信息来自代理服务器,可用于隐藏真实的服务端或帮助服务器做负载均衡、安全防护等。
网关
用于连接两个或多个使用不同协议的应用程序,是协议转换器的角色,如通过 浏览器【HTTP 协议】 发送 邮件【POP3 协议】。
Web网关:一侧为 HTTP 协议,另一侧为其它协议的网关称为 Web 网关
安全网关:从网关出的时候将内部的 HTTP 转换为 HTTPS,从网关入的时候将外部的 HTTPS 转换为 HTTP
隧道
隧道技术是给 IP 包加一层新的 IP,这样原本数据包无法通过的地带就可以通过了,此时服务器接收到的是外层 IP。
隧道本身不会去解析HTTP请求,请求保持原样中转给之后的服务器。隧道会在通信双方断开连接时结束。VPN 就是采用了隧道技术。
使用隧道技术时,会发起 CONNECT 请求
HTTP内容协商
访问相同 URI 时,服务器可根据需要返回不同资源,而具体返回哪种资源,则由客户端和服务器协商决定,该机制为 HTTP 的内容协商机制。
Accept 为客户端告诉服务器希望接收什么数据,而 Content 为服务器实际发送了什么数据,内容协商相关首部字段如下:
AcceptAccpet-LanguageAccept-CharsetAccept-EncodingContent-TypeContent-LanguageContent-Encoding质量因子:质量因子q在内容协商机制中用于权重,范围 0~1,默认1。
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.7,en;q=0.6 复制代码优先级从高到低,顺序从左到右依次寻找,直到找到对应资源。
接下来我们两两配对,来说一说这些字段
Accept && Content-Type:用于指定数据类型
媒体类型:又称 MIME 类型,用来表示文档、文件或字节流的性质和格式,使用 type / subtype 结构表示
type 分类如下:text -- 文本、image -- 图片、audio -- 音频、video -- 视频、application -- 二进制
Multipart 类型:常见如 multipart/form-data 用于发送 FormData 类型的数据,笔者在 文件上传攻略手册 这篇文章中用到了该类型
Accpet-Language && Content-Language:用于指定支持语言,可通过其来实现国际化
Accept-Charset && Content-Type:用于指定字符集,如 utf-8
< Content-Type: text/html; charset=utf-8 > Accept-Charset: charset=utf-8 复制代码Accept-Encoding && Content-Encoding:用于指定压缩方式,如 gzip
HTTP范围请求
对于大文件来说,HTTP 允许客户端每次请求资源的一部分,即范围请求。
Accept-Ranges 用来告知客户端服务器是否能处理范围请求,可以处理为 bytes,不能处理为 none。
Range 告知服务器要请求哪一部分内容,若进行范围请求,状态码返回 206。
Range: bytes=500-999 复制代码500-999:表示从第 500 字节到第 999 字节500-:表示从第 500 字节到文件终点-499:表示从第 0 字节到第 499 字节UA
User-Agent,用于告知服务器客户端的应用类型、操作系统、软件开发商以及版本号。
可使用 window.navigator.userAgent 查看本机 UA 信息
通用格式如下:Mozilla/5.0 (平台信息) 引擎版本 浏览器类型与版本,用其进行设备判断,从而做内容区分或兼容处理。
HTTPS
HTTPS = HTTP + TLS(传输层加密协议,前身为 SSL 协议),其目的是解决 HTTP 明文传输时会被中间人监听和篡改的问题。
为兼顾性能及安全性,HTTPS 采用了非对称加密 + 对称加密的方案
为保证公钥传输不被篡改,采用数字签名做校验,并借助 CA 机构和系统根证书机制保证 HTTPS 证书的公信力
配置HTTPS
推荐一个免费获取安全证书的网站:FreeSSL.cn
选择浏览器生成,其会自动下载证书,然后把下载好的证书上传至服务器。
我们将其填入域名解析中,主机记录为TXT记录,记录类型为TXT,并填写上述记录值。
然后配置 nginx:
server { listen443 ssl; listen[::]:443 ssl; server_name sso.deeruby.com; ssl_certificate /etc/nginx/ssl/sso/full_chain.pem; ssl_certificate_key /etc/nginx/ssl/sso/private.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location / { proxy_pass :3001; } } server { listen 80; server_namesso.deeruby.com; rewrite ^(.*) https://$server_name$1 permanent; }