package pro.gravit.launchserver.helper; import com.google.gson.JsonElement; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.Launcher; import pro.gravit.launcher.request.RequestException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Type; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; import java.util.function.Function; public final class HttpHelper { private static transient final Logger logger = LogManager.getLogger(); private HttpHelper() { throw new UnsupportedOperationException(); } public static <T, E> HttpOptional<T, E> send(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException { try { var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); return handler.apply(response); } catch (InterruptedException e) { throw new IOException(e); } } public static <T, E> CompletableFuture<HttpOptional<T, E>> sendAsync(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException { return client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply(handler::apply); } public static <T> HttpResponse.BodyHandler<T> ofJsonResult(Class<T> type) { return ofJsonResult((Type) type); } public static <T> HttpResponse.BodyHandler<T> ofJsonResult(Type type) { return new JsonBodyHandler<>(HttpResponse.BodyHandlers.ofInputStream(), (input) -> { try (Reader reader = new InputStreamReader(input)) { return Launcher.gsonManager.gson.fromJson(reader, type); } catch (IOException e) { throw new RuntimeException(e); } }); } public static <T> HttpRequest.BodyPublisher jsonBodyPublisher(T obj) { return HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(obj)); } public interface HttpErrorHandler<T, E> { HttpOptional<T, E> apply(HttpResponse<InputStream> response); } public interface HttpJsonErrorHandler<T, E> extends HttpErrorHandler<T, E> { HttpOptional<T, E> applyJson(JsonElement response, int statusCode); default HttpOptional<T, E> apply(HttpResponse<InputStream> response) { try (Reader reader = new InputStreamReader(response.body())) { var element = Launcher.gsonManager.gson.fromJson(reader, JsonElement.class); return applyJson(element, response.statusCode()); } catch (IOException e) { throw new RuntimeException(e); } } } public static class HttpOptional<T, E> { protected final T result; protected final E error; protected final int statusCode; public HttpOptional(T result, E error, int statusCode) { this.result = result; this.error = error; this.statusCode = statusCode; } public T result() { return result; } public E error() { return error; } public int statusCode() { return statusCode; } public boolean isSuccessful() { return statusCode >= 200 && statusCode < 300; } public T getOrThrow() throws RequestException { if (isSuccessful()) { return result; } else { throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString()); } } } public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> { private final Class<T> type; public BasicJsonHttpErrorHandler(Class<T> type) { this.type = type; } @Override public HttpOptional<T, Void> applyJson(JsonElement response, int statusCode) { return new HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode); } } private static class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T> { private final HttpResponse.BodyHandler<InputStream> delegate; private final Function<InputStream, T> func; private JsonBodyHandler(HttpResponse.BodyHandler<InputStream> delegate, Function<InputStream, T> func) { this.delegate = delegate; this.func = func; } @Override public HttpResponse.BodySubscriber<T> apply(HttpResponse.ResponseInfo responseInfo) { return new JsonBodySubscriber<>(delegate.apply(responseInfo), func); } } private static class JsonBodySubscriber<T> implements HttpResponse.BodySubscriber<T> { private final HttpResponse.BodySubscriber<InputStream> delegate; private final Function<InputStream, T> func; private JsonBodySubscriber(HttpResponse.BodySubscriber<InputStream> delegate, Function<InputStream, T> func) { this.delegate = delegate; this.func = func; } @Override public CompletionStage<T> getBody() { return delegate.getBody().thenApply(func); } @Override public void onSubscribe(Flow.Subscription subscription) { delegate.onSubscribe(subscription); } @Override public void onNext(List<ByteBuffer> item) { delegate.onNext(item); } @Override public void onError(Throwable throwable) { delegate.onError(throwable); } @Override public void onComplete() { delegate.onComplete(); } } }