聊聊HTTP

理解应用层与HTTP

应用层研究的是两个应用进程如何交换报文,本质上是进程通信,开发者是可以直接控制的。而应用层协议本质上是为了制定一个规则——如果进程B提供某些服务(如HTTP),那么进程A与进程B之间应该遵循什么样的报文交换规则和报文语法。
应用层不需要关心报文如何传输到对面,因为应用层可以直接使用传输层提供的报文传输服务,关注点也不再是两个端点之间的通信,而是两个进程之间的通信(如客户端进程和服务器进程)

HTTP超文本传输协议,如果一个服务器进程支持超文本传输服务(web服务器),那么客户端进程想要请求服务器进程的某些资源,则必须遵守HTTP协议进行报文交换。
超文本是一种非线性组织文本的方式,它的内容含有大量超链接,就像一个指向其他资源的指针,可以指向一个动态资源或静态资源包括servlet、文字、图片、视频、音频资源等。其中html超文本标记语言,是一种标记语言,不需要编译便可以直接被浏览器识别并渲染,是超文本的一种格式。

WEB是一个基于CS架构的网络应用,web可以大致分为四部分组成:【1】应用层协议【2】web浏览器【3】web服务器【4】传输的文档格式标准(如html)

网站就是网页的集合,网页的本质是一个个html文件,其中web浏览器实现了http客户端,用于请求超文本(html),如果web服务器实现了HTTP协议,那么它便可以被看做HTTP服务器(用于提供超文本传输服务的服务器),用于存储超文本,每个超文本通过URL寻址(同一资源定位符)。Http协议定义了http客户端向http服务器请求超文本的方式已经服务器向客户端回传超文本的方式。

网址

url是统一资源定位符,也就是常说的网址,用于定位某种资源。(不一定非用于web)

URL统一资源定位符(locator)是一个更具体的概念,它不但说明了资源是什么,还告诉你怎么获取。而统一资源标识符URI(identitor)是抽象的,它可以定位出某一个资源,但是你不知道如何获取。总结:URL是URI的子集,URI是抽象的,URL是具体的。

语法规则
Scheme : // host . domain : port/path/filename
schema定义因特网服务类型,如ftp 、 http 、 file等
Host定义域主机(http的默认主机是www)
Domain定义因特网域名
Port定义主机上的端口号
Path定义资源在服务器上的路径
Filename定位资源的名称

把WEB看作一个特殊的文件系统,那么URL就是普通的文件路径,和用于本地定位文件的路径没有差异。而文件系统是虚拟的,schema可以看作对这个文件系统实现的标识。两个不同的进程进行通信,其实也简单看作两个文件之间的数据复制操作(把网卡设备看作一个文件),我们从网络获取某个资源,也无非就是从文件系统检出某些文件,复制到本机的文件系统中。

地址栏一敲…

这里简单聊一聊个人对“地址栏一敲”这个问题的思路。首先,我觉得没必要一直深挖到物理层,最多答到网络层即可,其实答到传输层即可。
我们可以从两方面答:客户端行为(浏览器发送)服务器行为(服务器接收)

地址栏一敲,那么浏览器肯定先对输入进行检查,并且生成一个正确的URL。地址栏拼接好之后,浏览器肯定接着准备生成请求报文发出去,但是这个时候有两个工作可能还没做,一个是域名解析,另外一个是传输层建立连接。

这里域名解析和建立间接是否展开说看个人吧,如果可以展开说说也无妨。

域名解析完成后,浏览器构造一个请求报文就可以委托为操作系统发出去了(socket相关系统调用),操作系统发送报文前则需要先和服务器端点三次握手建立连接,如果基于http协议通信那么这时就可以直接把上层呈递的报文看作字节流传输出去了,如果是基于https协议还需要接着进行安全层的握手,进行算法协商、身份鉴别、计算会话秘钥等操作(是否展开看个人)。

还能再展开,数据从用户缓冲发送到操作系统为socket维护的发送缓冲,DMA设备将发送缓冲的数据拷贝到网卡控制器的缓冲区(一个网卡对应一个ip地址),最终数据到达服务器的网卡控制器的缓冲区,DMA将数据拷贝到socket的接收缓冲区时(数据准备完毕)通知CPU。

数据成功发出后,服务器进程将收到浏览器的请求,一般服务器会为每个客户单独开启一个线程,并且将请求包装为request对象,服务器根据用户的请求从服务器路径中找到可以处理服务请求的处理器(controller),并将处理得到的结果封装为一个response对象返回(例如,这是一个get请求,最终会找到一个可以处理get请求的处理器,也有可能直接交给静态资源处理器)。最终服务器返回的结果会基于http响应的形式达到客户端,浏览器拿到响应报文后,解析响应头、渲染响应体。

本题十分开放,很多地方都可以展开,不过也可以不深度展开而展现广度,如根据网站的业务加入一些对CDN、cookie/session验证的想法。浏览器一方比较固定,但是服务器如何响应就可以相对开放一些了(服务器软件本身做了什么+处理器(controller)大概做了什么)。

二次补充:开放题的核心要素就是能够去分类讨论,请求的是静态资源还是动态资源?配置了缓存吗?这个请求是直接给源站还是给代理服务器?配置反向代理吗…只有不断分类讨论,才能让提问者看见你的广度,而且你的回答“链路”越长,越能够看见你的深度,没错,这种问题就是多叉树式展开。

HTTP细节

Http请求头的通用格式:
请求行/一行行的请求头/空行/请求体
其中请求头由三部分组成:请求方法+URL+版本 如GET /ABC/A.jsp HTTP/1.1

Http响应头的通用格式:
响应行/一行行的响应头/空行/响应体
其中响应头由三部分组成:版本+响应码+短语 如 HTTP/1.1 200 OK

常见的头部字段

其中accept和accept-charset是客户端期望服务器返回的资源类型,以及内容的编码。可以是多个值,使用逗号分割。
content-Type就是服务器对以上两个头信息的回应——例如Content-Type:text/html;charset=utf8,代表当前响应体的内容基于utf8编码,如果浏览器想要正确的读取响应体的内容,那么它需要使用utf8解码。

编码就是将字符在某一映射表下(字符表),转换为字节。人看不懂字节,只能看懂字符,但是传输是基于字节的,因此传输前需要将字符编码为字节,而不同的编码方式,最终获得的字节总数也不一样,因此不止一种编码方式。而一方使用错误的解码方式去解编码则会导致乱码。

contentType指的都是MIME类型,指示资源所属类型。如果遇到浏览器收到未知类型会提示下载。

accept-encoding客户告诉服务器自己能够接收什么样的压缩格式,content-Encoding服务器告诉客户端,数据使用了什么压缩格式

accept-Language表示客户端期待的原因,而回应即content-Language
content-Length表示响应体的长度,收到响应报文的一方可以通过该字段直接获取响应体的长度。

Host是http请求报文必须携带的头,否则服务器将会抛出400 bad request。http1.1允许一个服务器部署多个web网站(一个服务器软件可以托管多个不同域名的网站,host可以指定具体的虚拟主机),用来指定服务器的域名

connect字段用于客户端声明连接复用,http1.1之后默认长连接connect:keep-alive,而http1.1之前只有短连接connect:close

长连接和短连接

http是不需要建立连接的,建立完报文就直接委托传输层发送出去了,这里的连接指的是TCP的连接。
短连接下,一次会话等于http请求报文传输完毕、http响应报文传输完毕,之后这个连接将会被主动释放。也就是说每个连接都是和一次request/response绑定的。

请求一个包含十个图片标签的html,最终会创建11个短的TCP链接。而如果使用长连接,只需要创建一个长连接。

长连接下,一次会话可以包含多次request/response,也就是说TCP连接不会在一次request/response后立即被释放,而是会被复用。
http1.1默认支持长连接,请求头中加入connect:keep-alive,同一个用户的多次请求将复用同一条连接。

长连接的优点,是在多次连续通信时,节省了创建和关闭连接的开销(每次创建连接至少占用一个RTT)。但是需要占用额外的内存去维护这个连接,一旦服务器积压了过多的连接数,可能会影响服务器的性能。

现代大部分浏览器都会打开若干个并行的TCP连接,使得响应的时间缩短,一定程度上缩减了短连接创建花费的时间。短连接是独立的,而长连接是复用的,这可能导致长连接更容易被攻击而造成安全性问题。连接复用还有可能造成“上层数据报文黏连”的问题(TCP不知道要传输的数据独立的还是、连续的,要传输的数据都看作流),而短连接和UDP则不会。

但是短连接有一个很大的问题:短连接可能造成服务器短时间内创建大量连接,而且可能造成服务器大量套接字处于timeWait状态,以至于可用socket被耗尽,内存压力也很大。

使用短连接报文发送一个请求,需要两个TCP分组往返时延,同时服务器需要等待2MSL才可以释放通信套接字占用的系统资源而且每一次新建一个连接,都有分配新的缓冲区(发送缓存、接收缓存)和各种变量。

状态追踪

http是无状态的协议,服务器连续收到两个请求报文,它无法判断这两个请求报文是否来自同一个客户端。
无状态使得服务器不需要维护额外的变量去记录客户端的信息,但是也需要额外的手段去进行状态跟踪。
cookie、session、token的深刻理解,请看这里

cookie

客户端的会话技术:cookie

从编程的角度,cookie是一份小数据,是服务器首次响应客户端时,并且存储在客户端的一份小数据(键值对组织)。下次客户端访问服务器时,会自动带上这个cookie。服务器通过cookie可以区分客户端

当客户端通过HTTP协议访问服务器时,服务器会以K/V的形式将一些信息保存在客户端,客户端下次访问时会捎带这些信息。
当服务器程序调用response.addCookie()时,底层会生成cookie字符串,并加入到header中(addHeader),其中name就是“set-cookie”。
Cookie的name和value不可以设置为非ASCII字符,应该在两端使用URLEncoding进行编码和URLDecoding进行解码。
当浏览器请求某个URL时,会将符合这个URL的path对应的cookie放入请求头。服务器可以通过getCookies()获取所有的cookie

cookie的特点:
不够安全,存放数据不可以太敏感。(服务器保存在客户端上,服务器不放心啊)
存储的数据有限,而且没有统一标准,不便于服务器管理。(浏览器太杂了,有的让存储4KB有的让存储1KB,不但有限而且没有统一标准)

session

服务器的会话技术:session

客户端与服务器交互时,不需要传递所有的cookie,而是传递一个ID,这个ID(name为JSESSIONID的cookie)保存在客户端,而ID对应的数据保存在服务器
如果浏览器不支持cookie则可以进行
URL重写
,如果支持则基于cookie(支持后者则会覆盖前者的id)
当服务器第一次触发getSession时,如果没有对象(过期被清理)则会创建,并且添加到session容器中。服务器将管理所有session的生命周期,session过期将被回收,服务器关闭session将被持久化到硬盘中。根据SessionID可以对应一个Session对象。
Session的恢复和持久化必须经过stop和start方法,直接结束servlet容器的进程(kill)不会持久化。

session的特点:
存储在服务器端,而且存储一次会话多次请求的数据。
可以存储任意类型的数据,任意大小的数据(不再受到浏览器的限制,保存在服务器上也更加安全)

token

token是存储浏览器中的,服务器为用户生成token后便保存在用户本地,之后向server请求时在请求头中自动带上。服务器收到用户的token后会进行校验,判断是否合法。(和sessionId一样,都是一个自解释的字符串)

以JWT为例,token由header、payload、signature组成,服务器拥有外部不知道的秘钥,生成token时对head+payload进行加密,存入signature。当浏览器回传token,服务器再次根据head+payload计算signature并与原signature进行比对
header指定了签名算法,payload包含过期时间,已经用户标识符,服务器可以直接根据用户标识符拿到对应的用户对象。
因此只有保证server的加密秘钥不泄露,生成的token就是安全的。

token一旦生成无法令其失效,必须等待过期时间到达,因此token更适合作为一次性的命令认证,设置一个较短的有效期。(传输安全,但是存储不安全)

补充:token本质是一种上层的东西,是抽象的动态,它可以通过cookie实现、还可以基于URL实现、中间件实现等。以上的讨论主要是以其中一种实现方式json web token为例。

响应码

响应码可以分为五类,以下将列举常见的响应码:
【1】服务器收到请求,要求客户端继续执行
100 continue

通常,浏览器发送post请求时,需要两个TCP连接,先发送header,然后收到接收方的一个100响应码,然后继续传送body

101 switching protocols
【2】操作被成功处理
200 OK
【3】重定向
301 永久重定向

应用场景,原来的域名弃用了,但是还想往新域名继续引流,某些小网站直呼内行。

302 临时重定向
304 资源未修改(配合缓存使用)
305 使用代理
【4】客户端错误
400 bad request
401 未认证
403 forbidden
404 资源未找到
408 超时
【5】服务器错误
500 内部错误
501 服务器不支持该请求
502 bad gateway 代理服务器收到源服务器无效响应

如果一个服务器被DDOS干崩了,可能就会显示这个。

505 服务器不支持请求的HTTP版本

对get和put的思考

Get是一个请求,用于请求资源。而post是一个请求,用于提交信息。
Get是不安全,因为请求的参数将被拼接在URL(浏览器地址栏)中,可能会被窃取。而post提交的信息存于请求体中,用户不可见
Get请求的url有长度现在,而post内容存于响应体,可以存储大量的内容。
基本不同就是:语义、数据存放位置、数据容量、数据敏感性、安全性(安全性这一点很鸡肋,因为如果要求安全应该使用HTTPS,就算明文不写在URL上面,难道wireShark嗅探不出来body的内容吗)

请求方法之间的本质:
get、post、delete等都可以用来传输信息,get请求可以在body传输数据,而post也可以在url中传输数据,但是这不符合HTTP的语义规范。因此他们只在语义上产生区别,而安全的保证需要使用HTTPs协议

HTTP版本

HTTP采用了长连接,也称为持续连接。持续连接又可以流水线形式和非流水线(pipeline)形式。要知道,HTTP是基于TCP进行报文传输的,TCP可以一次性发出多个分组来提升效率,但是非流水线的HTTP则是类似“停等”的,浏览器必须收到服务器的前一个响应才能接着发出下一个请求。这对应短连接是很正常的,但是对于长连接就会使得TCP连接处于空闲。由于长连接中,TCP连接是多个HTTP请求复用的,因此HTTP1.1是支持流水线(pipeline)的,可以减少整体的HTTP请求响应时间。
但是HTTP1.1存在队头阻塞问题:多个HTTP请求可以在未收到前一个响应就全部发出去,但是服务器仍然按照顺序进行相应,顺序靠后的请求仍然需要等待靠前的请求被响应后才可以得到处理。(你想想,运输层全是清一色的字节流,你后发的请求先到达了在TCP看来就是提前来的,迟到的“空洞”不补上,那就等着吧)
总之:http1.1相对http1.0,增加了对长连接的支持,以及支持流水线式的长连接。
但是仍然具有缺点:头部信息不压缩,占用额外带宽。队头阻塞问题、请求无法定义优先级、服务器只能进行响应。以及明文传输的问题

HTTP2

http2支持压缩头部。使用HPACK算法,在客户端和服务器同时维护一张头信息表,使用索引替代字段。传输效率提升
http2不再使用文本格式,而是使用二进制格式,将头信息和数据体称为头信息帧数据帧
http2的数据包不是按顺序发送的,因此同一个连接中的若干个连续的数据包可能属于不同的响应。http2对同一个连接中的每个数据包都进行标记,每个请求/响应的所有数据包称为一个数据流stream。每个数据流都拥有独一无二的标记(且区分请求与响应数据流)。客户端还可以指定数据流的优先级,让服务器优先响应。(多个请求复用一个TCP连接,通过数据流标识某一个请求/响应)

http2在同一个tcp连接中发出了10个http请求,每个请求/响应所属的数据包看作一个流,而且具有编号标识它属于哪个请求或响应。http2是可以在一个连接并发处理多个请求或回应的,而且不用按照顺序意义对应(根据编号),多个HTTP请求/响应复用了一个TCP连接,而且是非串行复用的,降低了延迟,大幅提高了连接的利用率。

http2中,服务器是可以主动向客户端发送消息的,改善了传统的请求-应答模式,服务器可以提前将静态资源推送到客户端——在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待

队头阻塞问题的产生:应用层的处理顺序是request1-response1 request2-response2,此时即使request2提前到达服务器,服务器也需要先响应request1.HTTP2将request切分为若干帧,并且将一次request-response使用进行标记,并将其抽象为数据流。服务器并发处理数据流中的各种数据包。

HTTP3

http2多个数据流复用同一条连接,而且是无序的,但是在TCP看来,这就是一条普通的数据流,一旦发生丢包就会触发重传机制,这样所有的HTTP请求都必须等待丢失的包被重传。这是由TCP的特点决定的,http作为应用层协议无法干涉。

相当于http1.x的时候,http客户端发送三个包123,http服务器强制按照123去接收,而http2的时候,这三个包的发送顺序是不固定的,如果发送321,那么底层TCP不关心,它也是按照321对应的字节流去传输的,而不管最后哪一个包到达,http服务器都会进行响应。但是我们通过改变应用层协议解决了队头阻塞问题,而底层TCP仍然存在“空洞”报文无法向上呈递的问题。加入最终到达的是31缺少个2,那么空洞报文后面的分组及时到达也需要阻塞。

为了解决这个问题,HTTP3将传输层协议更换为了UDP,而且UDP不关心顺序和丢包,因此也不会出现队头阻塞和丢包重传问题。
为了保留可靠性,使用了基于UDP的QUIC协议,实现了类似TCP的可靠性传输。
QUIC有自己的一套机制可以保证传输的可靠性,当某个数据发送丢包时,之后阻塞这个流而不是影响其他流。
QUIC实际上相当于:支持可靠传输UDP + TLS + HTTP/2 的多路复用协议。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值