Tomcat本身是一个“HTTP服务器 + Servlet容器”,HTTP服务器就是处理网络通信,接收到HTTP请求后,把HTTP请求数据转成标准的ServletRequest对象,再把这个对象交给Servlet容器去处理。
HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。
假如浏览器需要从远程HTTP服务器获取一个HTML文本,在这个过程中,浏览器实际上要做两件事情。
首先,你要让服务端知道你是想获取内容 or 提交内容 然后,你需要告诉服务端你想要哪个内容。
要把这些信息以何格式放入请求?这就是HTTP协议要解决的问题。 即HTTP协议本质是一种浏览器与服务器之间约定好的通信格式。 浏览器与服务器之间具体是怎么工作的?
Tomcat作为一个HTTP服务器,在这个过程中主要
你有没有注意到,在浏览器和HTTP服务器之间通信的过程中,首先要将数据打包成HTTP协议的格式,那HTTP协议的数据包具体长什么样呢?
HTTP请求数据由三部分组成,分别是请求行、请求报头、请求正文。 比如一个简单的登录请求,浏览器会发如下HTTP请求:
GET /login?callBack=xxx HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Host: www.nowcoder.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
{"country":86, "password":"22"}
当这个HTTP请求数据到达Tomcat后,Tomcat会把HTTP请求数据字节流解析成一个Request对象,这个Request对象封装了HTTP所有的请求信息。 接着Tomcat把这个Request对象交给Web应用去处理,处理完后得到一个Response对象,Tomcat会把这个Response对象转成HTTP格式的响应数据并发送给浏览器。
HTTP的响应也是由三部分组成:状态行、响应报头、报文主体。
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Thu, 15 Jul 2021 02:37:13 GMT
Server: openresty
X-Backend-Response: 0.002
HTTP协议本身是无状态的,不同请求间协议内容无相关性,即本次请求与上次请求没有内容的依赖关系,本次响应也只针对本次请求的数据,至于服务器应用程序为用户保存的状态是属于应用层,与HTTP协议本身无关!
无状态,就是为完成某一操作,请求里包含了所有信息,服务端无需保存请求的状态,即无需保存session,无session的好处是带来了服务端良好的可伸缩性,方便failover,请求被LB转到不同server实例也没有区别。这样看,正是REST架构风格,才有了HTTP的无状态特性。REST和HTTP1.1其实都出自同一人之手。
http最初设计成无状态的是因为只是用来浏览静态文件的,无状态协议已经足够,也没什么负担。 但随着web发展,它需要变得有状态,但是不是就要修改HTTP协议使之有状态? 不需要:
在分布式场景下,为减少Session不一致,一般将Session存储到Redis,而非直接存储在server实例。
现在的Web容器都支持将session存储在第三方中间件(如Redis)中,为什么大多都绕过容器,直接在应用中将会话数据存入中间件中? 因为用Web容器的Session方案需要侵入特定的Web容器,用Spring Session可能比较简单,它使得程序员甚至感觉不到Servlet容器的存在,可以专心开发Web应用。它是通过Servlet规范中的Filter机制拦截了所有Servlet请求,偷梁换柱,将标准Servlet请求对象包装,换成自己的Request包装类对象,当程序员通过包装后的Request对象的getSession方法拿Session时,是通过Spring拿Session,和Web容器无关。
有了 session 就够了吗?这还有一个问题:Web应用不知道你是谁。 比如你登录淘宝,在购物车添加了三件商品,刷新一下网页,这时系统提示你仍然处未登录状态,购物车也空了! 因此HTTP协议需要一种技术让请求与请求之间建立联系,服务器需要知道这个请求来自谁,于是出现了Cookie。
Cookie是HTTP报文的一个请求头,Web应用可以将用户的标识信息或者其他一些信息(用户名等)存储在Cookie。用户经过验证后,每次HTTP请求报文中都包含Cookie,这样服务器读取这个Cookie请求头就知道用户是谁了。 Cookie本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息。
由于Cookie以明文的方式存储在本地,而Cookie中往往带有用户信息,这样就造成了非常大的安全隐患,于是产生了Session。
可理解为服务器端开辟的存储空间,里面保存了用户的状态,用户信息以Session的形式存储在服务端。当用户请求到来时,服务端可以把用户的请求和用户的Session对应起来。
通过Cookie,浏览器在Cookie中填个了类似sessionid的字段标识请求。
在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同的创建Session的方法。
在Java中,是Web应用程序在调用HttpServletRequest的getSession方法时,由Web容器(比如Tomcat)创建的。
Tomcat的Session管理器提供了多种持久化方案来存储Session,通常会采用高性能的存储方式,比如Redis,并且通过集群部署的方式,防止单点故障,从而提升高可用。同时,Session有过期时间,因此Tomcat会开启后台线程定期的轮询,如果Session过期了就将Session失效。
引入session是因为cookie存在客户端,有安全隐患;但是session id也是通过cookie由客户端发送到服务端,虽然敏感的用户信息没有在网络上传输了,但是攻击者拿到sessionid也可以冒充受害者发送请求,这就是为什么我们需要https,加密后攻击者就拿不到sessionid了,另外CSRF也是一种防止session劫持的方式。 token比如jwt token,本质也就是个加密的cookie。
HTTP协议和其他应用层协议一样,本质上是一种通信格式。类比现实生活:
像Cookie这些信息就像在信封表面的那些发信人相关个人信息。 TCP连接就是送信员,负责真正的传输数据信息。无状态表示每次寄信都是新的信封。 在服务端看来这些信之间是没有关系的,并且服务端通过阅读这封信就得到它要的全部信息,无需从其它地方(比如Session)获取这封信的更多上下文信息,服务端就知道怎么处理和回信。
HTTP协议就是浏览器与服务器之间的沟通语言,具体交互过程是请求、处理和响应。
在HTTP/1.0时期,每次HTTP请求都会创建一个新的TCP连接,请求完成后之后这个TCP连接就会被关闭。 这种通信模式的效率不高,所以在HTTP/1.1中,引入了HTTP长连接的概念。
使用长连接的HTTP协议,会在响应头加入Connection:keep-alive
。
这样当浏览器完成一次请求后,浏览器和服务器之间的TCP连接不会关闭,再次访问这个服务器上的网页时,浏览器会继续使用这一条已经建立的连接,也就是说两个请求可能共用一个TCP连接。
keep-alive表示TCP的连接可复用,指的是利用已有传输通道进行HTTP协议内容的传输,省去创建、关闭连接的开销以达到提升性能效果(类似线程池的复用线程)。 Connection:keep-alive只是建立TCP层的状态,省去了下一次的TCP三次握手,而HTTP本身还是继续保持无状态。 应用程序其实一般不关心这次HTTP请求的TCP传输细节,只关心HTTP协议的内容,因此只要复用TCP连接时做好必要的数据重置,是不算有状态的。
HTTP的无状态性与共用TCP连接发送多个请求之间没有冲突,这些请求之间相对独立,唯一的关系可能只有发送的先后顺序关系。 HTTP/1.1中的长连接没有解决 head of line blocking,请求是按顺序排队处理的,前面的HTTP请求处理会阻塞后面的HTTP请求,虽然HTTP pipelining对连接请求做了改善,但是复杂度太大,并没有普及,以至于后面的连接必须等待前面的返回了才能够发送。这个问题直到HTTP/2.0采取二进制分帧编码方式才彻底解决。