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

Unable to use custom handlers for HTTP OPTIONS method in subresources #45174

Merged
merged 1 commit into from
Dec 18, 2024
Merged
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
@@ -1,10 +1,15 @@
package io.quarkus.resteasy.reactive.server.test.resource.basic;

import static io.restassured.RestAssured.given;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

import java.util.Arrays;
import java.util.function.Supplier;

import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
@@ -22,6 +27,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.CorsPreflightResource;
import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorAbstractAnnotationFreeResource;
import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorAnnotationFreeSubResource;
import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorBaseResource;
@@ -68,7 +74,7 @@ public JavaArchive get() {
JavaArchive war = ShrinkWrap.create(JavaArchive.class);
war.addClass(ResourceLocatorQueueReceiver.class).addClass(ResourceLocatorReceiver.class)
.addClass(ResourceLocatorRootInterface.class).addClass(ResourceLocatorSubInterface.class)
.addClass(ResourceLocatorSubresource3Interface.class);
.addClass(ResourceLocatorSubresource3Interface.class).addClass(CorsPreflightResource.class);
war.addClasses(PortProviderUtil.class, ResourceLocatorAbstractAnnotationFreeResource.class,
ResourceLocatorAnnotationFreeSubResource.class, ResourceLocatorBaseResource.class,
ResourceLocatorCollectionResource.class, ResourceLocatorDirectory.class,
@@ -114,6 +120,48 @@ public void testSubresource() throws Exception {
}
}

/**
* @tpTestDetails Return custom handler for HTTP OPTIONS method in subresource redirection. The
* {@link CorsPreflightResource} instance should be returned
*/
@Test
@DisplayName("Test custom HTTP OPTIONS handler in subresource")
public void testOptionsMethodInSubresource() {
try (Response response = client.target(generateURL("/sub3/something/resources/test-options-method")).request()
.options()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));

var customHeader = response.getHeaderString(CorsPreflightResource.TEST_PREFLIGHT_HEADER);
assertThat(customHeader, notNullValue());
assertThat(customHeader, is("test"));

var allowHeader = response.getHeaderString("Allow");
assertThat(allowHeader, notNullValue());
assertThat(Arrays.asList(allowHeader.split(", ")),
containsInAnyOrder(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS, HttpMethod.HEAD));
}
}

/**
* @tpTestDetails Custom handler for HTTP OPTIONS method in subresource.
*/
@Test
@DisplayName("Test custom explicit HTTP OPTIONS handler in subresource")
public void testOptionsMethodExplicitInSubresource() {
try (Response response = client.target(generateURL("/sub3/something/resources/test-options-method-explicit")).request()
.options()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));

var customHeader = response.getHeaderString(ResourceLocatorSubresource2.TEST_PREFLIGHT_HEADER);
assertThat(customHeader, notNullValue());
assertThat(customHeader, is("test"));

var allowHeader = response.getHeaderString("Allow");
assertThat(allowHeader, notNullValue());
assertThat(Arrays.asList(allowHeader.split(", ")), containsInAnyOrder(HttpMethod.GET));
}
}

/**
* @tpTestDetails Two matching methods, one a resource locator, the other a resource method.
* @tpSince RESTEasy 3.0.20
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.resteasy.reactive.server.test.resource.basic.resource;

import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

public class CorsPreflightResource {
public static final String TEST_PREFLIGHT_HEADER = "preflight-header-test";

@Path("{any:.*}")
@OPTIONS
public Response preflight() {
return Response.ok().allow(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS, HttpMethod.HEAD)
.header(TEST_PREFLIGHT_HEADER, "test").build();
}
}
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import java.util.List;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@@ -61,4 +62,10 @@ public ResourceLocatorSubresource getSubresource() {
return new ResourceLocatorSubresource();
}

@OPTIONS
@Path("{any:.*}")
public Object preflight() {
return "Here might be a custom handler for HTTP OPTIONS method";
}

}
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
@@ -13,6 +14,8 @@
import org.jboss.resteasy.reactive.RestPath;
import org.junit.jupiter.api.Assertions;

import io.vertx.core.http.HttpServerRequest;

public class ResourceLocatorSubresource {

private static final Logger LOG = Logger.getLogger(ResourceLocatorSubresource.class);
@@ -62,6 +65,20 @@ public String getValueFromBeanParam(@BeanParam Params params) {
return params.param + " and " + params.value;
}

@Path("/test-options-method-explicit")
public Object testOptionsMethodExplicit() {
return new ResourceLocatorSubresource2();
}

@Path("/test-options-method")
public Object testOptionsMethod(@Context HttpServerRequest request) {
if (request.method().name().equals(HttpMethod.OPTIONS)) {
return new CorsPreflightResource();
}

return "Should be used only with HTTP @OPTIONS method";
}

public static class Params {
@RestPath
String param;
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package io.quarkus.resteasy.reactive.server.test.resource.basic.resource;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;

import org.jboss.logging.Logger;
import org.junit.jupiter.api.Assertions;

public class ResourceLocatorSubresource2 {

public static final String TEST_PREFLIGHT_HEADER = "test-preflight-header";
private static final Logger LOG = Logger.getLogger(ResourceLocatorSubresource2.class);

@GET
@@ -35,4 +38,10 @@ public String doGet(@PathParam("param") String param, @Context UriInfo uri) {
Assertions.assertEquals("2", param);
return this.getClass().getName() + "-" + param;
}

@OPTIONS
@Path("{any:.*}")
public Response preflight() {
return Response.ok().allow(HttpMethod.GET).header(TEST_PREFLIGHT_HEADER, "test").build();
}
}
Original file line number Diff line number Diff line change
@@ -63,29 +63,29 @@ public void onComplete(Throwable throwable) {
RequestMapper<RuntimeResource> mapper = target.get(requestContext.getMethod());
boolean hadNullMethodMapper = false;
if (mapper == null) {
String requestMethod = requestContext.getMethod();
if (requestMethod.equals(HttpMethod.HEAD)) {
mapper = target.get(HttpMethod.GET);
} else if (requestMethod.equals(HttpMethod.OPTIONS)) {
Set<String> allowedMethods = new HashSet<>();
for (String method : target.keySet()) {
if (method == null) {
continue;
}
allowedMethods.add(method);
}
allowedMethods.add(HttpMethod.OPTIONS);
allowedMethods.add(HttpMethod.HEAD);
requestContext.abortWith(Response.ok().allow(allowedMethods).build());
return;
}
mapper = target.get(null); //another layer of resource locators maybe
// we set this without checking if we matched, but we only use it after
// we check for a null mapper, so by the time we use it, it must have meant that
// we had a matcher for a null method
hadNullMethodMapper = true;

if (mapper == null) {
mapper = target.get(null); //another layer of resource locators maybe
// we set this without checking if we matched, but we only use it after
// we check for a null mapper, so by the time we use it, it must have meant that
// we had a matcher for a null method
hadNullMethodMapper = true;
String requestMethod = requestContext.getMethod();
if (requestMethod.equals(HttpMethod.HEAD)) {
mapper = target.get(HttpMethod.GET);
} else if (requestMethod.equals(HttpMethod.OPTIONS)) {
Set<String> allowedMethods = new HashSet<>();
for (String method : target.keySet()) {
if (method == null) {
continue;
}
allowedMethods.add(method);
}
allowedMethods.add(HttpMethod.OPTIONS);
allowedMethods.add(HttpMethod.HEAD);
requestContext.abortWith(Response.ok().allow(allowedMethods).build());
return;
}
}
}
if (mapper == null) {