Skip to content

Commit

Permalink
Stork path param resolution fix: use raw path and avoid double encodi…
Browse files Browse the repository at this point in the history
…ng when creating new URI

Related to quarkusio#37713

refactor: clean up a few commented lines

fix: use raw path and avoid double encoding, adapt tests accordingly
  • Loading branch information
aureamunoz committed Jan 9, 2024
1 parent 2735e21 commit ce206d5
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.quarkus.rest.client.reactive.stork;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

Expand All @@ -10,4 +14,13 @@
public interface HelloClient {
@GET
String hello();

@POST
@Consumes(MediaType.TEXT_PLAIN)
@Path("/")
String echo(String name);

@GET
@Path("/{name}")
public String helloWithPathParam(@PathParam("name") String name);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package io.quarkus.rest.client.reactive.stork;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Request;

@Path("/hello")
public class HelloResource {
Expand All @@ -12,4 +18,16 @@ public class HelloResource {
public String hello() {
return HELLO_WORLD;
}

@GET
@Path("/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String invoke(@PathParam("name") String name) {
return "Hello, " + name;
}

@POST
public String echo(String name, @Context Request request) {
return "hello, " + name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;
Expand All @@ -22,6 +23,18 @@ public String invokeClient() {
return client.hello();
}

@Path("/v2/{name}")
@GET
public String invokeClientWithPathParamContainingSlash(@PathParam("name") String name) {
return client.helloWithPathParam(name + "/" + name);
}

@Path("/{name}")
@GET
public String invokeClientWithPathParam(@PathParam("name") String name) {
return client.helloWithPathParam(name);
}

@Path("/cdi")
@GET
public String invokeCdiClient() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,25 @@ void shouldModifyStorkSettings() {
.body(equalTo(WIREMOCK_RESPONSE));
// @formatter:on
}

@Test
void shouldSayHelloNameWithSlash() {
when()
.get("/helper/v2/stork")
.then()
.statusCode(200)
// The response contains an encoded `/`
.body(equalTo("Hello, stork/stork"));

}

@Test
void shouldSayHelloNameWithBlank() {
when()
.get("/helper/smallrye stork")
.then()
.statusCode(200)
// The response contains an encoded blank espace
.body(equalTo("Hello, smallrye stork"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,65 @@
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.rest.client.reactive.HelloClient2;
import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.stork.api.NoSuchServiceDefinitionException;

public class StorkIntegrationTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloClient2.class, HelloResource.class))
.addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-application.properties");

@RestClient
HelloClient2 client;
HelloClient client;

@Test
void shouldDetermineUrlViaStork() {
String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello"))
.build(HelloClient2.class)
.build(HelloClient.class)
.echo("black and white bird");
assertThat(greeting).isEqualTo("hello, black and white bird");

greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello"))
.build(HelloClient.class)
.helloWithPathParam("black and white bird");
assertThat(greeting).isEqualTo("Hello, black and white bird");
}

@Test
void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException {
String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request().get(String.class);
assertThat(greeting).isEqualTo("Hello");
String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request()
.get(String.class);
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target(new URI("stork://hello-service/hello")).request().get(String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/hello")).request()
.get(String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target("stork://hello-service/hello").path("big bird").request()
.get(String.class);
assertThat(greeting).isEqualTo("Hello, big bird");
}

@Test
void shouldDetermineUrlViaStorkCDI() {
String greeting = client.echo("big bird");
assertThat(greeting).isEqualTo("hello, big bird");

greeting = client.helloWithPathParam("big bird");
assertThat(greeting).isEqualTo("Hello, big bird");
}

@Test
@Timeout(20)
void shouldFailOnUnknownService() {
HelloClient2 client2 = RestClientBuilder.newBuilder()
HelloClient client = RestClientBuilder.newBuilder()
.baseUri(URI.create("stork://nonexistent-service"))
.build(HelloClient2.class);
assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
.build(HelloClient.class);
assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;

import io.quarkus.rest.client.reactive.HelloClient2;
import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;

public class StorkResponseTimeLoadBalancerTest {
Expand All @@ -28,7 +26,7 @@ public class StorkResponseTimeLoadBalancerTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloClient2.class, HelloResource.class))
.addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-stat-lb.properties");

@BeforeAll
Expand All @@ -46,7 +44,7 @@ public static void shutDown() {
}

@RestClient
HelloClient2 client;
HelloClient client;

@Test
void shouldUseFasterService() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,64 @@
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.rest.client.reactive.HelloClient2;
import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.stork.api.NoSuchServiceDefinitionException;

public class StorkWithPathIntegrationTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloClient2.class, HelloResource.class))
.addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-application-with-path.properties");

@RestClient
HelloClient2 client;
HelloClient client;

@Test
void shouldDetermineUrlViaStork() {
String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service"))
.build(HelloClient2.class)
.build(HelloClient.class)
.echo("black and white bird");
assertThat(greeting).isEqualTo("hello, black and white bird");

greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service"))
.build(HelloClient.class)
.helloWithPathParam("black and white bird");
assertThat(greeting).isEqualTo("Hello, black and white bird");
}

@Test
void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException {
String greeting = ClientBuilder.newClient().target("stork://hello-service").request().get(String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target(new URI("stork://hello-service")).request().get(String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/")).request()
.get(String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greeting).isEqualTo("Hello, World!");

greeting = ClientBuilder.newClient().target("stork://hello-service/").path("big bird").request()
.get(String.class);
assertThat(greeting).isEqualTo("Hello, big bird");
}

@Test
void shouldDetermineUrlViaStorkCDI() {
String greeting = client.echo("big bird");
assertThat(greeting).isEqualTo("hello, big bird");

greeting = client.helloWithPathParam("big bird");
assertThat(greeting).isEqualTo("Hello, big bird");
}

@Test
@Timeout(20)
void shouldFailOnUnknownService() {
HelloClient2 client2 = RestClientBuilder.newBuilder()
HelloClient client = RestClientBuilder.newBuilder()
.baseUri(URI.create("stork://nonexistent-service"))
.build(HelloClient2.class);
assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
.build(HelloClient.class);
assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.ext.Provider;

import org.jboss.logging.Logger;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
// Service instance can also contain an optional path.
Optional<String> path = instance.getPath();
String actualPath = uri.getPath();
String actualPath = uri.getRawPath();
if (path.isPresent()) {
var p = path.get();
if (!p.startsWith("/")) {
Expand All @@ -79,11 +80,12 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
}
}

//To avoid the path double encoding we create uri with path=null and set the path after
URI newUri = new URI(scheme,
uri.getUserInfo(), host, port,
actualPath, uri.getQuery(), uri.getFragment());
requestContext.setUri(newUri);
null, uri.getQuery(), uri.getFragment());
URI build = UriBuilder.fromUri(newUri).path(actualPath).build();
requestContext.setUri(build);
if (measureTime && instance.gatherStatistics()) {
requestContext.setCallStatsCollector(instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
Expand All @@ -11,4 +14,9 @@ public interface Client {
@GET
@Consumes(MediaType.TEXT_PLAIN)
String echo(String name);

@GET
@Path("/v2/{name}")
@Produces(MediaType.TEXT_PLAIN)
String invoke(@PathParam("name") String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RestClient;

Expand All @@ -17,4 +20,11 @@ public class ClientCallingResource {
public String passThrough() {
return client.echo("World!");
}

@GET
@Path("/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String invoke(@PathParam("name") String name) {
return client.invoke(name + "/" + name);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.it.rest.reactive.stork;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate;

import java.util.Map;

Expand All @@ -25,6 +26,8 @@ int httpsPort() {
protected Map<String, String> initWireMock(WireMockServer server) {
server.stubFor(WireMock.get("/hello")
.willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200)));
server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}"))
.willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200)));
return Map.of("fast-service", "localhost:8443");
}
}
Loading

0 comments on commit ce206d5

Please sign in to comment.