1. Обзор
В этом руководстве мы увидим простой способ отправки заголовков в клиентских запросах событий, отправленных сервером (SSE), с помощью API клиента Jersey.
Мы также рассмотрим правильный способ отправки основных заголовков ключей/значений, заголовков аутентификации и ограниченных заголовков с использованием транспортного соединителя Джерси по умолчанию.
2. Прямо к делу
Вероятно, мы все сталкивались с этой ситуацией, пытаясь отправить заголовки с помощью SSES:
Мы используем Sse EventSource для получения проблем, но для создания Sse EventSource нам нужен экземпляр WebTarget , который не предоставляет нам способ добавления заголовков. Экземпляр Client тоже не поможет. Звучит знакомо?
Помните, однако, что заголовки связаны не с SSE, а с самим запросом клиента, поэтому мы действительно должны искать там.
Давайте посмотрим, что мы можем сделать с помощью ClientRequestFilter .
3. Зависимости
Чтобы начать наше путешествие, нам нужно |/джерси-клиент зависимость , а также Зависимость от размера Джерси в вашем Maven pom.xml файл:
org.glassfish.jersey.core jersey-client 2.29
org.glassfish.jersey.media jersey-media-sse 2.29
Обратите внимание, что Jersey поддерживает JAX-RS 2.1 начиная с 2.29, так что, похоже, мы сможем использовать функции из этого.
4. ClientRequestFilter
Во-первых, мы реализуем фильтр, который добавит заголовок к каждому запросу клиента:
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 как воображаемая конечная точка, из которой мы хотим, чтобы наш клиент потреблял события. На самом деле мы изменили бы это на реальную конечную точку сервера событий , которую мы хотим, чтобы наш клиент использовал.
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 клиента Jersey.
5. Заголовки в API клиента Джерси
Важно знать, что реализация transportconnector Джерси по умолчанию использует класс HttpURLConnection из JDK . Этот класс ограничивает использование некоторых заголовков. Чтобы избежать этого ограничения, мы можем установить системное свойство:
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
Мы можем найти список ограниченных заголовков в Jersey docs .
5.1. Простые общие заголовки
Самый простой способ определить заголовок-это вызов WebTarget#request для получения вызова .Конструктор , который предоставляет метод заголовка .
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 поддерживает понятие токенов на предъявителя в качестве еще одного механизма аутентификации.
Нам понадобится Jersey’s oauth2client dependency , чтобы предоставить нам Функцию поддержки клиентов OAuth2 , которая похожа на HttpAuthenticationFeature :
org.glassfish.jersey.security oauth2-client 2.29
Чтобы добавить токен на предъявителя, мы будем следовать той же схеме, что и раньше:
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, нам понадобится Джерси oauth1-клиент зависимость :
org.glassfish.jersey.security oauth1-client 2.29
И аналогично 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 .