Skip to content

Commit

Permalink
Merge pull request #378 from WhiteCat22/header_builder_method
Browse files Browse the repository at this point in the history
[283] Add new RestClientBuilder method for adding headers.
Emily-Jiang authored Jun 17, 2024
2 parents 8e48b96 + eeb764d commit a223aec
Showing 8 changed files with 261 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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");
Original file line number Diff line number Diff line change
@@ -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");
13 changes: 13 additions & 0 deletions spec/src/main/asciidoc/clientexamples.asciidoc
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

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

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.tck.interfaces.ClientBuilderHeaderClient;
import org.eclipse.microprofile.rest.client.tck.interfaces.ClientBuilderHeaderMethodClient;
import org.eclipse.microprofile.rest.client.tck.providers.ReturnWithAllDuplicateClientHeadersFilter;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.Assert;
import org.testng.annotations.Test;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;

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

@Test
public void testHeaderBuilderMethod() {

RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri("http://localhost:8080/");
builder.register(ReturnWithAllDuplicateClientHeadersFilter.class);
builder.header("InterfaceAndBuilderHeader", "builder");
ClientBuilderHeaderMethodClient client = builder.build(ClientBuilderHeaderMethodClient.class);

checkHeaders(client.getAllHeaders("headerparam"), "method");
}

@Test
public void testHeaderBuilderInterface() {

RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri("http://localhost:8080/");
builder.register(ReturnWithAllDuplicateClientHeadersFilter.class);
builder.header("InterfaceAndBuilderHeader", "builder");
ClientBuilderHeaderClient client = builder.build(ClientBuilderHeaderClient.class);

checkHeaders(client.getAllHeaders("headerparam"), "interface");
}

@Test
public void testHeaderBuilderMethodNullValue() {

RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri("http://localhost:8080/");
try {
builder.header("BuilderHeader", null);
} catch (NullPointerException npe) {
return;
}
fail("header(\"builderHeader\", null) should have thrown a NullPointerException");
}

private static void checkHeaders(final JsonObject headers, final String clientHeaderParamName) {
final List<String> clientRequestHeaders = headerValues(headers, "InterfaceAndBuilderHeader");

assertTrue(clientRequestHeaders.contains("builder"),
"Header InterfaceAndBuilderHeader did not container \"builder\": " + clientRequestHeaders);
assertTrue(clientRequestHeaders.contains(clientHeaderParamName),
"Header InterfaceAndBuilderHeader did not container \"" + clientHeaderParamName + "\": "
+ clientRequestHeaders);

final List<String> headerParamHeaders = headerValues(headers, "HeaderParam");
assertTrue(headerParamHeaders.contains("headerparam"),
"Header HeaderParam did not container \"headerparam\": " + headerParamHeaders);
}

private static List<String> headerValues(final JsonObject headers, final String headerName) {
final JsonArray headerValues = headers.getJsonArray(headerName);
Assert.assertNotNull(headerValues,
String.format("Expected header '%s' to be present in %s", headerName, headers));
return headerValues.stream().map(
v -> (v.getValueType() == JsonValue.ValueType.STRING ? ((JsonString) v).getString() : v.toString()))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.interfaces;

import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;

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

@ClientHeaderParam(name = "InterfaceAndBuilderHeader", value = "interface")
@Path("/")
public interface ClientBuilderHeaderClient {

@GET
JsonObject getAllHeaders(@HeaderParam("HeaderParam") String param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.interfaces;

import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;

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

@Path("/")
public interface ClientBuilderHeaderMethodClient {

@GET
@ClientHeaderParam(name = "InterfaceAndBuilderHeader", value = "method")
JsonObject getAllHeaders(@HeaderParam("HeaderParam") String param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2018, 2021 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.providers;

import java.io.IOException;
import java.util.List;

import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

public class ReturnWithAllDuplicateClientHeadersFilter implements ClientRequestFilter {

@Override
public void filter(ClientRequestContext clientRequestContext) throws IOException {
JsonObjectBuilder allClientHeaders = Json.createObjectBuilder();
MultivaluedMap<String, Object> clientHeaders = clientRequestContext.getHeaders();
for (String headerName : clientHeaders.keySet()) {
List<Object> header = clientHeaders.get(headerName);
final JsonArrayBuilder headerValues = Json.createArrayBuilder();
header.forEach(h -> headerValues.add(h.toString()));
allClientHeaders.add(headerName, headerValues);
}
clientRequestContext.abortWith(Response.ok(allClientHeaders.build()).build());
}

}

0 comments on commit a223aec

Please sign in to comment.