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

Support for dynamic headers on GraphQL clients #1919

Merged
merged 1 commit into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
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
Expand Up @@ -7,6 +7,7 @@
import java.util.ServiceLoader;

import io.smallrye.graphql.client.websocket.WebsocketSubprotocol;
import io.smallrye.mutiny.Uni;

/**
* Use this builder, when you are not in a CDI context, i.e. when working with Java SE.
Expand Down Expand Up @@ -74,6 +75,8 @@ default TypesafeGraphQLClientBuilder headers(Map<String, String> headers) {
*/
TypesafeGraphQLClientBuilder header(String name, String value);

TypesafeGraphQLClientBuilder dynamicHeader(String name, Uni<String> value);

/**
* Static payload to send with initialization method on subscription.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static java.util.stream.Collectors.toList;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class VertxDynamicGraphQLClient implements DynamicGraphQLClient {

private final boolean executeSingleOperationsOverWebsocket;
private final MultiMap headers;
private final Map<String, Uni<String>> dynamicHeaders;
private final Map<String, Object> initPayload;
private final List<WebsocketSubprotocol> subprotocols;
private final Integer subscriptionInitializationTimeout;
Expand All @@ -66,7 +68,8 @@ public class VertxDynamicGraphQLClient implements DynamicGraphQLClient {

VertxDynamicGraphQLClient(Vertx vertx, WebClient webClient,
String url, String websocketUrl, boolean executeSingleOperationsOverWebsocket,
MultiMap headers, Map<String, Object> initPayload, WebClientOptions options,
MultiMap headers, Map<String, Uni<String>> dynamicHeaders,
Map<String, Object> initPayload, WebClientOptions options,
List<WebsocketSubprotocol> subprotocols, Integer subscriptionInitializationTimeout,
boolean allowUnexpectedResponseFields) {
if (options != null) {
Expand All @@ -80,6 +83,7 @@ public class VertxDynamicGraphQLClient implements DynamicGraphQLClient {
this.webClient = webClient;
}
this.headers = headers;
this.dynamicHeaders = dynamicHeaders;
this.initPayload = initPayload;
if (url != null) {
if (url.startsWith("stork")) {
Expand Down Expand Up @@ -204,6 +208,10 @@ private Response executeSync(JsonObject json, MultiMap additionalHeaders) {
if (additionalHeaders != null) {
allHeaders.addAll(additionalHeaders);
}
// obtain values of dynamic headers and add them to the request
for (Map.Entry<String, Uni<String>> dynamicHeaderEntry : dynamicHeaders.entrySet()) {
allHeaders.add(dynamicHeaderEntry.getKey(), dynamicHeaderEntry.getValue().await().indefinitely());
}
return executeSingleResultOperationOverHttp(json, allHeaders).await().indefinitely();
}
}
Expand Down Expand Up @@ -306,7 +314,20 @@ private Uni<Response> executeAsync(JsonObject json, MultiMap additionalHeaders)
if (additionalHeaders != null) {
allHeaders.addAll(additionalHeaders);
}
return executeSingleResultOperationOverHttp(json, allHeaders);
List<Uni<Void>> unis = new ArrayList<>();
// append dynamic headers to the request
for (Map.Entry<String, Uni<String>> stringUniEntry : dynamicHeaders.entrySet()) {
unis.add(stringUniEntry.getValue().onItem().invoke(headerValue -> {
allHeaders.add(stringUniEntry.getKey(), headerValue);
}).replaceWithVoid());
}
if (unis.isEmpty()) {
return executeSingleResultOperationOverHttp(json, allHeaders);
} else {
return Uni.combine().all().unis(unis)
.combinedWith(f -> f).onItem()
.transformToUni(f -> executeSingleResultOperationOverHttp(json, allHeaders));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.smallrye.graphql.client.vertx.VertxClientOptionsHelper;
import io.smallrye.graphql.client.vertx.VertxManager;
import io.smallrye.graphql.client.websocket.WebsocketSubprotocol;
import io.smallrye.mutiny.Uni;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
Expand All @@ -33,6 +34,7 @@ public class VertxDynamicGraphQLClientBuilder implements DynamicGraphQLClientBui
private Boolean executeSingleOperationsOverWebsocket;
private String configKey;
private final MultiMap headersMap;
private Map<String, Uni<String>> dynamicHeaders;
private final Map<String, Object> initPayload;
private WebClientOptions options;
private List<WebsocketSubprotocol> subprotocols;
Expand All @@ -41,6 +43,7 @@ public class VertxDynamicGraphQLClientBuilder implements DynamicGraphQLClientBui

public VertxDynamicGraphQLClientBuilder() {
headersMap = new HeadersMultiMap();
dynamicHeaders = new HashMap<>();
initPayload = new HashMap<>();
headersMap.set("Content-Type", "application/json");
subprotocols = new ArrayList<>();
Expand All @@ -61,6 +64,11 @@ public VertxDynamicGraphQLClientBuilder header(String name, String value) {
return this;
}

public VertxDynamicGraphQLClientBuilder dynamicHeader(String name, Uni<String> value) {
dynamicHeaders.put(name, value);
return this;
}

public VertxDynamicGraphQLClientBuilder headers(Map<String, String> headers) {
headersMap.setAll(headers);
return this;
Expand Down Expand Up @@ -149,7 +157,7 @@ public DynamicGraphQLClient build() {
allowUnexpectedResponseFields = false;
}
return new VertxDynamicGraphQLClient(toUseVertx, webClient, url, websocketUrl,
executeSingleOperationsOverWebsocket, headersMap, initPayload, options, subprotocols,
executeSingleOperationsOverWebsocket, headersMap, dynamicHeaders, initPayload, options, subprotocols,
subscriptionInitializationTimeout, allowUnexpectedResponseFields);
}

Expand All @@ -169,6 +177,11 @@ private void applyConfig(GraphQLClientConfiguration configuration) {
this.headersMap.set(k, v);
}
});
configuration.getDynamicHeaders().forEach((k, v) -> {
if (!this.dynamicHeaders.containsKey(k)) {
this.dynamicHeaders.put(k, v);
}
});
configuration.getInitPayload().forEach((k, v) -> {
if (!this.initPayload.containsKey(k)) {
this.initPayload.put(k, v);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -22,6 +23,7 @@
import io.smallrye.graphql.client.vertx.VertxClientOptionsHelper;
import io.smallrye.graphql.client.vertx.VertxManager;
import io.smallrye.graphql.client.websocket.WebsocketSubprotocol;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
Expand All @@ -37,6 +39,7 @@ public class VertxTypesafeGraphQLClientBuilder implements TypesafeGraphQLClientB
private String websocketUrl;
private Boolean executeSingleOperationsOverWebsocket;
private Map<String, String> headers;
private Map<String, Uni<String>> dynamicHeaders;
private Map<String, Object> initPayload;
private List<WebsocketSubprotocol> subprotocols;
private Vertx vertx;
Expand Down Expand Up @@ -99,6 +102,15 @@ public VertxTypesafeGraphQLClientBuilder header(String name, String value) {
return this;
}

@Override
public TypesafeGraphQLClientBuilder dynamicHeader(String name, Uni<String> value) {
if (this.dynamicHeaders == null) {
this.dynamicHeaders = new LinkedHashMap<>();
}
this.dynamicHeaders.put(name, value);
return this;
}

public VertxTypesafeGraphQLClientBuilder initPayload(Map<String, Object> initPayload) {
if (this.initPayload == null) {
this.initPayload = new LinkedHashMap<>();
Expand Down Expand Up @@ -152,8 +164,12 @@ public <T> T build(Class<T> apiClass) {
if (allowUnexpectedResponseFields == null) {
allowUnexpectedResponseFields = false;
}
if (dynamicHeaders == null) {
dynamicHeaders = new HashMap<>();
}

VertxTypesafeGraphQLClientProxy graphQlClient = new VertxTypesafeGraphQLClientProxy(apiClass, headers, initPayload,
VertxTypesafeGraphQLClientProxy graphQlClient = new VertxTypesafeGraphQLClientProxy(apiClass, headers,
dynamicHeaders, initPayload,
endpoint,
websocketUrl, executeSingleOperationsOverWebsocket, httpClient, webClient, subprotocols,
websocketInitializationTimeout,
Expand Down Expand Up @@ -222,6 +238,9 @@ private void applyConfig(GraphQLClientConfiguration configuration) {
if (this.headers == null && configuration.getHeaders() != null) {
this.headers = configuration.getHeaders();
}
if (this.dynamicHeaders == null && configuration.getDynamicHeaders() != null) {
this.dynamicHeaders = configuration.getDynamicHeaders();
}
if (this.initPayload == null && configuration.getInitPayload() != null) {
this.initPayload = configuration.getInitPayload();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class VertxTypesafeGraphQLClientProxy {
private final ConcurrentMap<String, String> queryCache = new ConcurrentHashMap<>();

private final Map<String, String> additionalHeaders;
private final Map<String, Uni<String>> dynamicHeaders;
private final Map<String, Object> initPayload;
private final ServiceURLSupplier endpoint;
private final ServiceURLSupplier websocketUrl;
Expand All @@ -90,6 +91,7 @@ class VertxTypesafeGraphQLClientProxy {
VertxTypesafeGraphQLClientProxy(
Class<?> api,
Map<String, String> additionalHeaders,
Map<String, Uni<String>> dynamicHeaders,
Map<String, Object> initPayload,
URI endpoint,
String websocketUrl,
Expand All @@ -101,6 +103,7 @@ class VertxTypesafeGraphQLClientProxy {
boolean allowUnexpectedResponseFields) {
this.api = api;
this.additionalHeaders = additionalHeaders;
this.dynamicHeaders = dynamicHeaders;
this.initPayload = initPayload;
if (endpoint != null) {
if (endpoint.getScheme().startsWith("stork")) {
Expand Down Expand Up @@ -155,22 +158,47 @@ Object invoke(MethodInvocation method) {
}

private Object executeSingleResultOperationOverHttpSync(MethodInvocation method, JsonObject request, MultiMap headers) {
HttpResponse<Buffer> response = postSync(request.toString(), headers);
MultiMap allHeaders = new HeadersMultiMap();
allHeaders.addAll(headers);
// obtain values of dynamic headers and add them to the request
for (Map.Entry<String, Uni<String>> dynamicHeaderEntry : dynamicHeaders.entrySet()) {
allHeaders.add(dynamicHeaderEntry.getKey(), dynamicHeaderEntry.getValue().await().indefinitely());
}
HttpResponse<Buffer> response = postSync(request.toString(), allHeaders);
if (log.isTraceEnabled() && response != null) {
log.tracef("response graphql: %s", response.bodyAsString());
}
return new ResultBuilder(method, response.bodyAsString(),
response.statusCode(), response.statusMessage(), convertHeaders(headers),
response.statusCode(), response.statusMessage(), convertHeaders(allHeaders),
allowUnexpectedResponseFields).read();
}

private Uni<Object> executeSingleResultOperationOverHttpAsync(MethodInvocation method, JsonObject request,
MultiMap headers) {
return Uni.createFrom()
.completionStage(postAsync(request.toString(), headers))
.map(response -> new ResultBuilder(method, response.bodyAsString(),
response.statusCode(), response.statusMessage(), convertHeaders(headers),
allowUnexpectedResponseFields).read());
List<Uni<Void>> unis = new ArrayList<>();
MultiMap allHeaders = new HeadersMultiMap();
allHeaders.addAll(headers);
// obtain values of dynamic headers and add them to the request
for (Map.Entry<String, Uni<String>> stringUniEntry : dynamicHeaders.entrySet()) {
unis.add(stringUniEntry.getValue().onItem().invoke(headerValue -> {
allHeaders.add(stringUniEntry.getKey(), headerValue);
}).replaceWithVoid());
}
if (unis.isEmpty()) {
return Uni.createFrom().completionStage(postAsync(request.toString(), allHeaders))
.map(response -> new ResultBuilder(method, response.bodyAsString(),
response.statusCode(), response.statusMessage(), convertHeaders(allHeaders),
allowUnexpectedResponseFields).read());
} else {
// when all dynamic headers have been obtained, proceed with the request
return Uni.combine().all().unis(unis)
.combinedWith(f -> f)
.onItem().transformToUni(g -> Uni.createFrom()
.completionStage(postAsync(request.toString(), allHeaders))
.map(response -> new ResultBuilder(method, response.bodyAsString(),
response.statusCode(), response.statusMessage(), convertHeaders(allHeaders),
allowUnexpectedResponseFields).read()));
}
}

private Map<String, List<String>> convertHeaders(MultiMap input) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.List;
import java.util.Map;

import io.smallrye.mutiny.Uni;

/**
* The configuration of a single GraphQL client.
*/
Expand All @@ -23,6 +25,11 @@ public class GraphQLClientConfiguration {
*/
private Map<String, String> headers;

/**
* HTTP headers that need to be resolved dynamically for each request.
*/
private Map<String, Uni<String>> dynamicHeaders;

/**
* Additional payload sent on subscription initialization.
*/
Expand Down Expand Up @@ -130,6 +137,14 @@ public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}

public Map<String, Uni<String>> getDynamicHeaders() {
return dynamicHeaders;
}

public void setDynamicHeaders(Map<String, Uni<String>> dynamicHeaders) {
this.dynamicHeaders = dynamicHeaders;
}

public Map<String, Object> getInitPayload() {
return initPayload;
}
Expand Down Expand Up @@ -274,6 +289,11 @@ public GraphQLClientConfiguration merge(GraphQLClientConfiguration other) {
} else if (other.headers != null) {
other.headers.forEach((key, value) -> this.headers.put(key, value));
}
if (this.dynamicHeaders == null) {
this.dynamicHeaders = other.dynamicHeaders;
} else if (other.dynamicHeaders != null) {
other.dynamicHeaders.forEach((key, value) -> this.dynamicHeaders.put(key, value));
}
if (this.initPayload == null) {
this.initPayload = other.initPayload;
} else if (other.initPayload != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ private GraphQLClientConfiguration readConfigurationByKey(String clientName) {

// HTTP headers
configuration.setHeaders(getConfiguredHeaders(clientName, mpConfig));
// dynamic headers can't be configured via config properties
configuration.setDynamicHeaders(new HashMap<>());

configuration.setInitPayload(getConfiguredInitPayload(clientName, mpConfig));
// websocket subprotocols
Expand Down Expand Up @@ -157,6 +159,10 @@ public GraphQLClientConfiguration getClient(String key) {
return clients.computeIfAbsent(key, this::readConfigurationByKey);
}

public Map<String, GraphQLClientConfiguration> getClients() {
return clients;
}

/** this method is required by Quarkus */
public void addClient(String key, GraphQLClientConfiguration config) {
clients.put(key, config);
Expand Down
Loading