Skip to content

Commit

Permalink
[283] Add new RestClientBuilder method for adding headers.
Browse files Browse the repository at this point in the history
Signed-off-by: Adam Anderson <[email protected]>
  • Loading branch information
WhiteCat22 committed Jun 14, 2024
1 parent 78693d7 commit 32d1323
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,20 @@ default RestClientBuilder baseUri(String uri) {
*/
RestClientBuilder queryParamStyle(QueryParamStyle style);

/**
* Add an arbitrary header.
*
* @param name
* - the name of the header
* @param name
* - the value of the HTTP header to add to the request.
* @return the current builder with the header added to the request.
* @throws NullPointerException
* if the value is null.
* @since 4.0
*/
RestClientBuilder header(String name, Object value);

/**
* Based on the configured RestClientBuilder, creates a new instance of the given REST interface to invoke API calls
* against.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public RestClientBuilder queryParamStyle(QueryParamStyle style) {
throw new IllegalStateException("not implemented");
}

@Override
public RestClientBuilder header(String name, Object value) {
throw new IllegalStateException("not implemented");
}

@Override
public <T> T build(Class<T> clazz) {
throw new IllegalStateException("not implemented");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public RestClientBuilder queryParamStyle(QueryParamStyle style) {
throw new IllegalStateException("not implemented");
}

@Override
public RestClientBuilder header(String name, Object value) {
throw new IllegalStateException("not implemented");
}

@Override
public <T> T build(Class<T> clazz) {
throw new IllegalStateException("not implemented");
Expand Down
13 changes: 13 additions & 0 deletions spec/src/main/asciidoc/clientexamples.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ implementation must invoke the `DefaultClientHeadersFactoryImpl`. This default f

`org.eclipse.microprofile.rest.client.propagateHeaders`

You can also configure headers on a per instance basis using the `RestClientBuilder.header(String name, Object value)` method. Headers added via this method will be merged with the headers added via `@ClientHeaderParam` annotations, `@HeaderParam` annotations, and `ClientHeadersFactory` implementations.
**Note: The method will throw a `NullPointerException` if the value is `null`.**

Example:

[source, java]
----
RedirectClient client = RestClientBuilder.newBuilder()
.baseUri(someUri)
.header("Some-Header", headerValueObj)
.build(SomeClient.class);
----

=== Following Redirect Responses

By default, a Rest Client instance will not automatically follow redirect responses. Redirect responses are typically responses with status codes in the 300 range and include `Location` header that indicates the URL of the redirected resource.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.eclipse.microprofile.rest.client.tck;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.containing;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

import java.util.Arrays;
import java.util.stream.Collectors;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.tck.interfaces.SimpleGetApi;
import org.eclipse.microprofile.rest.client.tck.providers.ReturnWithAllClientHeadersFilter;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import com.github.tomakehurst.wiremock.client.MappingBuilder;

public class ClientBuilderHeaderTest extends WiremockArquillianTest {
@Deployment
public static Archive<?> createDeployment() {
return ShrinkWrap.create(WebArchive.class, ClientBuilderHeaderTest.class.getSimpleName() + ".war")
.addClasses(
SimpleGetApi.class,
ReturnWithAllClientHeadersFilter.class,
WiremockArquillianTest.class);
}

private static void stub(String expectedHeaderName, String... expectedHeaderValue) {
String expectedIncomingHeader = Arrays.stream(expectedHeaderValue)
.collect(Collectors.joining(","));
String outputBody = expectedIncomingHeader.replace(',', '-');
MappingBuilder mappingBuilder = get(urlEqualTo("/"));

// headers can be sent either in a single line with comma-separated values or in multiple lines
// this should match both cases:
Arrays.stream(expectedHeaderValue)
.forEach(val -> mappingBuilder.withHeader(expectedHeaderName, containing(val)));
stubFor(
mappingBuilder
.willReturn(
aResponse().withStatus(200)
.withBody(outputBody)));
}
@BeforeTest
public void resetWiremock() {
setupServer();
}

@Test
public void testHeaderBuilderMethod() {
stub("BuilderHeader", "BuilderHeaderValue");

RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri(getServerURI());
builder.header("BuilderHeader", "BuilderHeaderValue");
SimpleGetApi client = builder.build(SimpleGetApi.class);

assertEquals(client.executeGet(),
"BuilderHeaderValue");
}

@Test
public void testHeaderBuilderMethodNullValue() {
stub("BuilderHeader", "BuilderHeaderValue");

RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri(getServerURI());
try {
builder.header("BuilderHeader", null);
} catch (NullPointerException npe) {
return;
}
fail("header(\"builderHeader\", null) should have thrown a NullPointerException");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
Expand All @@ -43,6 +45,7 @@

import com.github.tomakehurst.wiremock.client.MappingBuilder;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;

public class ClientHeaderParamTest extends WiremockArquillianTest {
Expand All @@ -62,6 +65,8 @@ private static ClientHeaderParamClient client(Class<?>... providers) {
for (Class<?> provider : providers) {
builder.register(provider);
}
builder.header("BuilderHeader", "BuilderHeaderValue")
.header("InterfaceAndBuilderHeader", "builder");
return builder.build(ClientHeaderParamClient.class);
} catch (Throwable t) {
t.printStackTrace();
Expand Down Expand Up @@ -177,6 +182,7 @@ public void testHeaderNotSentWhenExceptionThrownAndRequiredIsFalse() {
assertEquals(headers.getString("OverrideableExplicit"), "overrideableInterfaceExplicit");
assertEquals(headers.getString("InterfaceHeaderComputed"), "interfaceComputed");
assertEquals(headers.getString("MethodHeaderExplicit"), "SomeValue");
assertEquals(headers.getString("BuilderHeaderValue"), "BuilderHeaderValue");
}

@Test
Expand All @@ -192,4 +198,21 @@ public void testMultivaluedHeaderInterfaceExplicit() {
assertEquals(client().methodComputeMultiValuedHeaderFromOtherClass(),
"abc-xyz");
}

@Test
public void testInterfaceAndBuilderHeaderBothExist() {
stub("InterfaceAndBuilderHeader", "builder", "interface", "method");

JsonObject headers = client(ReturnWithAllClientHeadersFilter.class).getAllHeaders();
JsonArray header = headers.getJsonArray("InterfaceAndBuilderHeader");
final List<String> headerValues =
header.stream().map(v -> v.toString().toLowerCase()).collect(Collectors.toList());

assertTrue(headerValues.contains("builder"),
"Header InterfaceAndBuilderHeader did not container \"builder\": " + headers);
assertTrue(headerValues.contains("interface"),
"Header InterfaceAndBuilderHeader did not container \"interface\": " + headers);
assertTrue(headerValues.contains("method"),
"Header InterfaceAndBuilderHeader did not container \"method\": " + headers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import static org.testng.Assert.assertTrue;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.tck.ext.CustomClientHeadersFactory;
Expand All @@ -35,6 +37,7 @@
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;

public class ClientHeadersFactoryTest extends Arquillian {
Expand All @@ -53,6 +56,8 @@ private static ClientHeadersFactoryClient client(Class<?>... providers) {
for (Class<?> provider : providers) {
builder.register(provider);
}
builder.header("myHeader", "myHeaderValue")
.header("InterfaceAndBuilderHeader", "builder");
return builder.build(ClientHeadersFactoryClient.class);
} catch (Throwable t) {
t.printStackTrace();
Expand All @@ -79,5 +84,22 @@ public void testClientHeadersFactoryInvoked() {
assertEquals(headers.getString("MethodHeader"), "methodValueModified");
assertEquals(headers.getString("ArgHeader"), "argValueModified");
assertEquals(headers.getString("FactoryHeader"), "factoryValue");
assertEquals(headers.getString("BuilderHeader"), "BuilderHeaderValue");
}

@Test
public void testInterfaceAndBuilderHeaderBothExist() {

JsonObject headers = client(ReturnWithAllClientHeadersFilter.class).getAllHeaders();
JsonArray header = headers.getJsonArray("InterfaceAndBuilderHeader");
final List<String> headerValues =
header.stream().map(v -> v.toString().toLowerCase()).collect(Collectors.toList());

assertTrue(headerValues.contains("builder"),
"Header InterfaceAndBuilderHeader did not container \"builder\": " + headers);
assertTrue(headerValues.contains("interface"),
"Header InterfaceAndBuilderHeader did not container \"interface\": " + headers);
assertTrue(headerValues.contains("method"),
"Header InterfaceAndBuilderHeader did not container \"method\": " + headers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
@ClientHeaderParam(name = "OverrideableComputed", value = "{computeForInterface2}")
@ClientHeaderParam(name = "OptionalInterfaceHeader", value = "{fail}", required = false)
@ClientHeaderParam(name = "InterfaceMultiValuedHeaderExplicit", value = {"abc", "xyz"})
@ClientHeaderParam(name = "InterfaceAndBuilderHeader", value = "interface")
@Path("/")
public interface ClientHeaderParamClient {
@GET
Expand Down Expand Up @@ -85,6 +86,10 @@ public interface ClientHeaderParamClient {
// CHECKSTYLE:OFF
String methodComputeMultiValuedHeaderFromOtherClass();

@GET
@ClientHeaderParam(name = "InterfaceAndBuilderHeader", value = "method")
JsonObject getAllHeaders();

default String computeForInterface() {
return "interfaceComputed";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import jakarta.json.JsonObject;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;

Expand All @@ -34,4 +35,8 @@ public interface ClientHeadersFactoryClient {
@DELETE
@ClientHeaderParam(name = "MethodHeader", value = "methodValue")
JsonObject delete(@HeaderParam("ArgHeader") String argHeader);

@GET
@ClientHeaderParam(name = "InterfaceAndBuilderHeader", value = "method")
JsonObject getAllHeaders();
}

0 comments on commit 32d1323

Please sign in to comment.