02 | HTTP协议必知必会

在开始学习Web容器之前,我想先问你一个问题:HTTP和HTML有什么区别?

为什么我会问这个问题?你可以把它当作一个入门测试,检测一下自己的对HTTP协议的理解。因为Tomcat和Jetty本身就是一个“HTTP服务器 + Servlet容器”,如果你想深入理解Tomcat和Jetty的工作原理,我认为理解HTTP协议的工作原理是学习的基础。

如果你对这个问题还稍有迟疑,那么请跟我一起来回顾一下HTTP协议吧。

HTTP的本质

HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。

下面我通过一个例子来告诉你HTTP的本质是什么。

假如浏览器需要从远程HTTP服务器获取一个HTML文本,在这个过程中,浏览器实际上要做两件事情。

  • 与服务器建立Socket连接。

  • 生成请求数据并通过Socket发送出去。

第一步比较容易理解,浏览器从地址栏获取用户输入的网址和端口,去连接远端的服务器,这样就能通信了。

我们重点来看第二步,这个请求数据到底长什么样呢?都请求些什么内容呢?或者换句话说,浏览器需要告诉服务端什么信息呢?

首先最基本的是,你要让服务端知道你的意图,你是想获取内容还是提交内容;其次你需要告诉服务端你想要哪个内容。那么要把这些信息以一种什么样的格式放到请求里去呢?这就是HTTP协议要解决的问题。也就是说,HTTP协议的本质就是一种浏览器与服务器之间约定好的通信格式。那浏览器与服务器之间具体是怎么工作的呢?

HTTP工作原理

请你来看下面这张图,我们过一遍一次HTTP的请求过程。

从图上你可以看到,这个过程是:

1.用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。

2.浏览器向服务端发出TCP连接请求。

3.服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接。

4.浏览器将请求数据打包成一个HTTP协议格式的数据包。

5.浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。

6.服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。

7.得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。

8.服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。

9.服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。

10.浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是HTML。

11.浏览器将HTML文件展示在页面上。

那我们想要探究的Tomcat和Jetty作为一个HTTP服务器,在这个过程中都做了些什么事情呢?主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。这里请你注意,可能有成千上万的浏览器同时请求同一个HTTP服务器,因此Tomcat和Jetty为了提高服务的能力和并发度,往往会将自己要做的几个事情并行化,具体来说就是使用多线程的技术。这也是专栏所关注的一个重点,我在后面会进行专门讲解。

HTTP请求响应实例

你有没有注意到,在浏览器和HTTP服务器之间通信的过程中,首先要将数据打包成HTTP协议的格式,那HTTP协议的数据包具体长什么样呢?这里我以极客时间的登陆请求为例,用户在登陆页面输入用户名和密码,点击登陆后,浏览器发出了这样的HTTP请求:

你可以看到,HTTP请求数据由三部分组成,分别是请求行、请求报头、请求正文。当这个HTTP请求数据到达Tomcat后,Tomcat会把HTTP请求数据字节流解析成一个Request对象,这个Request对象封装了HTTP所有的请求信息。接着Tomcat把这个Request对象交给Web应用去处理,处理完后得到一个Response对象,Tomcat会把这个Response对象转成HTTP格式的响应数据并发送给浏览器。

我们再来看看HTTP响应的格式,HTTP的响应也是由三部分组成,分别是状态行、响应报头、报文主体。同样,我还以极客时间登陆请求的响应为例。

具体的HTTP协议格式,你可以去网上搜索,我就不再赘述了。为了更好地帮助你理解HTTP服务器(比如Tomcat)的工作原理,接下来我想谈一谈Cookie跟Session的原理。

Cookie和Session

我们知道,HTTP协议有个特点是无状态,请求与请求之间是没有关系的。这样会出现一个很尴尬的问题:Web应用不知道你是谁。比如你登陆淘宝后,在购物车中添加了三件商品,刷新一下网页,这时系统提示你仍然处于未登录的状态,购物车也空了,很显然这种情况是不可接受的。因此HTTP协议需要一种技术让请求与请求之间建立起联系,并且服务器需要知道这个请求来自哪个用户,于是Cookie技术出现了。

1. Cookie技术

Cookie是HTTP报文的一个请求头,Web应用可以将用户的标识信息或者其他一些信息(用户名等)存储在Cookie中。用户经过验证之后,每次HTTP请求报文中都包含Cookie,这样服务器读取这个Cookie请求头就知道用户是谁了。Cookie本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息

2. Session技术

由于Cookie以明文的方式存储在本地,而Cookie中往往带有用户信息,这样就造成了非常大的安全隐患。而Session的出现解决了这个问题,Session可以理解为服务器端开辟的存储空间,里面保存了用户的状态,用户信息以Session的形式存储在服务端。当用户请求到来时,服务端可以把用户的请求和用户的Session对应起来。那么Session是怎么和请求对应起来的呢?答案是通过Cookie,浏览器在Cookie中填充了一个Session ID之类的字段用来标识请求。

具体工作过程是这样的:服务器在创建Session的同时,会为该Session生成唯一的Session ID,当浏览器再次发送请求的时候,会将这个Session ID带上,服务器接受到请求之后就会依据Session ID找到相应的Session,找到Session后,就可以在Session中获取或者添加内容了。而这些内容只会保存在服务器中,发到客户端的只有Session ID,这样相对安全,也节省了网络流量,因为不需要在Cookie中存储大量用户信息。

3. Session创建与存储

那么Session在何时何地创建呢?当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同的创建Session的方法。在Java中,是Web应用程序在调用HttpServletRequest的getSession方法时,由Web容器(比如Tomcat)创建的。那HttpServletRequest又是什么呢?别着急,我们下一期再聊。

Tomcat的Session管理器提供了多种持久化方案来存储Session,通常会采用高性能的存储方式,比如Redis,并且通过集群部署的方式,防止单点故障,从而提升高可用。同时,Session有过期时间,因此Tomcat会开启后台线程定期的轮询,如果Session过期了就将Session失效。

本期精华

HTTP协议和其他应用层协议一样,本质上是一种通信格式。回到文章开头我问你的问题,其实答案很简单:HTTP是通信的方式,HTML才是通信的目的,就好比HTTP是信封,信封里面的信(HTML)才是内容;但是没有信封,信也没办法寄出去。HTTP协议就是浏览器与服务器之间的沟通语言,具体交互过程是请求、处理和响应。

由于HTTP是无状态的协议,为了识别请求是哪个用户发过来的,出现了Cookie和Session技术。Cookie本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息;Session可以理解为服务器端开辟的存储空间,里面保存的信息用于保持状态。作为Web容器,Tomcat负责创建和管理Session,并提供了多种持久化方案来存储Session。

课后思考

在HTTP/1.0时期,每次HTTP请求都会创建一个新的TCP连接,请求完成后之后这个TCP连接就会被关闭。这种通信模式的效率不高,所以在HTTP/1.1中,引入了HTTP长连接的概念,使用长连接的HTTP协议,会在响应头加入Connection:keep-alive。这样当浏览器完成一次请求后,浏览器和服务器之间的TCP连接不会关闭,再次访问这个服务器上的网页时,浏览器会继续使用这一条已经建立的连接,也就是说两个请求可能共用一个TCP连接。

今天留给你的思考题是,我在上面提到HTTP的特点是无状态的,多个请求之间是没有关系的,这是不是矛盾了?

不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。

精选留言

  • 吃饭饭

    2019-05-13 22:41:50

    我一直不太理解什么是无状态,restful经常听说是无状态的,是一个概念吗?求解答
    作者回复

    我的理解是REST是一种架构风格:将网络上的信息实体看作是资源,可以是图片、文件、一个服务...资源用URI统一标识,URI中没有动词哦,这是因为它是资源的标识,那怎么操作这些资源呢,于是定义一些动作:GET、POST、PUT和DELETE。通过URI+动作来操作一个资源。所谓的无状态说的是,为了完成一个操作,请求里包含了所有信息,你可以理解为服务端不需要保存请求的状态,也就是不需要保存session,没有session的好处是带来了服务端良好的可伸缩性,方便failover,请求被LB转到不同的server实例上没有差别。从这个角度看,正是有了REST架构风格的指导,才有了HTTP的无状态特性,顺便提一下,REST和HTTP1.1出自同一人之手。但是理想是丰满的,现实是骨感的,为了方便开发,大多数复杂的Web应用不得不在服务端保存Session。为了尽量减少Session带来的弊端,往往将Session集中存储到Redis上,而不是直接存储在server实例上..

    2019-05-14 01:42:13

  • 阿斯蒂芬

    2019-05-13 19:05:39

    Http的无状态我理解是指不同请求间协议内容无相关性,即本次请求与上次请求没有内容的依赖关系,本次响应也只针对本次请求的数据,至于服务器应用程序为用户保存的状态是属于应用层,与协议是无关的。
    keep-alive表示tcp的连接可以复用,指的是利用已有的传输通道进行http协议内容的传输,省去创建/关闭连接的开销达到提升性能的效果。应用程序其实一般不关心这次Http请求的TCP传输细节,只关心Http协议的内容,因此只要复用tcp连接时做好必要的数据重置,是不算有状态的。
    作者回复

    说的很清楚 👍

    2019-05-13 19:17:47

  • Dark

    2019-05-14 09:37:44

    无状态的协议,使用cookie、session等机制实现有状态的的web。
    无状态是指协议对于事务处理没有记忆功能,对同一个url请求没有上下文关系,每次的请求都是独立的,服务器中没有保存客户端的状态。HTTP协议长连接、短连接实质上是TCP协议的长连接、短连接。长连接省去了较多的TCP建立、关闭操作,减少了浪费,节约时间;短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。
    那为什么HTTP协议会被设计成无状态的呢?http最初设计成无状态的是因为只是用来浏览静态文件的,无状态协议已经足够,也没什么其他的负担。随着web的发展,它需要变得有状态,但是不是就要修改http协议使之有状态呢?是不需要的。因为我们经常长时间逗留在某一个网页,然后才进入到另一个网页,如果在这两个页面之间维持状态,代价是很高的。其次,历史让http无状态,但是现在对http提出了新的要求,按照软件领域的通常做法是,保留历史经验,在http协议上再加上一层实现我们的目的。所以引入了cookie、session等机制来实现这种有状态的连接。
    作者回复

    说的很好很详细👍

    2019-05-14 10:13:22

  • yang

    2019-05-13 23:02:06

    http1.0: 买一个信封只能传送一个来回的信。
    http1.1: keep–alive:买一个信封可以重复使用,但前提是得等到服务端把这个信封送回来。

    作者回复

    优秀👍

    2019-05-13 23:29:13

  • 刘为红

    2019-05-14 07:25:20

    sessionid是服务端生成的,服务端通过set-cookie放在http的响应头里,然后浏览器写到cookie里,后续每次请求就会自动带上来了,这点感觉讲得不是很清楚
    作者回复

    谢谢指出~

    2019-05-14 18:29:33

  • 微信小助手

    2019-05-13 20:34:43

    HTTP的无状态性与共用TCP连接发送多个请求之间没有冲突,
    这些请求之间相对独立,唯一的关系可能只有发送的先后顺序关系。
    此外,HTTP/1.1中的长连接依然没有解决 head of line blocking 的问题,
    后面的连接必须等待前面的返回了才能够发送,
    这个问题直到HTTP/2.0采取二进制分帧编码方式才彻底解决。
    作者回复

    👍Tomcat和Jetty都支持HTTP2.0了

    2019-05-13 21:08:37

  • 逍遥哥哥

    2019-05-14 22:44:40

    老师,您好,现在的web容器都支持将session存储在第三方中间件(如redis)中,为什么很多公司喜欢绕过容器,直接在应用中将会话数据存入中间件中?
    作者回复

    用Web容器的Session方案需要侵入特定的Web容器,用Spring Session可能比较简单,不需要跟特定的Servlet容器打交道。

    这正是Spring喜欢做的事情,它使得程序员甚至感觉不到Servlet容器的存在,可以专心开发Web应用。但是Spring到底做了什么,Spring Session是如何实现的,我们还是有必要了解了解~

    其实它是通过Servlet规范中的Filter机制拦截了所有Servlet请求,偷梁换柱,将标准的Servlet请求对象包装了一下,换成它自己的Request包装类对象,这样当程序员通过包装后的Request对象的getSession方法拿Session时,是通过Spring拿Session,没Web容器什么事了。

    2019-05-14 23:58:07

  • Geek_28b75e

    2019-05-24 15:28:50

    老师,我们经常说的cookie跨域问题中,跨域是什么概念呢
    作者回复

    cookie有两个重要属性:
    domain字段 :表示浏览器访问这个域名时才带上这个cookie
    path字段:表示访问的URL是这个path或者子路径时才带上这个cookie

    跨域说的是,我们访问两个不同的域名或路径时,希望带上同一个cookie,跨域的具体实现方式有很多..

    2019-05-24 22:10:01

  • 许童童

    2019-05-13 19:05:08

    Connection:keep-alive只是建立TCP层的状态,省去了下一次的TCP三次握手,而HTTP本身还是继续保持无状态的特点。
  • 二两豆腐

    2019-05-14 22:13:22

    建立tcp链接是可以理解为先修路 ,每次建立推出链接也就是先把路给修好,路修好之后才能走在这条路上送信(http),keep-alive指的就是需要送信的时候要不要修路,还是在走已经修好的路上去送信,信是一封一封的送,上一封信和下一封信没啥联系,这就是无状态
  • 今夜秋风和

    2019-05-14 20:29:46

    服务端怎么检测这个tcp链接什么时候可以销毁释放?如果一个连接里面处理一个长事物,其他的请求会不会排队等待
    作者回复

    服务端会设置连接超时时间,如果TCP连接上超过一段时间没有请求数据,服务端会关闭这个连接。

    在HTTP1.1中,请求是按顺序排队处理的,前面的HTTP请求处理会阻塞后面的HTTP请求,虽然HTTP pipelining对连接请求做了改善,但是复杂度太大,并没有普及,这个问题在HTTP2.0中得到了解决。

    2019-05-14 22:14:01

  • Royal

    2019-05-13 21:52:30

    您好!上面提到的引入session是因为cookie存在客户端,有安全隐患;但是session id也是通过cookie由客户端发送到服务端,同样有安全隐患啊?
    作者回复

    是的,虽然敏感的用户信息没有在网络上传输了,但是攻击者拿到sessionid也可以冒充受害者发送请求,这就是为什么我们需要https,加密后攻击者就拿不到sessionid了,另外CSRF也是一种防止session劫持的方式。

    2019-05-13 22:26:07

  • 有所思

    2019-08-08 18:02:11

    用token机制呢
    作者回复

    token比如jwt token本质是个加密的cookie

    2019-08-11 17:51:13

  • 八百

    2019-05-26 23:05:54

    老师我有二个问题
    1.如果没请求中没有jsessionid ,每次发起http请求是否都会生成session,如果每次请求都生成session,那么是不是可以作为一个攻击的手段啊,让服务器存在大量session,导致oom
    2.如果我从别人的浏览器中拿到jsessionid,把它放在我自己的请求头中,是不是在服务端对应同一个session,那是不是就可以窃取人家信息了。。
    作者回复

    1. 会不会产生session取决于Web应用如何实现,如果在用户没有登录的情况下调用Request.getSession(true),会产生Session,也就有你说的那个问题。
    2.对的

    2019-05-27 08:12:44

  • 郑泽洲

    2019-08-04 17:54:18

    请教李号双老师,现在流行的token验证机制和session技术相比如何?
    服务器集中维护session;客户端有对应的cookie保存session id,优点是一次登录后就不用输入密码了;但是缺点很多,首先是服务器内存压力大,要来维护session,并且要将session持久化,也有成本;其次是不能动态扩展,另外是存在浪费,比如用于直接关闭了浏览器后只能超时退出。
    token验证机制,反客为主,由客户端来管理和验证session。服务器压力小,相当于颁发了一张证书给客户端,里面规定了这个客户有效登录和超时时间,并有服务器对此的签名,然后服务器就不管了,服务器易于横向扩展,服务器也不需要将其持久化。
  • 业余草

    2019-05-21 10:30:45

    这里没有讲,为什么 Tomcat 等要采用 HTTP,为什么不采用其他的一些协议?HTTP 协议的好处是什么?以及扩展到 HTTPS 上!
  • Joker

    2019-05-18 07:19:28

    老师,我这样进行比喻您看看合不合适,原来的http老师,我这样进行比喻您看看合不合适。原来的http/1.0的时期,如果把每次发送http数据包都看成一次送信的过程的话,那么就是每次发送都会新叫一个送信员(也就是新建一个TCP连接)。
    http/1.1的长链接就相当于给了你和服务器和客户端有了专属的随时待命的送信员,你就免去了以前每次都要寻找送信员的过程。
    而http 的无状态我认为就是代表:每次寄信都是用的新的信封。额,这个和评论区上面的那个同学的观点有些差异,还请老师详细说说,谢谢了。
    像cookie这些信息就像在信封表面的那些发信人的地址这些信息。
    虽然设置长连接开启是在应用层的http协议,但是真正起作用的是在传输层的TCP协议。
    作者回复

    Joker你好,把TCP连接当做送信员的比喻很形象也很贴切!无状态表示每次寄信都是新的信封,这也是对的,我再多说几句,在服务端看来这些信没有关系的,并且服务端通过阅读这封信就得到了它要的全部信息,不需要从其他地方(比如Session里)来获取这封信的更多上下文信息,服务端就知道怎么处理和回信。

    2019-05-18 08:59:36

  • 五先生

    2019-05-25 10:07:46

    keepalive建立的通道:是基于什么来建立的呢?也就是什么情况下会是两个通道?一次会话吗?
    作者回复

    你说的“通道”我理解是TCP连接。为了开启keepalive。

    在HTTP1.0中,你需要在请求头加上“Connection:Keep-Alive。

    在HTTP1.1中,默认就是keepalive。

    2019-05-27 09:42:32

  • on the way

    2019-05-20 18:29:20

    那么 Session 是怎么和请求对应起来的呢?答案是通过Cookie,那cookie被禁用了怎么办
    作者回复

    URL重写和隐藏表单域。

    2019-05-20 20:12:12

  • Geek_9b24cd

    2019-05-17 16:34:16

    老师你好,我在Java中用HttpRequest向其他服务器发送http请求时为了节约创建连接的开销加入了长链接,然后请求就一直超时,关闭长链接之后就能正常调用了,可能是什么原因呢?
    作者回复

    你说的信息有点少,我只能靠猜了啊,超时错误分两种,connect timeout和read timeout,前者可能是网络问题,或者服务端连接池不够用了。后者是连接已经建立了,但是服务端太忙了,不能及时处理完你的请求~

    2019-05-17 17:57:47