Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for new property to ignore responses in exceptions thrown by the Client API #4641

Merged
merged 1 commit into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,27 @@ public final class ClientProperties {
*/
public static final String USE_ENCODING = "jersey.config.client.useEncoding";

/**
* Ignore a response in an exception thrown by the client API by not forwarding
* it to this service's client. A value of {@code true} indicates that responses
* will be ignored, and only the response status will return to the client. This
* property will normally be specified as a system property; note that system
* properties are only visible if {@link CommonProperties#ALLOW_SYSTEM_PROPERTIES_PROVIDER}
* is set to {@code true}.
* <p>
* The value MUST be an instance convertible to {@link java.lang.Boolean}.
* </p>
* <p>
* The default value is {@code false}.
* </p>
* <p>
* The name of the configuration property is <tt>{@value}</tt>.
* </p>
*
* @see org.glassfish.jersey.CommonProperties#ALLOW_SYSTEM_PROPERTIES_PROVIDER
*/
public static final String IGNORE_EXCEPTION_RESPONSE = "jersey.config.client.ignoreExceptionResponse";

/**
* If {@code true} then disable auto-discovery on the client.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public class JerseyInvocation implements javax.ws.rs.client.Invocation {
// Copy request context when invoke or submit methods are invoked.
private final boolean copyRequestContext;

private boolean ignoreResponseException;

private JerseyInvocation(final Builder builder) {
this(builder, false);
}
Expand All @@ -91,6 +93,15 @@ private JerseyInvocation(final Builder builder, final boolean copyRequestContext

this.requestContext = new ClientRequest(builder.requestContext);
this.copyRequestContext = copyRequestContext;

Object value = builder.requestContext.getConfiguration()
.getProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE);
if (value != null) {
Boolean booleanValue = PropertiesHelper.convertValue(value, Boolean.class);
if (booleanValue != null) {
this.ignoreResponseException = booleanValue;
}
}
}

private enum EntityPresence {
Expand Down Expand Up @@ -875,56 +886,60 @@ public JerseyInvocation property(final String name, final Object value) {
}

private ProcessingException convertToException(final Response response) {
// Use an empty response if ignoring response in exception
final int statusCode = response.getStatus();
final Response finalResponse = ignoreResponseException ? Response.status(statusCode).build() : response;

try {
// Buffer and close entity input stream (if any) to prevent
// leaking connections (see JERSEY-2157).
response.bufferEntity();

final WebApplicationException webAppException;
final int statusCode = response.getStatus();
final Response.Status status = Response.Status.fromStatusCode(statusCode);

if (status == null) {
final Response.Status.Family statusFamily = response.getStatusInfo().getFamily();
webAppException = createExceptionForFamily(response, statusFamily);
final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily();
webAppException = createExceptionForFamily(finalResponse, statusFamily);
} else {
switch (status) {
case BAD_REQUEST:
webAppException = new BadRequestException(response);
webAppException = new BadRequestException(finalResponse);
break;
case UNAUTHORIZED:
webAppException = new NotAuthorizedException(response);
webAppException = new NotAuthorizedException(finalResponse);
break;
case FORBIDDEN:
webAppException = new ForbiddenException(response);
webAppException = new ForbiddenException(finalResponse);
break;
case NOT_FOUND:
webAppException = new NotFoundException(response);
webAppException = new NotFoundException(finalResponse);
break;
case METHOD_NOT_ALLOWED:
webAppException = new NotAllowedException(response);
webAppException = new NotAllowedException(finalResponse);
break;
case NOT_ACCEPTABLE:
webAppException = new NotAcceptableException(response);
webAppException = new NotAcceptableException(finalResponse);
break;
case UNSUPPORTED_MEDIA_TYPE:
webAppException = new NotSupportedException(response);
webAppException = new NotSupportedException(finalResponse);
break;
case INTERNAL_SERVER_ERROR:
webAppException = new InternalServerErrorException(response);
webAppException = new InternalServerErrorException(finalResponse);
break;
case SERVICE_UNAVAILABLE:
webAppException = new ServiceUnavailableException(response);
webAppException = new ServiceUnavailableException(finalResponse);
break;
default:
final Response.Status.Family statusFamily = response.getStatusInfo().getFamily();
webAppException = createExceptionForFamily(response, statusFamily);
final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily();
webAppException = createExceptionForFamily(finalResponse, statusFamily);
}
}

return new ResponseProcessingException(response, webAppException);
return new ResponseProcessingException(finalResponse, webAppException);
} catch (final Throwable t) {
return new ResponseProcessingException(response, LocalizationMessages.RESPONSE_TO_EXCEPTION_CONVERSION_FAILED(), t);
return new ResponseProcessingException(finalResponse,
LocalizationMessages.RESPONSE_TO_EXCEPTION_CONVERSION_FAILED(), t);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.tests.e2e.client;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.concurrent.atomic.AtomicReference;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNull;
import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
* Tests ignoring of client responses in exceptions.
*
* @author Santiago Pericas-Geertsen
*/
public class IgnoreExceptionResponseTest extends JerseyTest {

static String lastAllowSystemProperties;
static String lastIgnoreExceptionResponse;
static AtomicReference<URI> baseUri = new AtomicReference<>();

@Override
protected Application configure() {
return new ResourceConfig(TestResource.class);
}

public IgnoreExceptionResponseTest() {
baseUri.set(getBaseUri());
}

/**
* Sets ignore exception response as system property after enabling the provider.
*/
@BeforeClass
public static void startUp() {
lastAllowSystemProperties = System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, "true");
lastIgnoreExceptionResponse = System.setProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE, "true");
}

/**
* Restores state after completion.
*/
@AfterClass
public static void cleanUp() {
if (lastIgnoreExceptionResponse != null) {
System.setProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE, lastIgnoreExceptionResponse);
}
if (lastAllowSystemProperties != null) {
System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, lastAllowSystemProperties);
}
}

@Test
public void test() {
Client client = ClientBuilder.newClient();
Response r = client.target(getBaseUri())
.path("test")
.path("first")
.request()
.get();
assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), r.getStatus());
assertNull(r.getHeaderString("confidential"));
assertNull(r.getCookies().get("confidential"));
assertFalse(r.hasEntity());
}

@Path("test")
public static class TestResource {

@Path("first")
@GET
public String first() {
Client client = ClientBuilder.newClient();
String entity = client.target(baseUri.get())
.path("test")
.path("second")
.request()
.get(String.class); // WebApplicationException may be thrown
return processEntity(entity);
}

@Path("second")
@GET
public String second() {
throw new WebApplicationException(
"Leaking confidential information",
Response.status(500)
.header("confidential", "nuke-codes")
.cookie(NewCookie.valueOf("confidential=more-nuke-codes"))
.entity("even-more-nuke-codes")
.build());
}

private String processEntity(String entity) {
return entity; // filter confidential information
}
}
}