Adicionar um cabeçalho a uma solicitação de cliente SSE de Jersey
1. Visão geral
Neste tutorial, veremos uma maneira fácil de enviar cabeçalhos em solicitações de clienteServer-Sent Event (SSE) usando a API Jersey Client.
Também abordaremos a maneira adequada de enviar cabeçalhos de chave / valor básicos, cabeçalhos de autenticação e cabeçalhos restritos usando o conector de transporte Jersey padrão.
2. Direto ao ponto
Provavelmente todos nós já nos deparamos com essa situação ao tentar enviar cabeçalhos usando SSEs:
UsamosSseEventSource para receber SSEs, mas para construirSseEventSource, precisamos de uma instânciaWebTarget que não nos fornece uma maneira de adicionar cabeçalhos. A instânciaClient também não ajuda. Soa familiar?
Remember though, headers aren’t related to SSE, but to the client request itself, portanto, realmente deveríamos estar olhando para lá.
Vamos ver o que podemos fazer, então, comClientRequestFilter.
3. Dependências
Para iniciar nossa jornada, precisamos dethe jersey-client dependency, bem comoJersey’s SSE dependency em nosso arquivo Mavenpom.xml:
org.glassfish.jersey.core
jersey-client
2.29
org.glassfish.jersey.media
jersey-media-sse
2.29
Observe queJersey supports JAX-RS 2.1 as of 2.29,, então parece que poderemos usar os recursos dele.
4. ClientRequestFilter
Primeiro, implementaremos o filtro que adicionará o cabeçalho a cada solicitação do cliente:
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);
}
}
Depois disso, vamos registrá-lo e consumi-lo.
Para nossos exemplos, usaremoshttps://sse.example.org como um ponto de extremidade imaginário a partir do qual queremos que nosso cliente consuma eventos. Na realidade, mudaríamos isso para oSSE Event server endpoint real que queremos que nosso cliente consuma.
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();
Agora, e se precisarmos enviar cabeçalhos mais complexos, como cabeçalhos de autenticação para nosso endpoint SSE?
Vamos juntos para as próximas seções para aprender mais sobre cabeçalhos na API Jersey Client.
5. Cabeçalhos na API do cliente de Jersey
Is important to know that the default Jersey transport connector implementation makes use of the HttpURLConnection class from the JDK. Esta classe restringe o uso de alguns cabeçalhos. Para evitar essa restrição, podemos definir a propriedade do sistema:
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
Podemos encontrar uma lista de cabeçalhos restritos emJersey docs.
5.1. Cabeçalhos gerais simples
A maneira mais direta de definir um cabeçalho é chamarWebTarget#request para obter umInvocation.Builder que fornece o métodoheader.
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();
}
E, na verdade, podemos compactar isso muito bem para facilitar a leitura:
public Response simpleHeaderFluently(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
return client.target("https://sse.example.org/")
.request()
.header(headerKey, headerValue)
.get();
}
A partir daqui, usaremos apenas a forma fluente para as amostras, pois é mais fácil de entender.
5.2. Autenticação básica
Na verdade, a Jersey Client APIprovides the HttpAuthenticationFeature class that allows us to send authentication headers facilmente:
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();
}
Como registramosfeature durante a construção do cliente, ele será aplicado a todas as solicitações. A API lida com a codificação do nome de usuário e senha exigidos pela especificação básica.
Note, by the way, that we are implying HTTPS as the mode for our connection. Embora seja uma medida de segurança valiosa, é fundamental ao usar a autenticação Básica, pois, caso contrário, a senha é exposta como texto simples. Jersey também oferece suporte amore sophisticated security configurations.
Agora, também podemos especificar os creds no momento da solicitação:
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. Autenticação Digest
HttpAuthenticationFeature de Jersey também suporta autenticação 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();
}
E, da mesma forma, podemos substituir no momento da solicitação:
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. Autenticação de token de portador com OAuth 2.0
OAuth 2.0 supports the notion of Bearer tokens como outro mecanismo de autenticação.
Precisaremos deJersey’s oauth2-client dependency para nos darOAuth2ClientSupportFeature que é semelhante aHttpAuthenticationFeature:
org.glassfish.jersey.security
oauth2-client
2.29
Para adicionar um token de portador, seguiremos um padrão semelhante ao anterior:
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();
}
Ou podemos substituir no nível de solicitação, o que é especialmente útil quando o token é alterado devido à rotação:
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. Autenticação de token de portador com OAuth 1.0
Em quarto lugar, se precisarmos integrar com um código legado que usa OAuth 1.0, precisaremos deJersey’s oauth1-client dependency:
org.glassfish.jersey.security
oauth1-client
2.29
E da mesma forma que o OAuth 2.0, temosOAuth1ClientSupport que podemos usar:
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();
}
Com o nível de solicitação novamente sendo habilitado pela propriedadeOAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN.
6. Conclusão
Para resumir, neste artigo, abordamos como adicionar cabeçalhos às solicitações de clientes SSE em Jersey usando filtros. Também abordamos especificamente como trabalhar com cabeçalhos de autenticação.
O código para este exemplo está disponívelover on GitHub.