HttpRequest(2)

📌 一句话总结:URI 是什么?

URI (统一资源标识符)是一个结构化字符串 ,用于唯一标识一个资源(比如一个网页、一个 WebSocket 端点、一个文件)。在 IM 中,它就是你客户端连接的**"目的地"**,例如 ws://example.com/chathttps://api.example.com/login


🔗 URI 和 Spring 中其他类的关系(回顾历史)

你之前已经接触过这些 Spring 类:

  • HttpMethod:表示请求的"动作"(GET、POST)。
  • HttpHeaders:表示请求的"元数据"(认证头、内容类型)。
  • HttpRequest :表示一个完整的 HTTP 请求 ,它包含了方法、URI、头等。

这里的 URI 就是 HttpRequest 的地址部分 。在你的 HandshakeInterceptor 中,你通过 request.getURI() 拿到这个对象,然后可以做很多事情:

  • 读取路径(如 /ws
  • 读取查询参数(如 ?token=xxx
  • 甚至判断是否指向了正确的端点。

所以,URIHttpRequest组成部分 ,而 HttpRequest 又在你的拦截器里被用来做校验和路由。


🧩 URI 的结构(与你的 IM 实际场景对应)

一个典型的 WebSocket 连接地址可能是:

复制代码
ws://yourdomain.com/chat?token=eyJhbGci...

对应的 URI 对象会解析为:

组件 说明
scheme ws 协议(WebSocket)
host yourdomain.com 主机名
port 未指定(默认80) 端口
path /chat 路径(端点)
query token=eyJhbGci... 查询字符串(常用来传 JWT)
fragment null 片段(#后部分,不常使用)

在你的拦截器中,你可以这样提取 token:

java 复制代码
URI uri = request.getURI();
String query = uri.getQuery(); // "token=eyJhbGci..."
Map<String, String> params = Arrays.stream(query.split("&"))
    .map(p -> p.split("="))
    .collect(Collectors.toMap(p -> p[0], p -> p[1]));
String token = params.get("token");

当然,更推荐从 Authorization 头取,但 URI 的 query 提供了一种备选方案。


🛠️ 核心方法一览(IM 开发常用)

方法 作用 示例
getScheme() 获取协议名 "ws"
getHost() 获取主机名 "yourdomain.com"
getPort() 获取端口(-1表示未指定) -1
getPath() 获取路径 "/chat"
getQuery() 获取查询字符串 "token=xxx"
getFragment() 获取片段 null
toString() 返回完整 URI 字符串 "ws://yourdomain.com/chat?token=xxx"
resolve(String) 基于当前 URI 解析相对 URI 可用于拼接端点
normalize() 规范化路径(去除 . 和 ...) 用于路径处理

特别注意getQuery() 返回的是原始 查询字符串,你需要自己解析(或用 URLEncodedUtils 等工具)。getPath() 返回的路径也是原始形式,但不会解码 %20 等转义字符(如果需要解码,可以用 getPath() 再调用 URLDecoder.decode)。


📦 构造 URI:几种方式

不必 手动创建 URI 对象(Spring 会帮你解析请求中的 URI),但在客户端发起连接时,你需要构建一个 URI 字符串。你可以用:

  1. 直接字符串new URI("ws://example.com/chat?token=xxx")
  2. 多参数构造new URI("ws", "example.com", "/chat", "token=xxx", null)

这些构造方法会自动检查非法字符并转义。


🧠 在 IM 中的典型应用场景

场景 1:WebSocket 握手拦截器(你已经见过)
  • 通过 request.getURI() 获取路径,判断是否匹配 /ws/chat
  • 通过 uri.getQuery()uri.getPath() 提取参数,做额外校验(例如白名单检查)。
场景 2:构造重定向地址
  • 用户登录成功后,服务端可能返回一个 Location 头,指向 ws://... 的 WebSocket 端点。HttpHeaders.setLocation(URI) 就接受 URI 对象。
场景 3:日志与监控
  • 打印请求的完整 URI,方便追踪问题。
场景 4:REST API 路由
  • 控制器中通过 @RequestMapping 匹配路径,底层也是基于 URI 的解析。

🆚 与 URL 类的区别(简单提一下)

URI 只负责解析和标识 ,不涉及网络操作;而 URL 可以打开连接(openConnection())。在 IM 后端,我们几乎只用 URI,因为不需要用 Java 标准库直接发起 WebSocket 连接(我们用的是 Spring 的 WebSocket 客户端或 STOMP)。


💡 总结:URI 在你的 IM 项目中扮演什么角色?

  • 它是 地址解析器:将客户端的连接字符串拆解成 scheme、host、path、query 等部分。
  • 它是 参数提取器:在握手阶段,你可以从 query 中获取 token,作为备用认证方式。
  • 它是 路由匹配器 :通过 path 判断该请求应该由哪个 WebSocket 处理器处理(比如区分 /chat/admin)。
  • 它是 日志记录器:完整 URI 能帮助你快速定位请求来源。

因此,URIHttpRequest 中不可或缺的"地址"组件,和 HttpMethod(动作)、HttpHeaders(元数据)一起,共同构成了一个完整的 HTTP 请求。理解这个类,你就能更清楚地知道 Spring 在拦截器中为你提供了哪些信息,从而编写更健壮的认证和路由逻辑。

HttpRequest

它虽然只有短短的几行,但在整个 Spring Web 体系中处于核心枢纽 的位置。它就像是把 HttpMethod(动词)、URI(地址)和 HttpHeaders(面单信息)组装成一张完整快递单的那个"包装盒"。

下面我带你逐行拆解,并在最后串联它在你的 IM 握手流程中的实际作用。


1. 包声明与导入(它的"家庭住址")

java 复制代码
package org.springframework.http;
  • 它属于 org.springframework.http 包。这个包是 Spring 中所有 HTTP 协议相关抽象 的核心所在地(包括 HttpMethodHttpHeadersMediaType 等)。
java 复制代码
import java.net.URI;
import java.util.Map;
  • java.net.URI :导入 Java 标准库中的 URI 类(就是你上一个问题详细分析的那个类)。这表示 HttpRequest 会用它来存储请求的"目标地址"。
  • java.util.Map:导入 Map 接口,用于存储请求级别的附加属性。

2. 类注释(设计意图说明书)

java 复制代码
/**
 * Represents an HTTP request message, consisting of a
 * {@linkplain #getMethod() method} and a {@linkplain #getURI() URI}.
 *
 * @author Arjen Poutsma
 * @since 3.1
 */
  • 核心含义 :该类代表一个 HTTP 请求消息。它由"方法"(Method)和"URI"组成。
  • 深层解读 :这是对 HTTP 请求最精简的抽象。一个标准的 HTTP 请求,必须包含"我要做什么"(GET/POST)和"我要访问谁"(/api/login)。这个接口就是锁定了这两个最核心的要素。

3. 接口声明与继承关系

java 复制代码
public interface HttpRequest extends HttpMessage {
  • public interface :这是一个接口,定义了一套规范,供 ServerHttpRequest(你拦截器里拿到的对象)等具体实现类去遵循。

  • extends HttpMessage(最关键的一点)
    注意,这里继承了 HttpMessage 。虽然在这段代码里看不到,但在 Spring 源码中,HttpMessage 接口定义了一个方法:

    java 复制代码
    HttpHeaders getHeaders();

    这意味着 :任何实现了 HttpRequest 的对象,同时自动拥有 getHeaders() 方法。因此,一个完整的 HttpRequest 实际上包含了:方法(Method) + 地址(URI) + 请求头(Headers)


4. 核心方法一:getMethod()

java 复制代码
/**
 * Return the HTTP method of the request.
 * @return the HTTP method as an HttpMethod value
 * @see HttpMethod#valueOf(String)
 */
HttpMethod getMethod();
  • 字面含义:返回这个请求的 HTTP 方法。
  • 结合历史 :这个方法返回的是 HttpMethod 对象(你之前详细分析过的那个枚举/类)。
  • 在你的 IM 中的作用 :在你的 HandshakeInterceptor 中,你正是通过 request.getMethod() 拿到它,然后判断 if (method != HttpMethod.GET),从而严格拒绝非 GET 方法的 WebSocket 升级请求,堵死恶意攻击路径。

5. 核心方法二:getURI()

java 复制代码
/**
 * Return the URI of the request (including a query string if any,
 * but only if it is well-formed for a URI representation).
 * @return the URI of the request (never {@code null})
 */
URI getURI();
  • 字面含义:返回请求的 URI(包含查询字符串,且永远不会为 null)。
  • 结合历史 :这个方法返回的是 java.net.URI 对象。
  • 在你的 IM 中的作用
    1. 获取路径 :你可以通过 uri.getPath() 判断请求是发给 /ws(WebSocket)还是 /api(普通接口)。
    2. 获取参数 :如果你选择不在 Header 里传 Token,而选择放在 URL 里(如 ws://host/chat?token=xxx),你可以用 uri.getQuery() 提取出来。这为你的认证方式提供了备选方案(虽然 Header 方式更安全,但了解这个可以帮你读懂别人的代码)。

6. 核心方法三:getAttributes()(Spring 6.2 新增)

java 复制代码
/**
 * Return a mutable map of request attributes for this request.
 * @since 6.2
 */
Map<String, Object> getAttributes();
  • 字面含义 :返回一个可变的请求属性 Map。

  • 区别于 Session :这里的 Attributes 是请求级别的(Request Scope)。它只在当前这一次 HTTP 请求的生命周期内有效,请求结束就销毁。

  • 在你的 IM 中的作用 :这是一个"临时便签本"。在你处理 WebSocket 握手时(beforeHandshake 还没结束),你可以把解析出来的 userId 临时塞进这个 Map:

    java 复制代码
    request.getAttributes().put("tempUserId", 123);

    然后,在同一个请求链的后续处理(比如调用 afterHandshake)中,你可以再取出来做最后的日志记录。不过,要把数据传给 WebSocket 长连接,你还是得用 HandshakeInterceptor 参数里的那个 attributes 参数 ,因为那个参数里的数据会被复制进 WebSocketSession


🚀 大串联:在你的 IM 软件中,它是如何协同工作的?

你现在已经学完了 IM 通信链路 中的所有"HTTP 零件",让我们把它们串成一条完整的链:

  1. 客户端发起请求 :字符串 "GET /ws?token=123 HTTP/1.1" 加上头 Authorization: Bearer xxx
  2. Spring 底层解析
    • "GET" 变成 HttpMethod.GET 对象。
    • "/ws?token=123" 变成 java.net.URI 对象。
    • 把头信息变成 HttpHeaders 对象。
    • 把这三者打包成一个 ServerHttpRequest 对象(即 HttpRequest 接口的实现)。
  3. 你的拦截器介入beforeHandshake):
    • request.getMethod() → 取 HttpMethod,校验是否为 GET。
    • request.getHeaders() → 取 HttpHeaders,拿出 JWT Token 校验(继承自 HttpMessage)。
    • request.getURI() → 取 URI,校验路径是否为 /ws
  4. 校验通过 :你通过拦截器参数里的 attributes 塞入用户名。
  5. 长连接建立 :Spring 把拦截器 attributes 里的数据搬运到 WebSocketSession 中。

总结一句话HttpRequest 接口就像一个**"数据集装箱"**,把 HttpMethod(动作标签)、URI(地址标签)、HttpHeaders(详情面单)牢牢绑在一起,交给了你的拦截器。你的拦截器打开这个集装箱,逐一检查里面的零件,全部合格后才批准"升级"为 WebSocket 电话线。理解了这个接口,你就彻底打通了 "HTTP 请求解析""WebSocket 握手决策" 之间的所有环节。🎉

相关推荐
wddptwd282 小时前
android studio 报错怎么处理 java.lang.NullPointerException
android·java·android studio
霸道流氓气质3 小时前
分布式系统中接口时序不确定性处理
java·开发语言·分布式
深入云栈3 小时前
Netty 4.2.x 源码深度解析 (四):HashedWheelTimer —— 时间轮定时调度算法
java
带刺的坐椅3 小时前
别再把 Coding Agent 当智能补全了:SolonCode 想做的是数字员工
java·solon·codex·claudecode·opencode·soloncode
大模型码小白3 小时前
在 Windows 下 Codex 安装、配置与使用详细指南
java·人工智能·windows
Gauss松鼠会3 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
mingo_敏3 小时前
DeepAgents : 后端(Backends)
java·开发语言
霸道流氓气质3 小时前
SpringBoot中事务内同步处理 + 事务后异步调用外部系统的通用模式示例
java·spring boot·后端
霸道流氓气质4 小时前
Spring 事务传播机制与 REQUIRES_NEW
java·数据库·spring
BerryS3N4 小时前
Java 后端转型大模型:Demo 能跑不等于能上线
java·人工智能·python·java后端·spring ai·langchain4j·大模型转型