HTTP
前置知识:万维网、资源与URI
超文本的概念:网页就是一直超文本。
http作为一种网络协议,在万维网中被视为唯一资源标识符(URI)的一种。它实际上就是为万维网而设计的。
MIME类型用于标识万维网中资源的类型,而非通过资源文件的后缀来识别。
www
并非域名所必须的,但没有www
的域名一般重定向到它。这会影响SEO(爬虫会爬到重复的页面),但这个缺点可以通过<link rel="canonical">
来解决。
HTTP概述
它构建出万维网:通过
http
协议超链接构建的超文本系统,形成了当今的万维网。正如上所说,实际上它就是为万维网而设计的。它从上世纪90年代左右被设计,历史上比较著名的版本有99年的http/1.1
,以及2015年的http/2
.基于
http
的模型:在http构成的实际网络中,基本上存在主要的三个实体:客户
、服务端
和代理
。在浏览器(客户)和服务器(服务端)之间,有许多计算机和设备参与传递了 HTTP 消息。它们中工作在应用层的设备被称为代理。通过对服务器端或者客户端架设包裹一层代理的主机,可以发挥很多种作用:
- 缓存数据(CDN)
- 负载均衡
- 过滤不必要请求
- 隐藏真实目标ip
实际使用的语言API
XMLHttpRequest
、Fetch API
是基于 HTTP 的最常用 APIserver-sent
,允许服务端借助作为 HTTP 传输机制向客户端发送js
事件并执行
http基本性质
- 使用简单
- 无状态:短连接中一次只有一个请求响应;而在同一个长连接中,两个请求之间不存在任何关系。
- 尽管 HTTP 根本上来说是无状态的,但借助 HTTP Cookie 就可使用有状态的会话。利用标头的扩展性,HTTP Cookie 被加进了协议工作流程,每个请求之间就能够创建会话,让每个请求都能共享相同的上下文信息或相同的状态。
- 多个 http 连接无法复用一个TCP,这导致了需要多发很多次TCP请求,造成了额外的开销。
http传输流程
- 客户端建立一条 TCP 连接。
- 客户端发送请求并等待应答。服务器处理请求并送回应答,回应包括状态码和对应的数据。
短连接到长连接
- 短连接模型:
每次http请求都要建立一次tcp连接,这造成了打开一次网页通常需要发送几十次请求才能加载完整的页面。而且由于tcp连接的性能是随时间延长而动态进行改善的,所以http连接的一次请求时长内无法享受到tcp带来的网络效率改善。
- 长连接模型:
长连接复用上次http建立的tcp连接,在流水线模型中,允许发送一系列请求而不必等导上一次http已经得到响应。它解决了短连接的性能和占用资源问题。这个连接在空闲一段时间后(可以通过keep-alive字段控制)自动关闭。
长连接相对于短连接的缺点在于,它在请求响应结束后不会立即关闭连接,这个连接在设定时间内还是会存在,即还是会占用资源。这导致在请求量非常大的情况下,存在大量的空闲连接占用服务器资源。(即Dos攻击的原理)。
HTTP/1.0
里默认并不使用长连接。把 Connection
设置成 close
以外的其他参数都可以让其保持长连接,通常会设置为 retry-after
。
在 HTTP/1.1
里,默认就是长连接,不再需要标头(但我们还是会把它加上,以防因为某种原因报文退回到 HTTP/1.0
)。连接在完成第三阶段后不再关闭,客户端可以再次发起新的请求。
再到流水线
1.1版还引入了流水线,即在同一个TCP连接里面,客户端可以同时发送多个请求。虽然请求是并行的,但是服务器必须按照请求的顺序依次响应。所以流水线中content-length
字段也是必须的。
要是前面的响应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"。
为了避免流水线的这个问题,只有两种方法:一是减少请求数;二是同时多开并行连接。这就出现了很多优化技巧,比如域名分片来翻倍连接数量。
流水线的这种并行请求、却线性响应的缺陷在http/2
中得到解决。
http报文
报文分为四部分:
- 起始行,即报文的第一行;报文的首部,即对报文的整体概括信息;一个空格用于分离首部和剩余部分;剩余部分。
请求报文
- 起始行:
GET /www/. HTTP/1.1
;(请求类型 请求目标路径 协议类型) - 标头:
host: localhost:8000
;- 它不区分大小写的字符串、冒号(
':'
)和一个值。 - 分为几组,通用标头,请求标头,表示标头。
- 常见的有:
content-type
:内容类型;content-length
:内容长度(用于告诉tcp连接,它本次传输所承载的http数据包的大小。这在http1.0中不作要求,因为一个tcp仅仅只对应一次http,浏览器收到服务器的关闭连接,就知道本次连接的数据包已经传完了。);Transfer-Encoding
分块编码传输,用于分块下载传送总长度未定的流数据(它虽然没有总长度,但是在每次传输中,都会在数据块的尾部标注本次传输的长度)。
- 它不区分大小写的字符串、冒号(
- 一个空行。
- 主体:提交给服务器处理的数据。像
GET
,通常不需要主体。常见的的情况是 POST 请求。- 主体大致可分为三类:单个文件(已知长度),单个文件(未知长度,需要多次传送,标头有字段
transfer-encoding: chunked
),多个文件(较少见)。
- 主体大致可分为三类:单个文件(已知长度),单个文件(未知长度,需要多次传送,标头有字段
响应报文
- 起始行:
HTTP/1.1 200 OK
;(协议类型 响应码 非正规描述) - 标头。
- 一个空行。
- 主体:提交给客户端的数据。如果响应失败,则主体数据可以不再需要。比如
201
、404
。
- 主体:提交给客户端的数据。如果响应失败,则主体数据可以不再需要。比如
强制缓存和协商缓存
强制缓存
- cache-control
- expires
强制缓存通过资源响应标头的cache-control
(绝对时间)和expires
(相对时间)来实现。在未到达指定时间前,浏览器将使用本地缓存的数据而不发送请求。Cache-Control 的优先级高于 Expires。
协商缓存
状态码为304的数据报,表示可以直接使用本地缓存的资源。协商缓存可以基于两种头部来实现:
- 第一种:请求头部中的
If-Modified-Since
字段与响应头部中的Last-Modified
字段实现,这两个字段的意思是:- 响应头部中的
Last-Modified
:标示这个响应资源的最后修改时间,一个数,单位是秒(s); - 请求头部中的
If-Modified-Since
:当资源过期了,则再次发起请求的时候带上上次 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。
- 响应头部中的
- 第二种:请求头部中的
If-None-Match
字段与响应头部中的ETag
字段,这两个字段的意思是:- 响应头部中
Etag
:唯一标识响应资源,一个字符串; - 请求头部中的
If-None-Match
:浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。
- 响应头部中
- 二者的区别在于:第一种实现方式是基于时间+文件名实现的,第二种实现方式是基于一个文件唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。如果同时存在
last-modified
和etag
,则Etag 的优先级更高。
面试
什么是http?
http是指超文本传输协议,它是一个应用层协议,http1、2基于tcp的可靠传输服务,向万维网提供超文本传输服务。但是它已经被证实不仅仅适用于超文本,它更适用于网络中大部分的数据传输。
一个http连接模型是由客户--服务器构成的。客户端发送一个请求,而服务器发送响应报文。
http的常见状态码?
- 1xx用于表示协议处理的某些中间状态,很少使用
- 2xx表示成功,比如200 OK,204 NoContent 表示成功但是响应数据没有 body ,206 PartialContent表示传输的是分块数据,返回的body只是整个数据的一部分;
- 3xx表示重定向,301 MovedPermanently表示请求的资源已经不存在,需要重定向都新URL;302 Found表示资源虽然已经存在,但是暂时不可访问,需要重定向到临时的URL;303 SeeOther 表示要求使用get重定向到另一个页面,一般用于表单提交后跳转网页;304也称缓存重定向,用于通知浏览器直接使用本地的缓存资源;
- 4xx表示客户端错误:400表示客户端请求有误; 401表示权限有误,需要鉴定权限;403 Forbidden 表示禁止访问;404 NotFound表示目标资源找不到
- 5xx表示服务器错误:500表示服务器发生了错误;501 Not Implemented表示客户端请求的资源暂不提供; 502 BadGateway 表示目标服务器自身工作正常,但访问其他服务器发生了错误。
http常见字段?
- Host、server
- accept-encoding、Content-Encoding
- connection
- content-length/transfer-encoding
http中,请求安全和幂等是什么?
- 安全:不会修改服务器的数据;
- 幂等:指的是多次执行相同的操作得到的结果响应都相同。
显然get是安全,也幂等;而post不安全,也不幂等。
重定向的手段有哪些?
- 使用html的
<meta http-equiv="Refresh" content="0; URL=..." />
; - 使用js的
window.location
- http响应重定向标头。这可以在架设的nginx/apache等web服务器上配置。
重定向死锁:两个服务器(链接)相互指向对方...这种情况下服务器可以发现错误并返回;如果是多个服务器的话,则只能由浏览器先跳几次后发现自己在一个连接循环中,才能发现。这也就是为什么看到浏览器转圈然后又不转了,重新转圈...
- 使用html的
http1.1/2/3三个版本的对比?
1.1版本基于1.0版本,让单个TCP可以复用,实现了长连接;支持分块的流数据传送;同时使用流水线技术,让请求发送可以并行;
缺点在于,流水线的队头阻塞机制;http本身是无状态的,需要使用cookie回话来解决;http使用明文传输(不安全),并且头部不允许被压缩;
针对这些缺点,http2进行了改进,并且传输过程可以自动兼容和回退http1.1:
使用唯一ID标识每一对http数据流,解决了流水线的队头阻塞机制,让性能进一步提升;
http2基于https,所以自带加密;
http2从的报文从文本格式改为了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)。通常需要解析工具查看,虽然无法直接查看,但是计算机可以直接解析和呈现报文内容,所以没有什么差别。另外,http2使用缓存协商(HPack)算法,大幅压缩了首部的长度。
最后,http2一定程度放宽了基本的
客户-服务
模型的限制,某些条件下允许服务端主动发送数据。比如浏览器请求了一个html文件,服务器响应了html文件的传送后,可以不等浏览器请求该html依赖的其他css,js,直接先传送过去。http3基于QUIC(它基于udp)和TLS(传输层安全协议),解决了TCP算法中队列长度固定问题。
如何优化http性能?
其实就是问如何使用http比较好。尽可能的不请求(使用缓存),和减少请求(避免重定向、合并请求),对传送的内容进行压缩等。
其他
http2的HPACK算法
HPACK 算法主要包括三个组成部分,用于在 HTTP/2 中进行报文首部的压缩:
- 静态表(Static Table): 静态表包含了一组预定义的常见首部字段和对应的值。这个静态表在 HPACK 规范中被定义,所有 HTTP/2 的通信都可以使用这个静态表,以便避免重复传输常见的首部字段。
- 动态表(Dynamic Table): 动态表是在通信过程中动态调整的表格,用于存储最近发送或接收的首部字段。通过动态表,HPACK可以通过索引号引用已经传输过的首部字段,以减小冗余。
- Huffman 编码: HPACK 使用 Huffman 编码对首部字段名称和值进行压缩。Huffman 编码是一种变长编码方式,通过给常见的符号分配短编码来减小整体编码长度,从而减小传输的开销。
RPC(Remote Procedure Call)
RPC 本质上不算是协议,而是一种对TCP的使用方式,对外提供的语言接口,是一种调用方式。纯TCP是面向连接的可靠的字节流传输协议。正是由于字节流的特性,它存在粘包的特点,即两个数据传输报文没有标识区分,需要在应用层标识。RPC和http,对tcp封装,都解决了这个问题。
RPC相比于简单的
http
,可以改变协议本身的特性,所以能够做到更高的性能;而http只能在标准规定的报文的header部分,对协议进行扩展。http
用于B/S架构,更古老的RFC
被广泛用于C/S
架构。RPC在定制化的情况下,比
http1.1
的性能更好。RFC也可以在底层使用http
。
webSocket
webSocket
和http
一样,都是应用层协议。它的模型是全双工的,而不是http
的请求-响应半双工模型。它适用于浏览器端和服务器端需要频繁相互通信、实时同步性极强的场景(比如网页游戏、模拟终端、在线聊天室、在线协同办公...)。
最后
总的来讲,http
作为一个应用层的协议,试图为浏览器
提供一个完美的数据传输方案。此外,它的泛用性证明它不仅仅适用于网页传输,在各种非网页请求下的数据传输也有不错的表现。网络内部是一个非常复杂的东西,但是幸好它被封装为了一个个可插拔的、可配置的简单黑盒接口。
基于http
协议,就能够满足绝大部分的数据传输需求。对于定制化的需求,许多优秀的协议如gRPC
、webSocket
也有相对应的使用场景。实现是在需求中产生的,在新的需求出现时,想必会出现更加强大、性能更好、当然也更定制化的协议。