Add a Header to a Jersey SSE Client Request (#7662)

* Add a Header to a Jersey SSE Client Request

(cherry picked from commit ee70714e7885cf8713e9c2698a8a8d93fb6a53c8)

* Class and Methods rename
This commit is contained in:
Fabio Silva 2019-08-27 12:33:06 -03:00 committed by maibin
parent bfba59a42f
commit 0216c364b0
5 changed files with 490 additions and 1 deletions

View File

@ -32,7 +32,7 @@
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>${jersey.version}</version>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
@ -44,6 +44,21 @@
<artifactId>jersey-bean-validation</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.security</groupId>
<artifactId>oauth1-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.security</groupId>
<artifactId>oauth2-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>

View File

@ -0,0 +1,189 @@
package com.baeldung.jersey.client;
import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.client.oauth1.AccessToken;
import org.glassfish.jersey.client.oauth1.ConsumerCredentials;
import org.glassfish.jersey.client.oauth1.OAuth1ClientSupport;
import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.Response;
import javax.ws.rs.sse.InboundSseEvent;
import javax.ws.rs.sse.SseEventSource;
import static org.glassfish.jersey.client.authentication.HttpAuthenticationFeature.*;
public class JerseyClientHeaders {
private static final String BEARER_CONSUMER_SECRET = "my-consumer-secret";
private static final String BEARER_ACCESS_TOKEN_SECRET = "my-access-token-secret";
private static final String TARGET = "http://localhost:9998/";
private static final String MAIN_RESOURCE = "echo-headers";
private static final String RESOURCE_AUTH_DIGEST = "digest";
private static String sseHeaderValue;
public static Response simpleHeader(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target(TARGET);
WebTarget resourceWebTarget = webTarget.path(MAIN_RESOURCE);
Invocation.Builder invocationBuilder = resourceWebTarget.request();
invocationBuilder.header(headerKey, headerValue);
return invocationBuilder.get();
}
public static Response simpleHeaderFluently(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.header(headerKey, headerValue)
.get();
}
public static Response basicAuthenticationAtClientLevel(String username, String password) {
//To simplify we removed de SSL/TLS protection, but it's required to have an encryption
// when using basic authentication schema as it's send only on Base64 encoding
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.get();
}
public static Response basicAuthenticationAtRequestLevel(String username, String password) {
//To simplify we removed de SSL/TLS protection, but it's required to have an encryption
// when using basic authentication schema as it's send only on Base64 encoding
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.property(HTTP_AUTHENTICATION_BASIC_USERNAME, username)
.property(HTTP_AUTHENTICATION_BASIC_PASSWORD, password)
.get();
}
public static Response digestAuthenticationAtClientLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest(username, password);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.path(RESOURCE_AUTH_DIGEST)
.request()
.get();
}
public static Response digestAuthenticationAtRequestLevel(String username, String password) {
HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.path(RESOURCE_AUTH_DIGEST)
.request()
.property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
.property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
.get();
}
public static Response bearerAuthenticationWithOAuth1AtClientLevel(String token, String consumerKey) {
ConsumerCredentials consumerCredential = new ConsumerCredentials(consumerKey, BEARER_CONSUMER_SECRET);
AccessToken accessToken = new AccessToken(token, BEARER_ACCESS_TOKEN_SECRET);
Feature feature = OAuth1ClientSupport
.builder(consumerCredential)
.feature()
.accessToken(accessToken)
.build();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.get();
}
public static Response bearerAuthenticationWithOAuth1AtRequestLevel(String token, String consumerKey) {
ConsumerCredentials consumerCredential = new ConsumerCredentials(consumerKey, BEARER_CONSUMER_SECRET);
AccessToken accessToken = new AccessToken(token, BEARER_ACCESS_TOKEN_SECRET);
Feature feature = OAuth1ClientSupport
.builder(consumerCredential)
.feature()
.build();
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.property(OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN, accessToken)
.get();
}
public static Response bearerAuthenticationWithOAuth2AtClientLevel(String token) {
Feature feature = OAuth2ClientSupport.feature(token);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.get();
}
public static Response bearerAuthenticationWithOAuth2AtRequestLevel(String token, String otherToken) {
Feature feature = OAuth2ClientSupport.feature(token);
Client client = ClientBuilder.newBuilder().register(feature).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.property(OAuth2ClientSupport.OAUTH2_PROPERTY_ACCESS_TOKEN, otherToken)
.get();
}
public static Response filter() {
Client client = ClientBuilder.newBuilder().register(AddHeaderOnRequestFilter.class).build();
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.get();
}
public static Response sendRestrictedHeaderThroughDefaultTransportConnector(String headerKey, String headerValue) {
Client client = ClientBuilder.newClient();
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
return client.target(TARGET)
.path(MAIN_RESOURCE)
.request()
.header(headerKey, headerValue)
.get();
}
public static String simpleSSEHeader() throws InterruptedException {
Client client = ClientBuilder.newBuilder()
.register(AddHeaderOnRequestFilter.class)
.build();
WebTarget webTarget = client.target(TARGET)
.path(MAIN_RESOURCE)
.path("events");
SseEventSource sseEventSource = SseEventSource.target(webTarget).build();
sseEventSource.register(JerseyClientHeaders::receiveEvent);
sseEventSource.open();
Thread.sleep(3_000);
sseEventSource.close();
return sseHeaderValue;
}
private static void receiveEvent(InboundSseEvent event) {
sseHeaderValue = event.readData();
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.jersey.client.filter;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import java.io.IOException;
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);
}
}

View File

@ -0,0 +1,72 @@
package com.baeldung.jersey.server;
import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.*;
import javax.ws.rs.sse.OutboundSseEvent;
import javax.ws.rs.sse.Sse;
import javax.ws.rs.sse.SseEventSink;
@Path("/echo-headers")
public class EchoHeaders {
static final String REALM_KEY = "realm";
static final String REALM_VALUE = "Baeldung";
static final String QOP_KEY = "qop";
static final String QOP_VALUE = "auth";
static final String NONCE_KEY = "nonce";
static final String NONCE_VALUE = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
static final String OPAQUE_KEY = "opaque";
static final String OPAQUE_VALUE = "5ccc069c403ebaf9f0171e9517f40e41";
static final String SSE_HEADER_KEY = "x-sse-header-key";
@Context
HttpHeaders headers;
@GET
public Response getHeadersBack() {
return echoHeaders();
}
@RolesAllowed("ADMIN")
@GET
@Path("/digest")
public Response getHeadersBackFromDigestAuthentication() {
// As the Digest authentication require some complex steps to work we'll simulate the process
// https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation
if (headers.getHeaderString("authorization") == null) {
String authenticationRequired = "Digest " + REALM_KEY + "=\"" + REALM_VALUE + "\", " + QOP_KEY + "=\"" + QOP_VALUE + "\", " + NONCE_KEY + "=\"" + NONCE_VALUE + "\", " + OPAQUE_KEY + "=\"" + OPAQUE_VALUE + "\"";
return Response.status(Response.Status.UNAUTHORIZED)
.header("WWW-Authenticate", authenticationRequired)
.build();
} else {
return echoHeaders();
}
}
@GET
@Path("/events")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void getServerSentEvents(@Context SseEventSink eventSink, @Context Sse sse) {
OutboundSseEvent event = sse.newEventBuilder()
.name("echo-headers")
.data(String.class, headers.getHeaderString(AddHeaderOnRequestFilter.FILTER_HEADER_KEY))
.build();
eventSink.send(event);
}
private Response echoHeaders() {
Response.ResponseBuilder responseBuilder = Response.noContent();
headers.getRequestHeaders()
.forEach((k, v) -> {
v.forEach(value -> responseBuilder.header(k, value));
});
return responseBuilder.build();
}
}

View File

@ -0,0 +1,197 @@
package com.baeldung.jersey.server;
import com.baeldung.jersey.client.JerseyClientHeaders;
import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
import org.glassfish.jersey.media.sse.SseFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class EchoHeadersUnitTest extends JerseyTest {
private static final String SIMPLE_HEADER_KEY = "my-header-key";
private static final String SIMPLE_HEADER_VALUE = "my-header-value";
private static final String USERNAME = "baeldung";
private static final String PASSWORD = "super-secret";
private static final String AUTHORIZATION_HEADER_KEY = "authorization";
private static final String BEARER_TOKEN_VALUE = "my-token";
private static final String BEARER_CONSUMER_KEY_VALUE = "my-consumer-key";
private static final String BEARER_REQUEST_TOKEN_VALUE = "my-request-token";
@Test
public void whenCallingSimpleHeader_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.simpleHeader(SIMPLE_HEADER_KEY, SIMPLE_HEADER_VALUE);
assertEquals(response.getHeaderString(SIMPLE_HEADER_KEY), SIMPLE_HEADER_VALUE);
}
@Test
public void whenCallingSimpleHeaderFluently_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.simpleHeaderFluently(SIMPLE_HEADER_KEY, SIMPLE_HEADER_VALUE);
assertEquals(response.getHeaderString(SIMPLE_HEADER_KEY), SIMPLE_HEADER_VALUE);
}
@Test
public void whenCallingBasicAuthenticationAtClientLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.basicAuthenticationAtClientLevel(USERNAME, PASSWORD);
assertBasicAuthenticationHeaders(response);
}
@Test
public void whenCallingBasicAuthenticationAtRequestLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.basicAuthenticationAtRequestLevel(USERNAME, PASSWORD);
assertBasicAuthenticationHeaders(response);
}
@Test
public void whenCallingDigestAuthenticationAtClientLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.digestAuthenticationAtClientLevel(USERNAME, PASSWORD);
Map<String, String> subHeadersMap = parseAuthenticationSubHeader(response, 7);
assertDigestAuthenticationHeaders(subHeadersMap);
}
@Test
public void whenCallingDigestAuthenticationAtRequestLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.digestAuthenticationAtRequestLevel(USERNAME, PASSWORD);
Map<String, String> subHeadersMap = parseAuthenticationSubHeader(response, 7);
assertDigestAuthenticationHeaders(subHeadersMap);
}
@Test
public void whenCallingBearerAuthenticationWithOAuth1AtClientLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth1AtClientLevel(BEARER_TOKEN_VALUE, BEARER_CONSUMER_KEY_VALUE);
Map<String, String> subHeadersMap = parseAuthenticationSubHeader(response, 6);
assertBearerAuthenticationHeaders(subHeadersMap);
}
@Test
public void whenCallingBearerAuthenticationWithOAuth1AtRequestLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth1AtRequestLevel(BEARER_TOKEN_VALUE, BEARER_CONSUMER_KEY_VALUE);
Map<String, String> subHeadersMap = parseAuthenticationSubHeader(response, 6);
assertBearerAuthenticationHeaders(subHeadersMap);
}
@Test
public void whenCallingBearerAuthenticationWithOAuth2AtClientLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth2AtClientLevel(BEARER_TOKEN_VALUE);
assertEquals("Bearer " + BEARER_TOKEN_VALUE, response.getHeaderString(AUTHORIZATION_HEADER_KEY));
}
@Test
public void whenCallingBearerAuthenticationWithOAuth2AtRequestLevel_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth2AtRequestLevel(BEARER_TOKEN_VALUE, BEARER_REQUEST_TOKEN_VALUE);
assertEquals("Bearer " + BEARER_REQUEST_TOKEN_VALUE, response.getHeaderString(AUTHORIZATION_HEADER_KEY));
}
@Test
public void whenCallingFilter_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.filter();
assertEquals(AddHeaderOnRequestFilter.FILTER_HEADER_VALUE, response.getHeaderString(AddHeaderOnRequestFilter.FILTER_HEADER_KEY));
}
@Test
public void whenCallingSendRestrictedHeaderThroughDefaultTransportConnector_thenHeadersReturnedBack() {
Response response = JerseyClientHeaders.sendRestrictedHeaderThroughDefaultTransportConnector("keep-alive", "keep-alive-value");
assertEquals("keep-alive-value", response.getHeaderString("keep-alive"));
}
@Test
public void whenCallingSimpleSSEHeader_thenHeadersReturnedBack() throws InterruptedException {
String sseHeaderBackValue = JerseyClientHeaders.simpleSSEHeader();
assertEquals(AddHeaderOnRequestFilter.FILTER_HEADER_VALUE, sseHeaderBackValue);
}
private void assertBearerAuthenticationHeaders(Map<String, String> subHeadersMap) {
assertEquals(BEARER_TOKEN_VALUE, subHeadersMap.get("oauth_token"));
assertEquals(BEARER_CONSUMER_KEY_VALUE, subHeadersMap.get("oauth_consumer_key"));
assertNotNull(subHeadersMap.get("oauth_nonce"));
assertNotNull(subHeadersMap.get("oauth_signature"));
assertNotNull(subHeadersMap.get("oauth_callback"));
assertNotNull(subHeadersMap.get("oauth_signature_method"));
assertNotNull(subHeadersMap.get("oauth_version"));
assertNotNull(subHeadersMap.get("oauth_timestamp"));
}
private void assertDigestAuthenticationHeaders(Map<String, String> subHeadersMap) {
assertEquals(EchoHeaders.NONCE_VALUE, subHeadersMap.get(EchoHeaders.NONCE_KEY));
assertEquals(EchoHeaders.OPAQUE_VALUE, subHeadersMap.get(EchoHeaders.OPAQUE_KEY));
assertEquals(EchoHeaders.QOP_VALUE, subHeadersMap.get(EchoHeaders.QOP_KEY));
assertEquals(EchoHeaders.REALM_VALUE, subHeadersMap.get(EchoHeaders.REALM_KEY));
assertEquals(USERNAME, subHeadersMap.get("username"));
assertEquals("/echo-headers/digest", subHeadersMap.get("uri"));
assertNotNull(subHeadersMap.get("cnonce"));
assertEquals("00000001", subHeadersMap.get("nc"));
assertNotNull(subHeadersMap.get("response"));
}
private Map<String, String> parseAuthenticationSubHeader(Response response, int startAt) {
String authorizationHeader = response.getHeaderString(AUTHORIZATION_HEADER_KEY);
// The substring(startAt) is used to cut off the authentication schema part from the value returned.
String[] subHeadersKeyValue = authorizationHeader.substring(startAt).split(",");
Map<String, String> subHeadersMap = new HashMap<>();
for (String subHeader : subHeadersKeyValue) {
String[] keyValue = subHeader.split("=");
if (keyValue[1].startsWith("\"")) {
keyValue[1] = keyValue[1].substring(1, keyValue[1].length() - 1);
}
subHeadersMap.put(keyValue[0].trim(), keyValue[1].trim());
}
return subHeadersMap;
}
private void assertBasicAuthenticationHeaders(Response response) {
String base64Credentials = response.getHeaderString(AUTHORIZATION_HEADER_KEY);
// The substring(6) is used to cut the "Basic " part of the value returned,
// as it's used to indicates the authentication schema and does not belong to the credentials
byte[] credentials = Base64.getDecoder().decode(base64Credentials.substring(6));
String[] credentialsParsed = new String(credentials).split(":");
assertEquals(credentialsParsed[0], USERNAME);
assertEquals(credentialsParsed[1], PASSWORD);
}
@Override
protected Application configure() {
return new ResourceConfig()
.register(EchoHeaders.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
// We need this definition here, because if you are running
// the complete suit test the sendingRestrictedHeaderThroughDefaultTransportConnector_shouldReturnThanBack
// will fail if only defined on the client method, since the JerseyTest is created once.
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
}
}