Перейти к основному содержимому

Добавить заголовок в запрос клиента SSE на Джерси

· 5 мин. чтения

1. Обзор

В этом руководстве мы увидим простой способ отправки заголовков в клиентских запросах Server-Sent Event (SSE) с использованием API клиента Джерси.

Мы также рассмотрим правильный способ отправки основных заголовков типа «ключ-значение», заголовков проверки подлинности и ограниченных заголовков с использованием стандартного транспортного соединителя Джерси.

2. Прямо к делу

Вероятно, мы все сталкивались с этой ситуацией при попытке отправить заголовки с помощью SSE:

Мы используем SseEventSource для получения SSE, но для создания SseEventSource нам нужен экземпляр WebTarget , который не дает нам возможности добавлять заголовки. Экземпляр Client тоже не поможет. Звучит знакомо?

Помните, однако, что заголовки связаны не с SSE, а с самим запросом клиента, поэтому нам действительно следует искать там.

Давайте посмотрим, что мы можем сделать с ClientRequestFilter .

3. Зависимости

Чтобы начать наше путешествие, нам нужна зависимость jersey-client , а также зависимость SSE от Jersey в нашем файле Maven pom.xml :

<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>

Обратите внимание, что Jersey поддерживает JAX-RS 2.1 начиная с версии 2.29, поэтому, похоже, мы сможем использовать его функции.

4. Фильтр запроса клиента

Во-первых, мы реализуем фильтр, который будет добавлять заголовок к каждому запросу клиента:

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

После этого мы зарегистрируем его и используем.

Для наших примеров мы будем использовать 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();

А что, если нам нужно отправить более сложные заголовки, такие как заголовки аутентификации, на нашу конечную точку SSE?

Давайте вместе перейдем к следующим разделам, чтобы узнать больше о заголовках в клиентском API Джерси.

5. Заголовки в клиентском API Джерси

Важно знать, что реализация транспортного соединителя Джерси по умолчанию использует класс HttpURLConnection из JDK . Этот класс ограничивает использование некоторых заголовков. Чтобы избежать этого ограничения, мы можем установить системное свойство:

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

Мы можем найти список запрещенных заголовков в документации Джерси .

5.1. Простые общие заголовки

Самый простой способ определить заголовок — это вызвать WebTarget#request для получения 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();
}

И, на самом деле, мы можем довольно хорошо сжать это для большей читабельности:

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. Базовая аутентификация

На самом деле 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();
}

Поскольку мы зарегистрировали функцию при создании клиента, она будет применяться к каждому запросу. API обрабатывает кодировку имени пользователя и пароля, требуемую базовой спецификацией.

Обратите внимание, кстати, что мы подразумеваем 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. Дайджест-аутентификация

HttpAuthenticationFeature в Джерси также поддерживает дайджест-аутентификацию:

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("http://sse.example.org/")
.request()
.property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
.property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
.get();
}

5.4. Аутентификация токена носителя с помощью OAuth 2.0

OAuth 2.0 поддерживает понятие токенов-носителей в качестве еще одного механизма аутентификации.

Нам понадобится зависимость oauth2-client Джерси, `` чтобы предоставить нам OAuth2ClientSupportFeature , который похож на HttpAuthenticationFeature :

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

Чтобы добавить токен на предъявителя, мы будем следовать тому же шаблону, что и раньше:

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

Или мы можем переопределить на уровне запроса, что особенно удобно, когда токен меняется из-за ротации:

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. Аутентификация токена носителя с помощью OAuth 1.0

В- четвертых, если нам нужно интегрироваться с устаревшим кодом, использующим OAuth 1.0, нам понадобится зависимость Jersey oauth1-client :

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

Аналогично 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();
}

Уровень запроса снова включается свойством OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN .

6. Заключение

Подводя итог, в этой статье мы рассмотрели, как добавлять заголовки к клиентским запросам SSE в Джерси с помощью фильтров. Мы также специально рассмотрели, как работать с заголовками аутентификации.

Код для этого примера доступен на GitHub .