Skip to content

Commit

Permalink
Unable to use custom handlers for HTTP OPTIONS method in subresources
Browse files Browse the repository at this point in the history
Fixes quarkusio#45173

Signed-off-by: Martin Bartoš <[email protected]>
  • Loading branch information
mabartos committed Dec 17, 2024
1 parent cfa2677 commit 087ceef
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.util.function.Supplier;

import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.CorsPreflightResource;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
Expand Down Expand Up @@ -37,6 +38,13 @@
import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorSubresource3Interface;
import io.quarkus.resteasy.reactive.server.test.simple.PortProviderUtil;
import io.quarkus.test.QuarkusUnitTest;
import jakarta.ws.rs.HttpMethod;

import java.util.Arrays;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.notNullValue;

/**
* @tpSubChapter Resource
Expand Down Expand Up @@ -68,7 +76,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,
Expand Down Expand Up @@ -114,6 +122,44 @@ 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
Expand Down
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
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Up @@ -2,8 +2,10 @@

import java.util.List;

import io.vertx.core.http.HttpServerRequest;
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;
Expand Down Expand Up @@ -62,6 +64,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;
Expand Down
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
Expand All @@ -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
Expand Up @@ -63,29 +63,30 @@ 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;
switch (requestContext.getMethod()) {
case HttpMethod.HEAD:
mapper = target.get(HttpMethod.GET);
break;
case 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) {
Expand Down

0 comments on commit 087ceef

Please sign in to comment.