Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.hyperfoil.client.RestClient Maven / Gradle / Ivy
package io.hyperfoil.client;
import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import io.hyperfoil.api.config.Benchmark;
import io.hyperfoil.controller.Client;
import io.hyperfoil.controller.model.Version;
import io.hyperfoil.util.Util;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.Json;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
public class RestClient implements Client, Closeable {
final Vertx vertx = Vertx.vertx();
final WebClientOptions options;
final WebClient client;
public RestClient(String host, int port) {
// Actually there's little point in using async client, but let's stay in Vert.x libs
options = new WebClientOptions().setDefaultHost(host).setDefaultPort(port);
client = WebClient.create(vertx, options.setFollowRedirects(false));
}
static RestClientException unexpected(HttpResponse response) {
StringBuilder sb = new StringBuilder("Server responded with unexpected code: ");
sb.append(response.statusCode()).append(", ").append(response.statusMessage());
String body = response.bodyAsString();
if (body != null && !body.isEmpty()) {
sb.append(":\n").append(body);
}
return new RestClientException(sb.toString());
}
public String host() {
return options.getDefaultHost();
}
public int port() {
return options.getDefaultPort();
}
@Override
public BenchmarkRef register(Benchmark benchmark, String prevVersion) {
byte[] bytes;
try {
bytes = Util.serialize(benchmark);
} catch (IOException e) {
throw new RuntimeException(e);
}
return sync(
handler -> {
HttpRequest request = client.request(HttpMethod.POST, "/benchmark");
if (prevVersion != null) {
request.putHeader(HttpHeaders.IF_MATCH.toString(), prevVersion);
}
request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/java-serialized-object")
.sendBuffer(Buffer.buffer(bytes), handler);
}, 0,
response -> {
if (response.statusCode() == 204) {
return new BenchmarkRefImpl(this, benchmark.name());
} else if (response.statusCode() == 409) {
throw new EditConflictException();
} else {
throw unexpected(response);
}
});
}
@Override
public List benchmarks() {
return sync(
handler -> client.request(HttpMethod.GET, "/benchmark").send(handler), 200,
response -> Arrays.asList(Json.decodeValue(response.body(), String[].class)));
}
@Override
public BenchmarkRef benchmark(String name) {
return new BenchmarkRefImpl(this, name);
}
@Override
public List runs(boolean details) {
return sync(
handler -> client.request(HttpMethod.GET, "/run?details=" + details).send(handler), 200,
response -> Arrays.asList(Json.decodeValue(response.body(), io.hyperfoil.controller.model.Run[].class)));
}
@Override
public RunRef run(String id) {
return new RunRefImpl(this, id);
}
@Override
public long ping() {
return sync(handler -> client.request(HttpMethod.GET, "/").send(handler), 200, response -> {
try {
String header = response.getHeader("x-epoch-millis");
return header != null ? Long.parseLong(header) : 0L;
} catch (NumberFormatException e) {
return 0L;
}
});
}
@Override
public Version version() {
return sync(handler -> client.request(HttpMethod.GET, "/version").send(handler), 200,
response -> Json.decodeValue(response.body(), Version.class));
}
@Override
public Collection agents() {
return sync(handler -> client.request(HttpMethod.GET, "/agents").send(handler), 200,
response -> Arrays.asList(Json.decodeValue(response.body(), String[].class)));
}
@Override
public String downloadLog(String node, String logId, long offset, String destinationFile) {
String url = "/log" + (node == null ? "" : "/" + node);
// When there's no more data, content-length won't be present and the body is null
// the etag does not match
CompletableFuture future = new CompletableFuture<>();
vertx.runOnContext(ctx -> {
HttpRequest request = client.request(HttpMethod.GET, url + "?offset=" + offset);
if (logId != null) {
request.putHeader(HttpHeaders.IF_MATCH.toString(), logId);
}
request.send(rsp -> {
if (rsp.failed()) {
future.completeExceptionally(rsp.cause());
return;
}
HttpResponse response = rsp.result();
if (response.statusCode() == 412) {
downloadFullLog(destinationFile, url, future);
return;
} else if (response.statusCode() != 200) {
future.completeExceptionally(unexpected(response));
return;
}
try {
String etag = response.getHeader(HttpHeaders.ETAG.toString());
if (logId == null) {
try {
byte[] bytes;
if (response.body() == null) {
bytes = "".getBytes(StandardCharsets.UTF_8);
} else {
bytes = response.body().getBytes();
}
Files.write(Paths.get(destinationFile), bytes);
} catch (IOException e) {
throw new RestClientException(e);
}
future.complete(etag);
} else if (etag != null && etag.equals(logId)) {
if (response.body() != null) {
// When there's no more data, content-length won't be present and the body is null
try (RandomAccessFile rw = new RandomAccessFile(destinationFile, "rw")) {
rw.seek(offset);
rw.write(response.body().getBytes());
} catch (IOException e) {
throw new RestClientException(e);
}
}
future.complete(etag);
} else {
downloadFullLog(destinationFile, url, future);
}
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
});
return waitFor(future);
}
@Override
public void shutdown(boolean force) {
sync(handler -> client.request(HttpMethod.GET, "/shutdown?force=" + force).send(handler), 200, response -> null);
}
private void downloadFullLog(String destinationFile, String url, CompletableFuture future) {
// the etag does not match
client.request(HttpMethod.GET, url).send(rsp -> {
if (rsp.failed()) {
future.completeExceptionally(rsp.cause());
return;
}
HttpResponse response = rsp.result();
if (response.statusCode() != 200) {
future.completeExceptionally(unexpected(response));
return;
}
try {
Files.write(Paths.get(destinationFile), response.body().getBytes());
future.complete(response.getHeader(HttpHeaders.ETAG.toString()));
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
}
static T waitFor(CompletableFuture future) {
try {
return future.get(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RestClientException(e);
} catch (ExecutionException e) {
if (e.getCause() instanceof RestClientException) {
throw (RestClientException) e.getCause();
}
throw new RestClientException(e.getCause() == null ? e : e.getCause());
} catch (TimeoutException e) {
throw new RestClientException("Request did not complete within 30 seconds.");
}
}
T sync(Consumer>>> invoker, int statusCode, Function, T> f) {
CompletableFuture future = new CompletableFuture<>();
vertx.runOnContext(ctx -> {
invoker.accept(rsp -> {
if (rsp.succeeded()) {
HttpResponse response = rsp.result();
if (statusCode != 0 && response.statusCode() != statusCode) {
future.completeExceptionally(unexpected(response));
return;
}
try {
future.complete(f.apply(response));
} catch (Throwable t) {
future.completeExceptionally(t);
}
} else {
future.completeExceptionally(rsp.cause());
}
});
});
return waitFor(future);
}
@Override
public void close() {
client.close();
vertx.close();
}
public String toString() {
return options.getDefaultHost() + ":" + options.getDefaultPort();
}
}