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


原始标题:Add a Header to a Jersey SSE Client Request