com.xlrit.gears.runner.graphql.GraphQLClient Maven / Gradle / Ivy
The newest version!
package com.xlrit.gears.runner.graphql;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.channels.Channels;
import java.nio.channels.Pipe;
import java.util.Objects;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xlrit.gears.runner.runnertarget.InterpreterException;
import com.xlrit.gears.runner.utils.MiscUtils;
import lombok.Setter;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GraphQLClient {
private static final Logger LOG = LoggerFactory.getLogger(GraphQLClient.class);
private static final ContentType JSON_GRAPHQL = ContentType.create("application/json+graphql");
private final String url;
private final ObjectMapper objectMapper;
private final HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
@Setter
private String token = null;
public GraphQLClient(String url, ObjectMapper objectMapper) {
this.url = url;
this.objectMapper = objectMapper;
}
public JsonNode invoke(Operation operation) {
try {
HttpRequest request = makeRequest(operation);
HttpResponse response = sendRequest(request);
JsonNode rootNode = objectMapper.readTree(response.body());
LOG.debug("operation={}, query=\n{}, response={}", operation.name, operation.query, rootNode);
JsonNode errorsNode = rootNode.at("/errors");
if (!errorsNode.isEmpty()) {
throw new GraphQLErrorsException(operation, errorsNode);
}
return rootNode.get("data");
}
catch (URISyntaxException | GraphQLErrorsException | IOException e) {
throw new InterpreterException(e.getMessage(), e);
}
}
private HttpRequest makeRequest(Operation operation) throws URISyntaxException, IOException {
String query = operation.query.replace('\'', '"');
ObjectNode payload = objectMapper.createObjectNode();
payload.put("query", query);
payload.set("variables", objectMapper.valueToTree(operation.variables));
HttpRequest.Builder requestBuild = HttpRequest
.newBuilder()
.uri(new URI(url));
if (token != null) requestBuild.header("Authorization", "BEARER " + token);
if (operation.files == null) {
return requestBuild
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(payload)))
.build();
}
ObjectNode map = objectMapper.createObjectNode();
for (int i = 0; i < operation.files.size(); i++) {
map.set(String.format("file%d", i),
objectMapper.createArrayNode().add(objectMapper.readTree(String.format("\"variables.files.%d\"", i))));
}
MultipartEntityBuilder meb = MultipartEntityBuilder.create()
.addPart("operations", new StringBody(objectMapper.writeValueAsString(payload), JSON_GRAPHQL))
.addPart("map", new StringBody(objectMapper.writeValueAsString(map), ContentType.TEXT_PLAIN));
int i = 0;
for (File file : operation.files) {
ContentType contentType = MiscUtils.getContentType(file.getName());
meb.addBinaryBody(String.format("file%d", i++), file, contentType, file.getName());
}
HttpEntity httpEntity = meb.build();
Pipe pipe = Pipe.open();
new Thread(() -> {
try (OutputStream os = Channels.newOutputStream(pipe.sink())) {
httpEntity.writeTo(os);
} catch (IOException e) {
e.printStackTrace();
throw new InterpreterException(e.getMessage(), e.getCause());
}
}).start();
return requestBuild
.header("Content-Type", httpEntity.getContentType().getValue())
.POST(HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())))
.build();
}
@SuppressWarnings("BusyWait")
private HttpResponse sendRequest(HttpRequest request) throws IOException {
try {
while (true) {
try {
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
catch (IOException e) {
long delay = getRetryDelay(e);
if (delay > 0) {
LOG.info("Retrying HTTP request after {} ms", delay);
Thread.sleep(delay);
}
else throw e;
}
}
}
catch (InterruptedException e) {
// this shouldn't happen, but handle it anyway
Thread.currentThread().interrupt();
throw new InterpreterException("HTTP request/sleep was interrupted: " + e.getMessage(), e);
}
}
private long getRetryDelay(IOException e) {
String msg = Objects.requireNonNullElse(e.getMessage(), "");
if (msg.contains("GOAWAY received")) return 1000L; // retry after one second on HTTP2 "GOAWAY received"
return -1L; // don't retry
}
private static class GraphQLErrorsException extends Exception {
private final String message;
public GraphQLErrorsException(Operation operation, JsonNode errorsNode) {
message = String.format("Error during operation %s: %s", operation.name, errorsNode);
}
@Override
public String getMessage() {
return message;
}
}
}