Add a Header to a Jersey SSE Client Request – 在Jersey SSE客户端请求中添加一个标题

最后修改: 2019年 9月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll see an easy way to send headers in Server-Sent Event (SSE) client requests using the Jersey Client API.

在本教程中,我们将看到使用Jersey客户端API在Server-Sent Event(SSE)客户端请求中发送头信息的简单方法。

We’ll also cover the proper way to send basic key/value headers, authentication headers, and restricted headers using the default Jersey transport connector.

我们还将介绍使用默认的Jersey传输连接器发送基本键/值头、认证头和限制性头的正确方法。

2. Straight to the Point

2.直奔主题

Probably we’ve all run into this situation while trying to send headers using SSEs:

可能我们都在尝试使用SSE发送头信息时遇到过这种情况。

We use a SseEventSource to receive SSEs, but to build the SseEventSource, we need a WebTarget instance that doesn’t provide us with a way to add headers. The Client instance is no help either. Sound familiar?

我们使用SseEventSource来接收SSE,但是为了构建SseEventSource,我们需要一个WebTarget实例,该实例没有为我们提供添加头文件的方法。Client实例也没有帮助。听起来很熟悉吧?

Remember though, headers aren’t related to SSE, but to the client request itself, so we really ought to be looking there.

但请记住,头文件与SSE无关,而是与客户端请求本身有关,因此我们真的应该在那里寻找。

Let’s see what we can do, then, with ClientRequestFilter.

让我们看看用ClientRequestFilter能做什么。

3. Dependencies

3.依赖性

To start our journey, we need the jersey-client dependency as well as Jersey’s SSE dependency in our Maven pom.xml file:

要开始我们的旅程,我们需要在Maven的pom.xml文件中加入jersey-client依赖项以及Jersey的SSE依赖项

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.29</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-sse</artifactId>
    <version>2.29</version>
</dependency>

Note that Jersey supports JAX-RS 2.1 as of 2.29, so it looks like we’ll be able to use features from that.

请注意,Jersey从2.29开始支持JAX-RS 2.1,所以看起来我们将能够使用该功能。

4. ClientRequestFilter

4.ClientRequestFilter

First, we’ll implement the filter that will add the header to each client request:

首先,我们要实现过滤器,将头信息添加到每个客户端请求中。

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);
    }
}

After that, we’ll register it and consume it.

之后,我们将注册它并消费它。

For our examples, we’ll use https://sse.example.org as an imaginary endpoint from which we want our client to consume events. In reality, we’d change this to the real SSE Event server endpoint that we want our client to consume.

在我们的示例中,我们将使用https://sse.example.org作为一个假想的端点,我们希望我们的客户端能够从该端点消费事件。在现实中,我们会将其改为真正的SSE事件服务器端点,我们希望我们的客户端能够消费该事件。

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) -> { /* Consume event here */ });
sseEventSource.open();
// do something here until ready to close
sseEventSource.close();

Now, what if we need to send more complex headers like authentication headers to our SSE endpoint?

现在,如果我们需要向我们的SSE端点发送更复杂的头信息,如认证头信息,怎么办?

Let’s go together to the next sections to learn more about headers in the Jersey Client API.

让我们一起进入下一节,了解更多关于Jersey客户端API中的头信息。

5. Headers in Jersey Client API

5.泽西岛客户端API中的头信息

Is important to know that the default Jersey transport connector implementation makes use of the HttpURLConnection class from the JDK. This class restricts the use of some headers. To avoid that restriction, we can set the system property:

需要知道的是,默认的Jersey传输连接器实现使用了JDK的HttpURLConnection。这个类限制了一些头文件的使用。为了避免这种限制,我们可以设置系统属性。

System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

We can find a list of restricted headers in the Jersey docs.

我们可以在Jersey docs中找到限制性头文件的列表。

5.1. Simple General Headers

5.1.简单的通用头文件

The most straightforward way to define a header is calling WebTarget#request to obtain an Invocation.Builder that provides the header method.

定义头的最直接方法是调用 WebTarget#request 以获得提供 header 方法的 Invocation.Builder

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();
}

And, actually, we can compact this quite nicely for added readability:

而且,实际上,我们可以很好地压缩这个,以增加可读性。

public Response simpleHeaderFluently(String headerKey, String headerValue) {
    Client client = ClientBuilder.newClient();

    return client.target("https://sse.example.org/")
      .request()
      .header(headerKey, headerValue)
      .get();
}

From here we’ll use only the fluent form for the samples as it’s easier to understand.

从这里开始,我们将只使用样本的流畅形式,因为它更容易理解。

5.2. Basic Authentication

5.2.基本认证

Actually, the Jersey Client API provides the HttpAuthenticationFeature class that allows us to send authentication headers easily:

实际上,Jersey Client API 提供了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();
}

Since we registered feature while building the client, it’ll be applied to every request. The API handles the encoding of the username and password that the Basic specification requires.

由于我们在构建客户端时注册了feature,它将被应用于每个请求。API处理Basic规范要求的用户名和密码的编码。

Note, by the way, that we are implying HTTPS as the mode for our connection. While this is always a valuable security measure, it’s fundamental when using Basic authentication as, otherwise, the password is exposed as plaintext. Jersey also supports more sophisticated security configurations.

顺便说一下,我们暗示HTTPS是我们的连接模式。虽然这始终是一项有价值的安全措施,但在使用基本认证时,它是最基本的,否则,密码会以明文形式暴露出来。Jersey还支持更复杂的安全配置

Now, we can also spec the creds at request-time as well:

现在,我们也可以在请求时规定信条。

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 Authentication

5.3.文摘认证

Jersey’s HttpAuthenticationFeature also supports Digest authentication:

Jersey的HttpAuthenticationFeature也支持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();
}

And, likewise, we can override at request-time:

而且,同样,我们可以在请求时间覆盖。

public Response digestAuthenticationAtRequestLevel(String username, String password) {
    HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest();
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("http://sse.example.org/")
      .request()
      .property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
      .property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
      .get();
}

5.4. Bearer Token Authentication with OAuth 2.0

5.4.用OAuth 2.0进行承载令牌认证

OAuth 2.0 supports the notion of Bearer tokens as another authentication mechanism.

OAuth 2.0支持承载令牌的概念作为另一种认证机制。

We’ll need Jersey’s oauth2-client dependency to give us OAuth2ClientSupportFeature which is similar to HttpAuthenticationFeature:

我们需要Jersey的oauth2-client依赖性来为我们提供OAuth2ClientSupportFeature,它与HttpAuthenticationFeature类似。

<dependency>
    <groupId>org.glassfish.jersey.security</groupId>
    <artifactId>oauth2-client</artifactId>
    <version>2.29</version>
</dependency>

To add a bearer token, we’ll follow a similar pattern as before:

要添加一个不记名令牌,我们将遵循与之前类似的模式。

public Response bearerAuthenticationWithOAuth2AtClientLevel(String token) {
    Feature feature = OAuth2ClientSupport.feature(token);
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.examples.org/")
      .request()
      .get();
}

Or, we can override at the request level, which is specifically handy when the token changes due to rotation:

或者,我们可以在请求层面进行覆盖,这在令牌因轮换而改变时特别方便。

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();
}

5.5. Bearer Token Authentication with OAuth 1.0

5.5.用OAuth 1.0进行承载令牌认证

Fourthly, if we need to integrate with a legacy code that uses OAuth 1.0, we’ll need Jersey’s oauth1-client dependency:

第四,如果我们需要与使用OAuth 1.0的遗留代码集成,我们将需要Jersey的oauth1-client依赖项

<dependency>
    <groupId>org.glassfish.jersey.security</groupId>
    <artifactId>oauth1-client</artifactId>
    <version>2.29</version>
</dependency>

And similarly to OAuth 2.0, we’ve got OAuth1ClientSupport that we can use:

与OAuth 2.0类似,我们也有OAuth1ClientSupport,我们可以使用。

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();
}

With the request-level again being enabled by the OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN property.

随着请求级再次被OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN属性所启用。

6. Conclusion

6.结语

To summarize, in this article, we covered how to add headers to SSE client requests in Jersey using filters. We also specifically covered how to work with authentication headers.

总结一下,在这篇文章中,我们介绍了如何使用过滤器向Jersey的SSE客户端请求添加头信息。我们还特别介绍了如何处理认证头信息。

The code for this example is available over on GitHub.

这个例子的代码可以在GitHub上找到