1. 概述
本文将介绍如何使用 Jersey Client API 在 Server-Sent Events(SSE)客户端请求中添加 HTTP 头信息(Header)。
我们将重点讲解如何通过 Jersey 默认的传输连接器,正确地发送普通键值对 Header、认证类 Header,以及受限制的 Header。内容实用,适合在实际项目中直接套用,避免踩坑。
✅ 核心思路:SSE 本身不处理 Header,Header 是 HTTP 请求的一部分,应从客户端请求层面入手解决。
2. 直奔主题
你是否也遇到过这种情况?
想给 SSE 请求加个 Header,于是创建了 SseEventSource
,但构建它依赖的 WebTarget
并没有提供直接添加 Header 的方法。Client
实例也帮不上忙。
看起来无解?其实关键点在于:
⚠️ Header 属于 HTTP 请求,而不是 SSE 协议本身。所以我们不该在
SseEventSource
上折腾,而应该从 客户端请求的拦截机制 入手。
解决方案:使用 ClientRequestFilter
。
3. 依赖配置
使用 Jersey 的 SSE 功能,需要引入以下两个核心依赖(Maven):
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>3.1.1</version>
</dependency>
如果需要支持 OAuth 认证,还需额外引入对应模块(后文详述)。
4. 使用 ClientRequestFilter 添加 Header
最通用且灵活的方式是实现 ClientRequestFilter
接口,在请求发出前动态添加 Header。
自定义过滤器示例
public class AddHeaderOnRequestFilter implements ClientRequestFilter {
public static final String FILTER_HEADER_VALUE = "filter-header-value";
public static final String FILTER_HEADER_KEY = "x-filter-header";
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(FILTER_HEADER_KEY, FILTER_HEADER_VALUE);
}
}
注册并使用过滤器
Client client = ClientBuilder.newBuilder()
.register(AddHeaderOnRequestFilter.class)
.build();
WebTarget webTarget = client.target("https://sse.example.org/");
SseEventSource sseEventSource = SseEventSource.target(webTarget).build();
sseEventSource.register((event) -> {
// 处理接收到的事件
System.out.println("Received event: " + event.readData());
});
sseEventSource.open();
// 保持连接,直到需要关闭
// sseEventSource.close();
✅ 优点:
- 一次注册,所有请求自动携带 Header
- 支持动态逻辑(如 token 刷新)
- 适用于所有基于该 Client 的请求
5. Jersey Client 中的 Header 使用详解
Jersey 默认使用 JDK 的 HttpURLConnection
作为底层传输实现。这个实现会限制某些敏感 Header 的设置(如 Connection
, Host
等)。
绕过 Header 限制
如确实需要设置受限 Header(谨慎使用),可通过系统属性开启:
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
📌 注意:该设置影响 JVM 全局行为,建议仅在明确需求时开启。
更多受限 Header 列表可参考 Jersey 官方文档。
5.1 普通 Header 添加方式
最简单的方式是通过 WebTarget.request()
获取 Invocation.Builder
,链式调用 .header()
方法。
基础写法
public Response simpleHeader(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("https://sse.example.org/");
Invocation.Builder invocationBuilder = webTarget.request();
invocationBuilder.header(headerKey, headerValue);
return invocationBuilder.get();
}
推荐:流式写法(简洁清晰)
public Response simpleHeaderFluently(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
return client.target("https://sse.example.org/")
.request()
.header(headerKey, headerValue)
.get();
}
📌 后续示例均采用流式风格,更易读。
5.2 Basic 认证
Jersey 提供了 HttpAuthenticationFeature
工具类,简化认证流程。
客户端级别配置(全局生效)
public Response basicAuthenticationAtClientLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.get();
}
✅ 自动完成 Base64 编码,符合 RFC 规范。
⚠️ 安全提醒:Basic 认证明文传输密码,必须配合 HTTPS 使用!
请求级别覆盖(灵活适配不同凭证)
public Response basicAuthenticationAtRequestLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.property(HTTP_AUTHENTICATION_BASIC_USERNAME, username)
.property(HTTP_AUTHENTICATION_BASIC_PASSWORD, password)
.get();
}
适用于多用户场景或临时凭证切换。
5.3 Digest 认证
与 Basic 类似,Jersey 也支持更安全的 Digest 认证。
客户端级别
public Response digestAuthenticationAtClientLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest(username, password);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.get();
}
请求级别覆盖
public Response digestAuthenticationAtRequestLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
.property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
.get();
}
5.4 OAuth 2.0 Bearer Token 认证
适用于现代 API 接口常见的 Token 认证方式。
引入依赖
<dependency>
<groupId>org.glassfish.jersey.security</groupId>
<artifactId>oauth2-client</artifactId>
<version>3.1.1</version>
</dependency>
客户端级别设置 Token
public Response bearerAuthenticationWithOAuth2AtClientLevel(String token) {
Feature feature = OAuth2ClientSupport.feature(token);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.get();
}
请求级别动态更新 Token(推荐用于刷新场景)
public Response bearerAuthenticationWithOAuth2AtRequestLevel(String token, String otherToken) {
Feature feature = OAuth2ClientSupport.feature(token);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.property(OAuth2ClientSupport.OAUTH2_PROPERTY_ACCESS_TOKEN, otherToken)
.get();
}
✅ 特别适合 Token 过期刷新后动态替换的场景。
5.5 OAuth 1.0 支持(遗留系统集成)
若需对接老系统,Jersey 也支持 OAuth 1.0。
引入依赖
<dependency>
<groupId>org.glassfish.jersey.security</groupId>
<artifactId>oauth1-client</artifactId>
<version>3.1.1</version>
</dependency>
配置示例
public Response bearerAuthenticationWithOAuth1AtClientLevel(String token, String consumerKey) {
ConsumerCredentials consumerCredential =
new ConsumerCredentials(consumerKey, "my-consumer-secret");
AccessToken accessToken = new AccessToken(token, "my-access-token-secret");
Feature feature = OAuth1ClientSupport
.builder(consumerCredential)
.feature()
.accessToken(accessToken)
.build();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target("https://sse.example.org/")
.request()
.get();
}
同样支持通过 OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN
属性在请求时动态替换 Token。
6. 总结
本文系统性地介绍了在 Jersey SSE 客户端中添加 Header 的多种方式:
方式 | 适用场景 | 是否推荐 |
---|---|---|
ClientRequestFilter |
通用 Header、动态逻辑 | ✅ 强烈推荐 |
Invocation.Builder#header() |
单次请求临时 Header | ✅ |
HttpAuthenticationFeature |
Basic/Digest 认证 | ✅ |
OAuth2ClientSupport |
OAuth 2.0 Token | ✅ |
系统属性 allowRestrictedHeaders |
特殊需求(慎用) | ⚠️ 仅限必要时 |
📌 最佳实践建议:
- 优先使用
ClientRequestFilter
统一管理通用 Header - 认证类 Header 使用官方 Feature 支持,避免手动拼接出错
- 生产环境务必使用 HTTPS,尤其是 Basic 认证
- Token 类认证建议支持运行时替换,便于刷新机制集成
完整示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/web-modules/jersey