Http知识汇总
前言
Github:https://github.com/HealerJean
一、Http 协议
1、http 连接
HTTP(HyperTextTransferProtocol,超文本传输协议)是 Web 应用的基础通信协议,也是移动端常用的网络协议之一。HTTP是一种应用层协议,建立在可靠的 TCP 协议之上。
1) 短连接、长连接
HTTP/1.0默认使用短连接:- 每次请求都需要单独建立一次
TCP连接,服务器处理完请求后立即关闭连接。这种方式开销大、效率低。
- 每次请求都需要单独建立一次
HTTP/1.1默认使用长连接(持久连接):- 同一个
TCP连接上可以连续发送多个HTTP请求和响应,无需为每个请求重新建立连接,显著减少了连接建立开销,提升性能 - 通过
Connection: keep-alive头部实现(HTTP/1.1中默认启用)。 HTTP协议本身是无状态的,它并不负责维护客户端的“在线状态”。- 某些应用场景(如即时通讯、实时推送)中,客户端会定期向服务器发送“心跳请求”以表明自身在线,但这属于业务层的设计,并非
HTTP协议的固有机制。 - 真正的连接保活也可依赖
TCP层的SO_KEEPALIVE选项,但其默认超时时间较长(通常数小时),不适合实时性要求高的场景。
- 某些应用场景(如即时通讯、实时推送)中,客户端会定期向服务器发送“心跳请求”以表明自身在线,但这属于业务层的设计,并非
- 同一个
| 场景 | 需要“心跳” | 说明 |
|---|---|---|
普通网页浏览(HTTP/1.1) |
不需要 | 浏览器自动复用连接,用完可保持空闲一段时间( Keep-Alive: timeout=5 ) |
WebSocket /MQTT / 自定义协议 |
需要 | 应用层需实现心跳包检测连接有效性 |
移动 App 后台保活 |
可能需要 | 因系统限制(如 Android 省电策略),需主动发包防止连接被回收 |
2)长连接、长轮询
- 介绍
- 长连接(如
WebSocket、TCPKeep-Alive):- 连接建立后长期保持,服务端需为每个连接维护状态(内存、文件描述符等)百万级连接对服务器压力极大。
- 长轮询(
LongPolling):长轮询的唯一优势是:无需心跳、无状态、兼容 HTTP 生态(配置中心)。- 基于
HTTP长连接(Keep-Alive),客户端发起一次请求后,服务端hold住连接直到有数据或超时,响应后连接可复用或关闭。 - 虽然每次交互是“请求-响应”,但单次连接寿命较长,服务端仍需为每个
pending请求维护状态(如eventloop中的等待队列),高并发下同样有压力,但通常低于真长连接。
- 基于
- 长连接(如
-
一句话总结:
-
想“用
HTTP伪装成推送”且实时性要求不是极端高,选长轮询; -
想“真正持久、双向、低延迟”,直接上长连接(如
WebSocket)。
-
a、原理比较
| 维度 | 长轮询 | 长连接 |
|---|---|---|
| 核心机制 | 客户端发请求 → 服务端挂起 → 有数据/超时返回 → 客户端立即重发 | TCP 连接长期保持,可多次双向通信 |
| 实时性 | 比短轮询好,数据一产生就能立刻返回;但仍有“请求-等待”周期,延迟在毫秒到秒级。 | 数据随时可推送,延迟最低,能做到“即时到达”。 |
| 连接开销 | 每次“挂起”都会占用服务器线程/进程资源;并发高时容易把连接数耗尽。 | 连接复用,省去了反复握手、挥手的消耗;但保持空闲连接本身也有内存和句柄开销。 |
| 实现复杂度 | 服务器要能“hold”住请求,需要异步I/O 或线程池,代码比短轮询复杂。 |
客户端和服务端都要处理心跳、超时、断线重连,协议层或应用层都要额外逻辑。 |
| 内存开销 | 中(每个 pending 请求需状态) |
高(每个连接长期占内存) |
| 典型场景 | WebIM、在线聊天、实时通知——既要实时又不想立即上 WebSocket 的场景。配置中心 |
WebSocket、数据库连接池、需要双向持续通信的后台服务。 |
b、性能状态
| 场景 | 长轮询 vs 长连接 |
|---|---|
| 理想情况(客户端偶尔拉取) | 长轮询压力 « 长连接(大部分时间无连接) |
| 现实情况(客户端持续监听) | 长轮询压力 ≈ 长连接(始终有 pending) |
极端情况(短 timeout + 高频重试) |
长轮询压力 > 长连接(频繁建连 + HTTP 开销 |
2)Http 状态码
HTTP状态码是服务器对客户端请求的响应状态,用以快速了解请求处理的情况。这些状态码分为五大类,每类以不同的百位数字开头
- 1xx(信息性响应):表示请求已被接收,继续处理。
- 2xx(成功响应):表示请求已成功被服务器接收、理解和处理。
- 3xx(重定向):为了完成请求,需要进一步操作。通常,这些状态码用于将请求重定向到另一个位置。
- 4xx(客户端错误):请求包含语法错误或无法完成请求。除非进行修改,否则客户端不应重复提交这个请求。
- 5xx(服务器错误):服务器在尝试处理请求时发生内部错误。这类错误通常由服务器端的问题引起。
a、1xx 状态码(已收到请求)
-
解释:信息响应,表示服务器已收到请求,但是需要请求者继续操作。
-
说明:主要涉及的状态码有101、102和103,主要用于协议切换、指示服务器正在处理请求以及预加载提示等。
| 状态码 | 作用 |
|---|---|
| 101 | 这只是个临时的响应状态,它表示到目前为止,客户端请求的内容都没有问题。但是客户端需要继续发送请求,才能完成本次请求过程。 |
| 102 | 该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。比如,我们在使用ws协议时常会见到这个状态码 |
| 103 | 预加载提示。此状态代码主要用于与Link 链接头一起使用,以允许用户代理在服务器仍在准备响应时开始预加载资源。 |
b、2xx 状态码(响应成功)
-
解释:响应成功,表示服务器已接收到请求并正确处理
-
说明:包括200(成功)、201(创建资源)、202(接受但未处理)、204(无内容)、206(部分内容)等,代表各种形式的成功响应
| 状态码 | 作用 |
|---|---|
| 200 | 服务器已成功处理了请求。通常,这表示服务器供了请求的网页。 |
| 201 | 请求成功并且服务器创建了新的资源。 |
| 202 | 服务器已接受请求,但尚未处理。 |
| 203 | 服务器已成功处理了请求,但返回的信息可能来自另一来源 |
| 204 | 服务器已成功处理了请求,但没有返回任何实体内容,并且浏览器不需要刷新或者重定向 |
| 205 | 服务器已成功处理了请求,但没有返回任何实体内容,不过浏览器应该立即刷新当前页面 |
| 206 | 服务器成功处理了部分 GET请求 |
c、3xx 状态码(重定向)
-
解释:重定向,表示服务器已接收到请求,但是没有直接处理,而是进行了重定向
-
说明:涉及重定向场景,包括301(永久移动)、302(临时重定向)、304(未修改,使用缓存)等。
| 状态码 | 作用 |
|---|---|
| 300 | 针对请求,服务器可执行多种操作。服务器可根据请求者(user_agent)选择一项操作,或提供操作列表供请求者选择。 |
| 301 | 请求的网页已永久移动到新位置。服务器返回此响应对GET或HEAD请求的响应时,会自动将请求者转到新位置 |
| 302 | 请求的资源被临时重定向到了别的URI。但这个重定向只是临时的,下次请求当前资源时仍然应该使用当前的地址 |
| 303 | 服务端已经收到请求,但是不会进行处理。客户端需要向响应中携带的新的URI发送GET请求,服务端才会进行处理 |
| 304 | 表示当前资源已被下载过,并且没有改变,因此客户端应该从缓存中获取该资源 |
| 305 | 当前资源需要使用指定的代理才能访问 |
| 306 | 最新的规范中,306不再被使用 |
| 307 | 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的 |
d、4xx 状态码(客户端错误)
-
解释:客户端错误,表示客户端发出的请求中存在错误,无法完成请求
-
说明:如400(错误请求)、401(未授权)、403(禁止访问)、404(找不到页面)、413(请求实体太大)等。
| 状态码 | 作用 |
|---|---|
| 400 | 请求有误。它包含两种情况:1.语义有误。服务端无法理解当前请求的含义,因此客户端必须对请求进行修改才可以重新发送。2.参数有误。客户端请求中携带的参数不符合服务端的要求,如数量不一致,类型错误,体积过大(如上传文件时,文件大小超出了服务端配置)等。 |
| 401 | 未授权。即当前请求需要进行用户验证 |
| 402 | 该状态码暂未使用,将来可能用于数字支付系统 |
| 403 | 服务器已经接受并理解了请求,但是拒绝执行 |
| 404 | 请求的资源不存在 |
| 405 | 请求方法不允许。比如服务端设置某个资源只能用POST方法进行访问,而客户端发送的是一个GET请求,服务端就会返回状态码405 |
| 406 | 请求的资源的内容特性无法满足请求头中的条件,因此无法生成响应实体。比如客户端设置返回的数据类型必须是JSON,而服务端没有配置转换JSON所依赖的包,或者所请求的资源本身就无法转化为JSON,这时服务端就无法生成符合客户端要求的响应实体,因此就会返回状态码406。 |
| 407 | 需要在代理服务器上进行身份验证。该状态码与401类似,但是它要求客户端必须在代理服务器上进行身份验证 |
| 408 | 请求超时。 |
| 409 | 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息 |
| 410 | 如果请求的资源已永久删除,服务器就会返回此响应。 |
| 411 | 服务器不接受不含有效内容长度标头字段的请求。 |
| 412 | 服务器未满足请求者在请求中设置的其中一个前提条件。 |
| 413 | 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力 |
| 414 | 请求的URI通常为网址过长,服务器无法处理 |
| 415 | 请求的格式不受请求页面的支持 |
| 416 | 如果页面无法提供请求的范围,则服务器会返回此状态代码 |
| 417 | 服务器未满足”期望”请求标头字段的要求 |
e、5xx 状态码(服务端错误)
-
解释:服务端错误,表示服务器在处理请求的过程中出现错误
-
说明:服务器问题,不能完成请求,如500(内部服务器错误)、502(无效网关)、503(服务不可用)、504(网关超时)等。
| 状态码 | 作用 |
|---|---|
| 500 | 服务器遇到错误,无法完成请求 |
| 501 | 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码 |
| 502 | 服务器作为网关或代理,从上游服务器收到无效响应 |
| 503 | 服务器目前无法使用,由于超载或停机维护。通常,这只是暂时状态 |
| 504 | 服务器作为网关或代理,但是没有及时从上游服务器收到请求 |
| 505 | 服务器不支持请求中所用的HTTP协议版本 |
2、浏览器中输入网址发生了什么
若使用
HTTPS,还会在TCP连接建立后进行TLS(Transport Layer Security,传输层安全协议)握手,完成加密通道的建立。
1)基本流程
URL解析: 浏览器解析URL,提取协议(如http/https)、域名(如www.example.com)、端口、路径等信息。DNS查询:- 首先检查本地
Hosts文件; - 然后查询操作系统或浏览器的
DNS缓存; - 若未命中,则向配置的
DNS服务器发起递归查询,最终获得目标域名对应的IP地址。
- 首先检查本地
- 建立
TCP连接: 浏览器通过获得的IP地址,与Web服务器进行TCP三次握手,建立可靠连接。 - 发送
HTTP请求:在已建立的TCP连接上,浏览器发送HTTP请求报文(如GET /index.html HTTP/1.1)。 - 接收
HTTP响应:服务器处理请求后返回HTTP响应(包含状态码、响应头和HTML内容等)。 - 渲染页面: 浏览器解析
HTML、加载CSS/JS、执行脚本,并最终将页面渲染到屏幕上。
2)VIP 参与
用户浏览器
↓ 输入域名(如 www.example.com)
DNS 服务器
↓ 返回 VIP(如 203.0.113.10)
VIP(虚拟IP)
↓ 由负载均衡器或高可用集群持有
真实服务器(Server A / Server B / ...)
↓ 处理实际请求

| 优势 | 说明 |
|---|---|
| 高可用 | 一台服务器宕机,流量自动切到其他机器 |
| 弹性扩展 | 加机器只需在负载均衡加节点,无需改 DNS |
| 维护透明 | 升级/重启服务器不影响用户访问 |
| 安全隔离 | 真实服务器 IP 不暴露,减少攻击面 |
3、TCP 三次握手
TCP三次握手(Three-way Handshake) 是建立可靠TCP连接的核心机制,确保通信双方都能正常收发数据。
1)关键字段说明
SYN(Synchronize Sequence Numbers):同步序列号,用于发起连接。ACK(Acknowledgement):确认号,表示期望收到的下一个字节序号。ACK(大写):控制标志位,是一个 1-bit 的标志。当ACK=1时,表示本报文段中的 确认号(ack)字段有效。ack(小写):确认号字段,是一个32位的数值,表示“期望收到的下一个字节的序号”,即对对方发送数据的确认
seq:当前报文的序列号。ack:确认号(即对方seq+1)。
2)握手过程

初始状态:客户端 & 服务器:CLOSED
服务器准备就绪:服务器调用 listen() 后进入:LISTEN
第一次握手(客户端 → 服务器)
- 客户端发送:
SYN=1, seq=x - 客户端状态变为:
SYN_SENTSYN:置为 1, 表示需要建立TCP连接
seq:序列号,是由发送端随机生成的
第二次握手(服务器 → 客户端)
- 服务器回复:
SYN=1, ACK=1, seq=y, ack=x+1 - 服务器状态变为:
SYN_RECEIVED(常简写为SYN_RCVD)
第三次握手(客户端 → 服务器)
- 客户端发送:
ACK=1, seq=x+1, ack=y+1 - 客户端状态变为:
ESTABLISHED - 服务器收到后也变为:
ESTABLISHED
3)状态说明
| 状态 | 含义 |
|---|---|
CLOSED |
初始状态:客户端 & 服务器 |
LISTEN |
服务器等待来自任意远程主机的连接请求。 |
SYN_SENT |
客户端已发送 SYN,等待服务器的确认。 |
SYN_RECEIVED |
服务器已收到 SYN 并发送了 SYN+ACK,等待客户端最终确认。 |
ESTABLISHED |
连接已成功建立,可进行数据传输。 |
4)为什么是“三次握手”?二次握手有什么问题
核心目的:防止已失效的连接请求报文段突然传到服务端,导致错误地建立连接,浪费服务器资源。
a、问题出现-两次握手
无法确认客户端是否真的希望建立连接。
- 客户端(
Client)曾向服务器(Server)发送一个连接请求(SYN报文),但该报文并未丢失,而是在网络中长时间滞留; - 此时客户端早已放弃该连接(可能已正常关闭),但滞留的
SYN报文在连接释放后很久才到达服务器; - 服务器收到这个“过期”的
SYN后,误以为是客户端发起的新连接请求,于是回复SYN+ACK并立即进入连接建立状态(ESTABLISHED); - 然而,客户端当前并未发起任何连接请求,因此会忽略服务器发来的
SYN+ACK; - 结果:服务器单方面认为连接已建立,持续等待客户端发送数据,导致内存、端口等资源被无效占用,严重时可能引发资源耗尽。
b、问题解决:三次握手
第三次握手本质上是对“客户端当前意愿”的最终确认,确保连接请求是“新鲜且有效”的。
- 服务器在收到
SYN后,仅进入SYN_RECV状态,不会立即分配完整连接资源; - 只有当客户端主动回传
ACK(第三次握手)后,双方才真正进入ESTABLISHED状态; - 在上述过期
SYN场景中:- 服务器因收不到第三次握手的
ACK,会在超时后丢弃该半连接; - 连接不会建立,资源不会浪费。
- 服务器因收不到第三次握手的
c、四次握手?没必要:
第三次握手已足够完成双向确认,更多握手只会增加延迟,无实际收益。三次握手是可靠性与效率之间的最佳平衡,既避免了资源浪费,又确保了连接的正确建立。
| 握手次数 | 是否能防止“过期 SYN”攻击 |
是否可靠建立双向连接 |
|---|---|---|
| 两次 | 不能 | 服务器单方面建立 |
| 三次 | 能 | 双方共同确认 |
4、SYN 攻击
1)攻击原理
SYNFlood是一种典型的 拒绝服务攻击(DoS),利用TCP三次握手的机制缺陷进行攻击:注意:这种攻击不依赖漏洞,而是滥用协议正常行为,属于资源耗尽型攻击。
- 攻击者伪造大量源
IP地址(通常是不存在或不可达的地址),向目标服务器发送大量SYN请求; - 服务器收到
SYN后,会为每个请求分配资源(如内存、半连接队列条目),并回复SYN+ACK,进入SYN_RECEIVED状态,等待客户端的最终ACK确认; - 由于源
IP是伪造的,攻击者不会(也无法)发送ACK,导致服务器长时间维持这些“半开连接”; - 服务器在超时前会多次重传
SYN+ACK,进一步消耗资源; - 最终,半连接队列被占满,合法用户的连接请求无法进入队列,导致服务不可用。
2)危害
- 耗尽服务器的半连接队列(
backlog queue); - 占用内存和
CPU资源(用于维护连接状态和重传); - 阻塞合法用户的新连接请求,造成服务拒绝(
Denial of Service); - 在大规模攻击下,可能导致整个服务器或网络瘫痪。
3)如何监测
少量
SYN_RECV是正常的(如网络延迟导致ACK未及时到达),但短时间内激增 + 源 IP 随机是典型攻击特征。
-
使用命令查看处于
SYN_RECEIVED状态的连接数量是否异常高:netstat -napt | grep SYN_RECV | wc -l -
观察这些连接的源 IP 是否高度随机、无规律(真实用户通常来自有限 IP 段);
-
监控系统日志或使用
IDS/IPS(如Snort、Suricata)检测异常流量模式。
4)防御措施
| 防御方法 | 原理说明 |
|---|---|
SYN Cookies |
服务器在收到 SYN 时不立即分配资源,而是通过加密算法生成一个“Cookie”作为初始序列号。只有收到合法 ACK 后,才重建连接上下文。无需维护半连接队列,可有效抵御 SYN Flood。 |
缩短 SYN_RECV 超时时间 |
减少半开连接的等待时间(如从默认 60–120 秒降至 10–20 秒),加快资源回收。 |
| 增大半连接队列 | 临时缓解小规模攻击,但无法根本解决问题(攻击者可继续填满)。 |
启用防火墙/IDS 规则 |
限制单 IP 的 SYN 速率(如 iptables + limit 模块),或直接丢弃可疑流量。 |
使用云防护/WAF |
如 Cloudflare、阿里云 DDoS 高防等,可在网络边缘清洗恶意流量。 |
5、Https
| 问题 | HTTP | HTTPS |
|---|---|---|
| 数据是否加密? | 明文 | TLS 加密 |
| 能否防篡改? | 无校验 | 数字签名 + Hash |
| 能否验身份? | 任意伪装 | CA 证书认证 |
| 是否推荐使用? | 仅用于非敏感场景 | 全站强制启用 |
1)HTTP 的三大安全问题
HTTP= 裸奔在网络上的信使,谁都能偷看、修改、冒充。
a、通信使用明文 → 内容可能被窃听
HTTP报文以明文形式传输,任何中间节点(如路由器、运营商、公共 Wi-Fi)都可直接读取内容。- 攻击者可通过网络嗅探还原用户账号、密码、银行卡等敏感信息。
- 运营商甚至可进行
HTTP劫持:插入广告、强制跳转下载页(通过302重定向或篡改 HTML)。
b、无法验证完整性 → 报文可能被篡改
HTTP没有机制校验数据是否被修改。- 即使内容未被解密,攻击者仍可篡改传输中的数据(如将“转账 100 元”改为“转账 10000 元”),而接收方无法察觉。
c、无法验证身份 → 通信方可能被伪装
HTTP不验证服务器身份,任何人都可搭建钓鱼网站。- 用户访问
http://bank.com时,实际可能连接到伪造的服务器,导致信息泄露。
2)HTTPS 是什么?
HTTPS=HTTP+SSL/TLS加密层
- 它不是新协议,而是 在
HTTP与TCP之间加入了一层安全协议(SSL/TLS)。- 实际上,
HTTPS就是“披着SSL/TLS外壳的 HTTP”。
SSL(Secure Socket Layer安全套接层)) 是早期协议,已废弃。TLS(**Transport Layer Security,传输层安全协议 是其继任者,现代HTTPS实际使用的是 TLS 1.2 或 TLS 1.3。
3)HTTPS 如何解决 HTTP 的问题
HTTPS通过 加密 + 身份认证 + 完整性校验 三位一体机制,全面保障通信安全。
a、防窃听:混合加密机制
HTTPS= 非对称加密(安全握手) + 对称加密(高效通信)
| 加密方式 | 优点 | 缺点 | 作用 |
|---|---|---|---|
对称加密(如 AES) |
加解密速度快 | 密钥分发困难(如何安全传密钥?) | 关键点:非对称加密只用于安全地传递这个对称密钥,不用于加密实际的 HTTP 数据! |
非对称加密(如 RSA) |
安全分发密钥 | 计算开销大,不适合大量数据 | 用于“实际数据传输 |
-
非对称加密(如
RSA、ECDHE)——只用于“密钥协商”-
客户端用服务器的公钥加密一个随机生成的对称密钥
-
客户端用服务器的公钥(来自证书)加密这个对称密钥,发送给服务器。
-
服务器用自己的私钥解密,得到这个对称密钥。
-
-
对称加密(如
AES-128-GCM、ChaCha20)–用于“实际数据传输-
一旦双方都拥有了相同的对称密钥,后续所有的
HTTP请求/响应数据(HTML、JSON、图片等)都用这个对称密钥加密。 -
对称加密速度快、开销小,适合大量数据传输。
-
b、防篡改:数字签名 + 哈希校验
- 发送方对消息计算
Hash值(摘要); - 用私钥对该摘要加密,生成 数字签名;
- 接收方用公钥解密签名,得到摘要
A; - 自己对收到的消息再算一次
Hash,得到摘要 B; - 若
A==B,则说明消息未被篡改。
c、防伪装:数字证书(由 CA 颁发)
引入 可信第三方 —— 证书颁发机构(
CA,CertificateAuthority),浏览器地址栏出现 🔒 锁形图标,即表示HTTPS有效且证书可信。
- 服务器向
CA提交公钥、域名等信息; CA验证身份(如域名所有权、企业资质);CA用自己的私钥对服务器信息+公钥进行签名,生成 数字证书;- 客户端内置了受信任
CA的根证书(含公钥); - 浏览器收到服务器证书后:
- 用
CA公钥验证证书签名; - 检查域名、有效期等;
- 验证通过 → 信任该服务器公钥。
- 用
二、Request
HttpServletRequest是JavaServletAPI中用于封装HTTP请求的核心对象。它提供了丰富的接口,用于获取客户端请求的各类信息。
1、路径相关
在
Spring Boot内嵌容器中,默认没有contextPath,因此getRequestURI()≈getServletPath()。
| 方法 | 说明 | 示例(Spring Boot 无上下文路径) |
|---|---|---|
getRequestURL() |
完整 URL(协议 + 主机 + 端口 + URI),不含查询参数 |
http://localhost:8081/healerjean/youhui/web/authorize |
getRequestURI() |
URI 部分(不含协议、主机、端口、参数) |
/healerjean/youhui/web/authorize |
getContextPath() |
Web 应用上下文路径(传统 WAR 部署时为项目名) |
/news(传统项目);Spring Boot 默认为空字符串 "" |
getServletPath() |
Servlet 映射路径(通常与 getRequestURI() 在无上下文路径时相同) |
/healerjean/youhui/web/authorize |
2、服务器与网络信息
getServerPort()受反向代理影响(如Nginx转发到8080,但Host是80,则getServerPort()=80);getLocalPort()是Tomcat实际绑定的端口(如8080)。
| 方法 | 说明 | 示例 |
|---|---|---|
getServerName() |
服务器主机名(来自 Host 头或本地配置) |
localhost、test.healerjean.cn |
getServerPort() |
获取 Http端口 |
8081、80 |
getLocalPort() |
获取 本机 端口 | 8081、8086 |
getHeader("Host") |
原始 Host 请求头(格式:host[:port],80/443 端口不显示) |
localhost:8081、test.healerjean.cn |
3、请求元数据
| 方法 | 说明 |
|---|---|
getMethod() |
HTTP 方法(GET、POST、PUT 等) |
getHeader("Referer") |
上一页面 URL(用于防盗链、来源分析),直接访问时为 null |
getContentType() |
请求体的 MIME 类型(如 application/json),GET 请求通常为 null |
a、常见 Content-Type 类型
getParameter()方法仅支持application/x-www-form-urlencoded和multipart/form-data(需额外解析)。
| 类型 | 使用场景 |
|---|---|
application/x-www-form-urlencoded |
普通表单提交(默认) |
multipart/form-data |
文件上传表单(必须设置) |
application/json |
AJAX 发送 JSON 数据 |
text/plain, text/html, image/* 等 |
静态资源或 API 返回类型(较少用于请求体) |
4、请求头(Headers)
a、获取所有请求头
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
System.out.println(name + ": " + request.getHeader(name));
}
b、GET vs POST 典型请求头差异
| 特征 | GET | POST |
|---|---|---|
是否含 Content-Type |
通常无 | 必有(如 application/json) |
是否含 Content-Length |
无 | 有(除非 chunked) |
| 参数位置 | URL 查询字符串 | 请求体 |
典型 Header |
Accept, Cookie |
Content-Type, Origin, Token |
5、参数获取
1)、获取请求参数
a、GET 参数:getQueryString()
// 仅适用于 GET,返回原始查询字符串(未解码)
request.getQueryString(); // age=1&name=healerjean
b、表单参数(非文件):getParameter()
- 支持:
application/x-www-form-urlencoded - 不支持:
multipart/form-data(需用MultipartResolver或ApacheFileUpload)
request.getParameter("name"); // 单值
request.getParameterValues("hobby"); // 多值(如 checkbox)
request.getParameterMap(); // 所有参数 Map<String, String[]>
2)获取请求体(Raw Body)
当需要处理 JSON、XML 或自定义格式时,需直接读取输入流:
// 方式1:字节流(适合二进制、JSON 等)
InputStream inputStream = request.getInputStream();
// 方式2:字符流(适合文本)
BufferedReader reader = request.getReader();
3)关键限制:流只能读一次!
a、限制逻辑
getParameter()、getInputStream()、getReader() 三者互斥,原因如下:
| 场景 | 行为 |
|---|---|
application/x-www-form-urlencoded |
调用 getParameter() 会提前消费输入流,后续 getInputStream() 返回空 |
multipart/form-data |
getParameter() 无法解析,必须用流或专用解析器(如 Spring 的 MultipartFile) |
| 任意情况 | getInputStream() 和 getReader() 不能同时调用,否则抛 IllegalStateException |
b、解决方案:缓存请求体(可重复读)
-
方案:通过
HttpServletRequestWrapper包装原始请求,预先读取并缓存body: -
适用场景:日志记录、验签、重复读取
body(如API网关、安全审计)。
通过 HttpServletRequestWrapper 包装原始请求,预先读取并缓存 body:
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
return new ByteArrayServletInputStream(cachedBody);
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
配合 Filter 使用:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(request);
chain.doFilter(wrappedRequest, res);
}
三、Cookie 与 Session
1、Session 详解
Session是服务端对象:- 由服务器创建并存储(内存/
Redis/DB等) - 每个
Session有唯一 ID(JSESSIONID)。
- 由服务器创建并存储(内存/
SessionID通过Cookie传递:- 默认情况下,服务器通过
Set-Cookie: JSESSIONID=xxx告知浏览器; - 浏览器后续请求自动携带该
Cookie。
- 默认情况下,服务器通过
Cookie的作用域(Domain+Path)决定了哪些请求会携带它。
1)Session 生命周期
| 行为 | 是否销毁 Session |
|---|---|
调用 session.invalidate() |
立即销毁 |
超过 maxInactiveInterval(默认 30 分钟) |
自动销毁 |
| 关闭浏览器 | 不销毁 |
| 服务器重启(内存存储) | 丢失(除非持久化到 Redis/File) |
2) Session 数据操作
HttpSession session = request.getSession(); // true: 无则创建
// 存
session.setAttribute("USER", userDTO);
// 取
UserDTO user = (UserDTO) session.getAttribute("USER");
// 遍历
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
Object value = session.getAttribute(key);
log.info("{}: {}", key, value);
}
2、Cookie 详解
1)获取 Cookie
request.getCookies()只返回当前请求携带的Cookie,受 Domain/Path/Secure 等属性限制。
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + "=" + cookie.getValue());
}
}
2)Cookie 与 Session 的关系
| 项目 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务端 |
| 安全性 | 低(可被窃取) | 高(数据在服务端) |
| 用途 | 传递 Session ID、记住偏好等 |
存储用户状态、权限等敏感信息 |
| 关联方式 | JSESSIONID Cookie 指向 Session |
Session ID 通常通过 Cookie 传递 |
a、创建 Session 时自动设置 Cookie
不需要写
response.addCookie(...),容器(如 Tomcat)会自动处理。
HttpSession session = request.getSession(); // 或 getSession(true)
如果这是用户第一次访问(还没有 Session),服务器会:
- 创建一个新的
HttpSession对象 - 生成一个唯一的
Session ID(如JSESSIONID=abc123xyz) - 自动通过
Set-Cookie响应头将JSESSIONID发送给浏览器
b、后续请求自动携带 Cookie
浏览器收到 Set-Cookie: JSESSIONID=... 后,会在同域下的后续请求中自动带上:
Cookie: JSESSIONID=abc123xyz
c、服务器自动识别 Session
Servlet 容器(如 Tomcat)会自动从请求的 Cookie 中提取 JSESSIONID,并根据它查找对应的 HttpSession 对象。你在代码中直接调用:
HttpSession session = request.getSession();
UserDTO user = (UserDTO) session.getAttribute("USER");
2、问题
1)不同端口,Session 是否共享
| 条件 | 浏览器是否共享 JSESSIONID Cookie? |
|---|---|
| 同域名 + 同端口 | 共享(同一应用) |
| 同域名 + 不同端口 | 不共享(浏览器将端口视为不同源 |
2)HTTP Cookie 的作用域规则
Domain=localhost(或未指定)时,Cookie默认绑定到完整的host:port组合;- 即使域名相同,
http://localhost:8080和http://localhost:8081被视为两个独立站点; - 因此,它们各自设置的
JSESSIONID互不影响,不会“覆盖”。
3)如何实现跨端口/跨应用 Session 共享
若需多个应用(如
SSO客户端)共享登录状态,不能依赖默认 Session 机制,而应采用:
方案 1:统一 Session 存储 + 相同 Cookie 配置
-
将
Session存入Redis(而非Tomcat内存); -
所有应用配置相同的:这样,
app1.healerjean.cn:8080和app2.healerjean.cn:8081可共享JSESSIONID。// Spring Boot 示例 server.servlet.session.cookie.domain = .healerjean.cn // 注意前缀点 server.servlet.session.cookie.path = /
方案 2:SSO(单点登录)架构(推荐)
- 用户只在 认证中心(CAS/OAuth2 Server) 登录;
- 各业务系统(Client)通过 Ticket / Token 验证身份;
- 各 Client 有自己的 Session,但通过
SSO协议同步登录状态; - 不要求
JSESSIONID相同,因此不存在“覆盖”问题。
4)关键结论
| 问题 | 正确认知 |
|---|---|
同域名不同端口是否共享 Session? |
默认不共享(浏览器隔离 Cookie) |
| 如何实现多应用登录状态同步? | 用 SSO 或统一 Session 存储 + Cookie Domain 配置 |
关闭浏览器会销毁 Session 吗? |
不会(除非 Session Cookie 未持久化) |
四、跨域解决
1、什么是跨域?
1)同源策略(Same-Origin Policy)
浏览器出于安全考虑,限制 不同源 的网页脚本访问资源。同源 = 协议 + 域名 + 端口 完全相同
URL A |
URL B |
是否同源 | 原因 |
|---|---|---|---|
http://localhost:8080 |
http://localhost:9000 |
❌ | 端口不同 |
https://api.example.com |
http://api.example.com |
❌ | 协议不同 |
http://example.com |
http://www.example.com |
❌ | 域名不同 |
2)跨域请求的表现
-
浏览器控制台报错:
Access to XMLHttpRequest at 'http://localhost:8080/api' from origin 'http://localhost:3000' has been blocked by CORS policy. -
请求可能发出(简单请求),但响应被浏览器拦截;
-
非简单请求(如带自定义
Header、JSONbody)会先发 预检请求(Preflight,OPTIONS)。
2、解决跨域
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 拦截器 | 全局统一 CORS | 一次配置,处处生效;自动处理 OPTIONS | 所有请求都经过拦截器(轻微性能开销) |
| 工具类 | 单接口精细控制 | 灵活,可选是否启用 CORS | 需在每个接口手动调用 |
1)工具类
package com.healerjean.proj.utils.http;
/**
* CorsUtils
*
* @author zhangyujin
* @date 2025/11/12
*/
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
public class CorsUtils {
/**
* 校验 Origin 是否在允许的域名白名单中
* - 以 "." 开头:表示匹配该域名及所有子域名(如 ".bail.com" 匹配 bail.com, app.bail.com, x.y.bail.com)
* - 不以 "." 开头:仅精确匹配该主域名(如 "bail.com" 只匹配 bail.com,不匹配 app.bail.com)
*/
public static boolean isValidOrigin(String origin, List<String> allowedDomains) {
if (origin == null || origin.trim().isEmpty()) {
return false;
}
String host;
try {
host = new URI(origin).getHost();
} catch (URISyntaxException e) {
return false;
}
if (host == null || host.isEmpty()) {
return false;
}
host = host.toLowerCase();
for (String pattern : allowedDomains) {
if (pattern == null || pattern.trim().isEmpty()) {
continue;
}
pattern = pattern.trim().toLowerCase();
if (pattern.startsWith(".")) {
// 模式是 .example.com → 匹配 example.com 及 *.example.com
String domain = pattern.substring(1);
if (domain.isEmpty()) {
continue;
}
// 匹配规则:
// 1. host == domain (主域)
// 2. host 以 ".domain" 结尾(子域)
if (host.equals(domain) || host.endsWith("." + domain)) {
return true;
}
} else {
// 模式是 example.com → 仅精确匹配
if (host.equals(pattern)) {
return true;
}
}
}
return false;
}
}
| 白名单配置 | Origin | 是否通过 | 说明 |
|---|---|---|---|
.bail.com |
https://bail.com |
✅ | 主域匹配 |
.bail.com |
https://app.bail.com |
✅ | 子域匹配 |
.bail.com |
https://x.y.bail.com |
✅ | 多级子域 |
.bail.com |
https://evilbail.com |
❌ | 不匹配(不是子域) |
.bail.com |
https://bail.com.cn |
❌ | 不匹配 |
bail.com(无点) |
https://bail.com |
✅ | 精确匹配 |
bail.com(无点) |
https://app.bail.com |
❌ | 不匹配子域 |
- 统一使用带点写法(推荐):如果你想支持子域名,全部写成
".xxx.com",语义清晰,避免混淆。 - 不要混用敏感域名: 避免像
".com"这种过于宽泛的配置,会带来安全风险。 - 配合
HTTPS使用: 在生产环境,建议只允许HTTPS的Origin(可在isValidOrigin中加协议判断):
2)Spring 拦截器解决
- 全局生效,无需每个接口处理
- 自动处理
OPTIONS预检 - 仅对白名单域名放行,安全可靠
// src/main/java/com/yourpackage/interceptor/CorsInterceptor.java
package com.yourpackage.interceptor;
import com.yourpackage.util.CorsUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CorsInterceptor implements HandlerInterceptor {
// 允许的主域名(支持任意子域名)
private static final List<String> ALLOWED_DOMAINS = Arrays.asList(
"a.com",
"b.com"
// 可按需添加,如 "example.org"
);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException {
String origin = request.getHeader("Origin");
String method = request.getMethod();
// 处理预检请求(OPTIONS)
if ("OPTIONS".equalsIgnoreCase(method)) {
if (origin != null && CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
setCorsHeaders(response, origin);
}
response.setStatus(HttpServletResponse.SC_OK);
return false; // 终止后续处理
}
// 处理实际请求
if (origin != null && CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
setCorsHeaders(response, origin);
}
return true; // 继续执行 Controller
}
private void setCorsHeaders(HttpServletResponse response, String origin) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
response.setHeader("Access-Control-Max-Age", "3600");
}
}
import com.yourpackage.interceptor.CorsInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private CorsInterceptor corsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(corsInterceptor)
.addPathPatterns("/**"); // 拦截所有路径
}
}
3)单个方法中手动处理 CORS
// src/main/java/com/yourpackage/util/CorsHelper.java
package com.yourpackage.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CorsHelper {
// 允许的主域名(支持任意子域名)
private static final List<String> ALLOWED_DOMAINS = Arrays.asList(
"a.com",
"b.com"
// 可按需添加,如 "example.org"
);
/**
* 处理 CORS 请求
*
* @return true 表示是 OPTIONS 请求且已处理完毕,调用方应直接返回;
* false 表示是非 OPTIONS 请求,可继续业务逻辑。
*/
public static boolean handleCors(HttpServletRequest request, HttpServletResponse response) throws IOException {
String origin = request.getHeader("Origin");
if (origin == null || !CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
// 非法 Origin 不设置 CORS 头(同源请求不受影响)
return !"OPTIONS".equalsIgnoreCase(request.getMethod());
}
// 设置 CORS 头
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
response.setHeader("Access-Control-Max-Age", "3600");
// 如果是 OPTIONS,直接返回
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
return false; // 继续业务
}
}
3、问题
1)OPTIONS 请求
a、什么时候浏览器会发 OPTIONS 请求
当请求满足 “非简单请求” 条件时,浏览器会自动先发一个 OPTIONS 预检请求(Preflight Request),例如:
现代前端(Axios、Fetch)几乎都会触发 OPTIONS,因为:
- 默认发
JSON:Content-Type: application/json - 带认证头:
Authorization: Bearer xxx
| 情况 | 是否触发 OPTIONS |
|---|---|
GET / POST + Content-Type: application/x-www-form-urlencoded |
❌ 不触发 |
POST + Content-Type: application/json |
✅ 触发 |
任意请求 + 自定义 Header(如 Authorization, X-Token) |
✅ 触发 |
使用 PUT / DELETE 等方法 |
✅ 触发 |
b、如果不处理 OPTIONS,会发生什么?
如果你的后端 没有返回 200 + 正确 CORS 头 → 浏览器直接 拦截后续 POST 请求
- 接口调用失败
- 错误信息误导性强(看起来像“没配 CORS”,其实是“没处理 OPTIONS”)
- 开发环境可能“偶然能用”(比如只测了 GET),但上线后 POST 全挂
c、为什么“历史代码没加也能跑”?
不处理
OPTIONS是“侥幸成功”,不是“正确实现”
| 原因 | 说明 |
|---|---|
| 只测试了简单请求 | 比如纯 GET、无自定义 Header,不会触发 OPTIONS |
| 前端用了代理 | 开发时通过 webpack devServer 代理,绕过了 CORS |
| 后端框架自动处理了 | Spring Boot 默认对未映射的 OPTIONS 返回 405,但某些旧版本或配置可能“巧合通过”(不可靠) |
| 浏览器缓存了预检结果 | 第一次失败后,短时间内不再发 OPTIONS,造成“能用”的假象 |
2)request.getHeader("Origin")
Origin 值 |
类型 | 来源 | 是否应处理 CORS |
|---|---|---|---|
null |
null |
同源请求、Postman、curl | 不需要 |
"null"(字符串) |
"null" |
file:// 本地页面 |
禁止响应(安全风险) |
"https://app.bail.com" |
合法 URL |
正常跨域页面 | 白名单校验后响应 |
a、什么时候 Origin 为 null?
-
同源请求(
Same-Origin)-
当前端页面和后端 API 同协议 + 同域名 + 同端口 时,浏览器 不会发送
Origin请求头。 -
此时
request.getHeader("Origin")返回null(Java 对象为null)。
-
- 非浏览器客户端发起的请求
Postman、curl、Java HttpClient、Python requests等工具默认 不带Origin头。- 结果:
getHeader("Origin") == null
- 某些特殊浏览器行为或隐私模式:极少数情况下(如隐私浏览、扩展干扰),浏览器可能省略
Origin。
b、什么时候 Origin 是字符串 "null"?
- 通过
file://协议打开的本地HTML页面- 浏览器出于安全考虑,将
Origin设为字符串"null"(注意:不是 Java 的null,而是字面量"null") - 所以
request.getHeader("Origin")返回的是"null"(String 类型)
- 浏览器出于安全考虑,将
c、是否要对 "null" 做响应?
-
强烈建议:不要为
"null"设置Access-Control-Allow-Origin! -
原因:
- 来自
file://的请求不可信,无法验证来源- 如果你返回
Access-Control-Allow-Origin: null,任何本地HTML文件都能调用你的API,造成严重安全风险!
- 如果你返回
- 来自
3)为什么要加 CORS 逻辑
让浏览器知道:“这个跨域请求是被允许的,请把响应内容暴露给前端
JavaScript”
| 是否需要 CORS | |
|---|---|
前端(React/Vue)调用自己后端 API(不同域) |
必须加,否则前端拿不到数据 |
| 提供开放 API 给第三方网页使用 | 必须加(或用 JSONP / 代理) |
App / 小程序 / Postman 调用 API |
不需要(它们不受浏览器 CORS 限制) |
| 同域部署(前端和后端同一个域名) | 不需要 |
a、进得去” ≠ “拿得到
| 阶段 | 谁在操作 | 是否受 CORS 影响 |
|---|---|---|
| 1. 请求发送 | 浏览器 → 你的服务器 | 不受影响(请求照发) |
| 2. 后端处理 | 你的 Spring Controller |
不受影响(照常执行) |
| 3. 响应返回 | 你的服务器 → 浏览器 | 网络层照常传输 |
| 4. 浏览器交付 | 浏览器 → 前端 JavaScript |
受 CORS 控制! |
b、结论
| 说法 | 是否正确 | 解释 |
|---|---|---|
跨域请求能进入 Controller |
正确 | 服务器无条件处理所有 HTTP 请求 |
不配 CORS 前端拿不到数据” |
正确 | 浏览器拦截了响应的“JS 可见性 |
不配 CORS 请求会被服务器拒绝 |
错误 | 服务器根本不知道“跨域”这回事 |
五、工具类
1、Ip 工具类
package com.hlj.utils.ip;
import com.hlj.utils.ExceptionLogUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @Desc:
* @Author HealerJean
* @Date 2018/7/4 下午2:36.
*/
public class IpUtil {
public static String getIp(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if(request==null)return null;
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if(ip.toLowerCase().contains("x-forwarded-for")){
String ip_temp = "";
if(ip.contains(":") && ip.split(":").length>0 && !ip.endsWith(":")){
ip_temp = ip.split(":")[1];
if(StringUtils.isBlank(ip_temp)) ip_temp = ip.substring(0,ip.lastIndexOf("X-Forwarded-For"));
}else{
ip_temp = ip.substring(0,ip.lastIndexOf("X-Forwarded-For"));
}
ip = ip_temp.replaceAll(" ","");
}
if (ip.indexOf(",") > -1) {
String ip_temp = ip.split(",")[0];
ip_temp = ip_temp.replaceAll(" ", "");
if(ip_temp.startsWith("10.") && ip.split(",").length>1){
ip = ip.split(",")[1];
ip = ip.replaceAll(" ", "");
}else ip = ip_temp;
}
return ip;
}
/**
* 获取调用的http/https+ 域名 + 端口
* @param urlTarget
* @return
*/
public static String getDomainAndPort(String urlTarget)
{
//跳转到对应的回调地址
String domain = "";
try {
URL url = new URL(urlTarget);
String host = url.getHost();
int port = url.getPort();
String s = url.toString();
domain = s.substring(0,s.indexOf(host)+host.length());
if(port != -1) {
domain = domain + ":" + port;
}
} catch (MalformedURLException e) {
log.info("获取域名失败");
}
return domain ;
}
/**
* 获取服务器ip
* @return
*/
public static String getHostIp(){
try {
InetAddress ia2 = InetAddress.getLocalHost();
return ia2.getHostAddress();
} catch (UnknownHostException e) {
ExceptionLogUtils.log(e,IpUtil.class);
return null ;
}
}
/**
* 根据域名获取ip
* www.baidu.com
* @param url
* @return
*/
public static String getIpByUrl(String url) {
try {
InetAddress ia2 = InetAddress.getByName(url);
return ia2.getHostAddress();
} catch (UnknownHostException e) {
ExceptionLogUtils.log(e,IpUtil.class );
return null;
}
}
public static IPEntry getAddress(String ip){
return IPSeeker.getInstance().getAddress(ip);
}
public static void main(String[] args) {
System.out.println(getAddress("106.39.75.134"));
}
}
2、Request 工具类
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 静态获取HttpServletRequest 和 session的方法
* 要使用此类需要在web.xml注册org.springframework.web.context.request.RequestContextListener
*/
public class RequestHolder {
public static HttpSession getSession() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession();
}
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
}
public static HttpServletResponse getResponse() {
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
}
}
3、Cookie 工具类
package com.duodian.youhui.admin.utils.cookie;
import com.duodian.youhui.admin.utils.RequestHolder;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Desc: Cookie 工具类
* @Date: 2018/9/12 下午1:02.
*/
public class CookieHelper {
public static void setCookie(String key,String value,Integer seconds){
HttpServletResponse response = RequestHolder.getResponse();
Cookie add = new Cookie(key,value);
add.setMaxAge(seconds);
add.setPath("/");
response.addCookie(add);
}
public static String getCookieValue(String key){
HttpServletRequest request = RequestHolder.getRequest();
Cookie[] cookies = request.getCookies();
if (cookies == null){
return null;
}
for (Cookie cookie : cookies){
if (StringUtils.equals(cookie.getName(), key)){
return cookie.getValue();
}
}
return null;
}
public static void clearCookie(String key){
HttpServletResponse response = RequestHolder.getResponse();
Cookie clear = new Cookie(key,"");
clear.setMaxAge(0);
clear.setComment("清除cookie");
clear.setPath("/");
response.addCookie(clear);
}
}
4、Session 工具
package com.duodian.admore.home.utils;
import com.duodian.admore.constants.AppConstants;
import com.duodian.admore.core.spring.RequestHolder;
import com.duodian.admore.entity.db.user.User;
import javax.servlet.http.HttpSession;
public class SessionUtils {
public static final String SESSION_USER = "user";
public static void initSession(User user){
HttpSession session = RequestHolder.getSession();
session.setAttribute(SESSION_USER,user);
}
public static void clearSession(){
HttpSession session = RequestHolder.getSession();
session.invalidate();
}
public static User getSessionUser(){
HttpSession session = RequestHolder.getSession();
User user = (User)session.getAttribute(SESSION_USER);
return user;
}
}


