From f8ee05ca11c86296cc0826999e3074f80d8afbf4 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Sun, 5 Aug 2018 18:35:13 -0400 Subject: [PATCH 1/2] SkinServer updates (#83) SkinServer update: * loadProfileData was unwrapped from Optional. Now it throws an exception. * getPreviewTextures is now a CompletableFuture so it won't lag GuiSkins * arguments for uploadSkin were condensed into a single object * Added a class wrapper for getting different http response types * HttpClient is now a singleton which lives in SkinServer.httpClient --- .../voxelmodpack/hdskins/HDSkinManager.java | 36 +++---- .../hdskins/PreviewTextureManager.java | 9 +- .../hdskins/gui/EntityPlayerModel.java | 13 +-- .../voxelmodpack/hdskins/gui/GuiSkins.java | 6 +- .../hdskins/skins/BethlehemSkinServer.java | 38 +++---- .../hdskins/skins/LegacySkinServer.java | 45 +++++---- .../hdskins/skins/MoreHttpResponses.java | 75 ++++++++++++++ .../voxelmodpack/hdskins/skins/NetClient.java | 65 ++---------- .../hdskins/skins/SkinServer.java | 25 ++--- .../hdskins/skins/SkinUpload.java | 34 +++++++ .../hdskins/skins/ValhallaSkinServer.java | 98 ++++++++----------- 11 files changed, 247 insertions(+), 197 deletions(-) create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinUpload.java diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java index bf36474a..f4b46f84 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java @@ -9,16 +9,11 @@ import com.google.common.collect.HashBiMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.properties.Property; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; -import com.mojang.util.UUIDTypeAdapter; import com.mumfrey.liteloader.core.LiteLoader; import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.voxelmodpack.hdskins.gui.GuiSkins; @@ -39,6 +34,9 @@ import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.resources.SkinManager.SkinAvailableCallback; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.FileUtils; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.logging.log4j.LogManager; import java.awt.Graphics; import java.awt.image.BufferedImage; @@ -53,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -62,13 +61,11 @@ import javax.annotation.Nonnull; public final class HDSkinManager implements IResourceManagerReloadListener { - private static final ResourceLocation LOADING = new ResourceLocation("LOADING"); - private static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) - .create(); + public static final ExecutorService skinUploadExecutor = Executors.newSingleThreadExecutor(); + public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8); + public static final CloseableHttpClient httpClient = HttpClients.createSystem(); - private static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8); - public static final ListeningExecutorService skinUploadExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + private static final ResourceLocation LOADING = new ResourceLocation("LOADING"); public static final HDSkinManager INSTANCE = new HDSkinManager(); @@ -133,7 +130,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null); if (textures != null) { String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8); - MinecraftTexturesPayload texturePayload = GSON.fromJson(json, MinecraftTexturesPayload.class); + MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class); if (texturePayload != null) { // name is optional String name = texturePayload.getProfileName(); @@ -216,10 +213,13 @@ public final class HDSkinManager implements IResourceManagerReloadListener { private Map loadProfileData(GameProfile profile) { Map textures = Maps.newEnumMap(Type.class); for (SkinServer server : skinServers) { - Optional profileData = server.loadProfileData(profile); - profileData.map(MinecraftTexturesPayload::getTextures).ifPresent(it -> it.forEach(textures::putIfAbsent)); - if (textures.size() == Type.values().length) { - break; + try { + server.loadProfileData(profile).getTextures().forEach(textures::putIfAbsent); + if (textures.size() == Type.values().length) { + break; + } + } catch (IOException e) { + LogManager.getLogger().trace(e); } } @@ -263,8 +263,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener { this.enabled = enabled; } - public static PreviewTextureManager getPreviewTextureManager(GameProfile profile) { - return new PreviewTextureManager(INSTANCE.getGatewayServer().getPreviewTextures(profile)); + public static CompletableFuture getPreviewTextureManager(GameProfile profile) { + return INSTANCE.getGatewayServer().getPreviewTextures(profile).thenApply(PreviewTextureManager::new); } public void addClearListener(ISkinCacheClearListener listener) { diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java index 057fd56f..18cf587d 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java @@ -2,7 +2,7 @@ package com.voxelmodpack.hdskins; import com.google.common.collect.Maps; import com.mojang.authlib.minecraft.MinecraftProfileTexture; -import net.minecraft.client.Minecraft; +import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import net.minecraft.client.renderer.IImageBuffer; import net.minecraft.client.resources.SkinManager; import net.minecraft.util.ResourceLocation; @@ -20,8 +20,8 @@ public class PreviewTextureManager { private final Map textures; - PreviewTextureManager(Map textures) { - this.textures = textures; + PreviewTextureManager(MinecraftTexturesPayload payload) { + this.textures = payload.getTextures(); } @Nullable @@ -47,7 +47,8 @@ public class PreviewTextureManager { } } } : null); - Minecraft.getMinecraft().getTextureManager().loadTexture(location, skinTexture); + + TextureLoader.loadTexture(location, skinTexture); return skinTexture; } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java b/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java index c9a05669..6dd0cc3a 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java @@ -8,7 +8,6 @@ import com.voxelmodpack.hdskins.DynamicTextureImage; import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.ImageBufferDownloadHD; import com.voxelmodpack.hdskins.PreviewTexture; -import com.voxelmodpack.hdskins.PreviewTextureManager; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.TextureManager; @@ -25,6 +24,7 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Map; + import javax.imageio.ImageIO; @SuppressWarnings("EntityConstructor") @@ -41,11 +41,11 @@ public class EntityPlayerModel extends EntityLivingBase { EntityEquipmentSlot.MAINHAND, ItemStack.EMPTY )); - private PreviewTexture remoteSkinTexture; + private volatile PreviewTexture remoteSkinTexture; private ResourceLocation remoteSkinResource; protected ResourceLocation localSkinResource; private DynamicTexture localSkinTexture; - private PreviewTexture remoteElytraTexture; + private volatile PreviewTexture remoteElytraTexture; private ResourceLocation remoteElytraResource; private ResourceLocation localElytraResource; private DynamicTexture localElytraTexture; @@ -77,10 +77,11 @@ public class EntityPlayerModel extends EntityLivingBase { this.textureManager.deleteTexture(this.remoteElytraResource); } - PreviewTextureManager ptm = HDSkinManager.getPreviewTextureManager(this.profile); + HDSkinManager.getPreviewTextureManager(this.profile).thenAccept(ptm -> { + this.remoteSkinTexture = ptm.getPreviewTexture(this.remoteSkinResource, Type.SKIN, getBlankSkin(), listener); + this.remoteElytraTexture = ptm.getPreviewTexture(this.remoteElytraResource, Type.ELYTRA, getBlankElytra(), null); + }); - this.remoteSkinTexture = ptm.getPreviewTexture(this.remoteSkinResource, Type.SKIN, getBlankSkin(), listener); - this.remoteElytraTexture = ptm.getPreviewTexture(this.remoteElytraResource, Type.ELYTRA, getBlankElytra(), null); } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java b/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java index 17117415..5d6f8340 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java @@ -12,6 +12,7 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.skins.SkinServer; +import com.voxelmodpack.hdskins.skins.SkinUpload; import com.voxelmodpack.hdskins.skins.SkinUploadResponse; import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG; import net.minecraft.client.Minecraft; @@ -567,7 +568,7 @@ public class GuiSkins extends GuiScreen { this.uploadingSkin = true; this.skinUploadMessage = I18n.format("hdskins.request"); HDSkinManager.INSTANCE.getGatewayServer() - .uploadSkin(session, null, this.textureType, getMetadata()) + .uploadSkin(session, new SkinUpload(this.textureType, null, getMetadata())) .thenAccept(this::onUploadComplete) .exceptionally(this::onFailure); } @@ -577,7 +578,7 @@ public class GuiSkins extends GuiScreen { this.skinUploadMessage = I18n.format("hdskins.upload"); URI path = skinFile == null ? null : skinFile.toURI(); HDSkinManager.INSTANCE.getGatewayServer() - .uploadSkin(session, path, this.textureType, getMetadata()) + .uploadSkin(session, new SkinUpload(this.textureType, path, getMetadata())) .thenAccept(this::onUploadComplete) .exceptionally(this::onFailure); } @@ -591,7 +592,6 @@ public class GuiSkins extends GuiScreen { this.btnUpload.enabled = true; } - private Void onFailure(Throwable t) { t = Throwables.getRootCause(t); LogManager.getLogger().warn("Upload failed", t); diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/BethlehemSkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/BethlehemSkinServer.java index cd98f9f2..39b5c3fd 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/BethlehemSkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/BethlehemSkinServer.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap.Builder; import com.google.gson.JsonObject; import com.google.gson.annotations.Expose; import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.util.UUIDTypeAdapter; @@ -16,9 +17,10 @@ import java.io.IOException; import java.net.URI; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; + @ServerType("bethlehem") public class BethlehemSkinServer implements SkinServer { @@ -32,24 +34,24 @@ public class BethlehemSkinServer implements SkinServer { } @Override - public Optional loadProfileData(GameProfile profile) { - NetClient client = new NetClient("GET", getPath(profile)); + public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException { + try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) { - String json = client.getResponseText(); + JsonObject s = response.json(JsonObject.class); - JsonObject s = gson.fromJson(json, JsonObject.class); - - if (s.has("success") && s.get("success").getAsBoolean()) { - s = s.get("data").getAsJsonObject(); - - return Optional.ofNullable(gson.fromJson(s, MinecraftTexturesPayload.class)); + if (s.has("success") && s.get("success").getAsBoolean()) { + s = s.get("data").getAsJsonObject(); + return gson.fromJson(s, MinecraftTexturesPayload.class); + } + throw new IOException(s.get("error").getAsString()); } - - return Optional.empty(); } @Override - public CompletableFuture uploadSkin(Session session, URI image, Type type, Map metadata) { + public CompletableFuture uploadSkin(Session session, SkinUpload skin) { + URI image = skin.getImage(); + Map metadata = skin.getMetadata(); + MinecraftProfileTexture.Type type = skin.getType(); return CallableFutures.asyncFailableFuture(() -> { SkinServer.verifyServerConnection(session, SERVER_ID); @@ -59,15 +61,17 @@ public class BethlehemSkinServer implements SkinServer { client.putFile(type.toString().toLowerCase(Locale.US), "image/png", image); } - if (!client.send()) { - throw new IOException(client.getResponseText()); + try (MoreHttpResponses response = client.send()) { + if (!response.ok()) { + throw new IOException(response.text()); + } + return new SkinUploadResponse(response.text()); } - return new SkinUploadResponse(client.getResponseText()); }, HDSkinManager.skinUploadExecutor); } - protected Map createHeaders(Session session, Type type, URI image, Map metadata) { + protected Map createHeaders(Session session, Type type, @Nullable URI image, Map metadata) { Builder builder = ImmutableMap.builder() .put("accessToken", session.getToken()) .put("user", session.getUsername()) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java index 418d0fd3..ef177030 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java @@ -18,11 +18,9 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; -import java.util.Collections; import java.util.EnumMap; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; @@ -45,30 +43,25 @@ public class LegacySkinServer implements SkinServer { } @Override - public Map getPreviewTextures(GameProfile profile) { + public CompletableFuture getPreviewTextures(GameProfile profile) { if (Strings.isNullOrEmpty(this.gateway)) { - return Collections.emptyMap(); + return CallableFutures.failedFuture(gatewayUnsupported()); } Map map = new EnumMap<>(MinecraftProfileTexture.Type.class); for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) { map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null)); } - return map; + return CompletableFuture.completedFuture(TexturesPayloadBuilder.createTexturesPayload(profile, map)); } @Override - public Optional loadProfileData(GameProfile profile) { + public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException { ImmutableMap.Builder builder = ImmutableMap.builder(); for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) { String url = getPath(this.address, type, profile); try { - HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); - if (urlConnection.getResponseCode() / 100 != 2) { - throw new IOException("Bad response code: " + urlConnection.getResponseCode()); - } - builder.put(type, new MinecraftProfileTexture(url, null)); - logger.debug("Found skin for {} at {}", profile.getName(), url); + builder.put(type, loadProfileTexture(profile, url)); } catch (IOException e) { logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e); } @@ -76,21 +69,32 @@ public class LegacySkinServer implements SkinServer { Map map = builder.build(); if (map.isEmpty()) { - logger.debug("No textures found for {} at {}", profile, this.address); - return Optional.empty(); + throw new IOException(String.format("No textures found for %s at %s", profile, this.address)); } + return TexturesPayloadBuilder.createTexturesPayload(profile, map); + } - return Optional.of(TexturesPayloadBuilder.createTexturesPayload(profile, map)); + private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException { + HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); + if (urlConnection.getResponseCode() / 100 != 2) { + throw new IOException("Bad response code: " + urlConnection.getResponseCode() + ". URL: " + url); + } + logger.debug("Found skin for {} at {}", profile.getName(), url); + return new MinecraftProfileTexture(url, null); } @SuppressWarnings("deprecation") @Override - public CompletableFuture uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map metadata) { + public CompletableFuture uploadSkin(Session session, SkinUpload skin) { if (Strings.isNullOrEmpty(this.gateway)) { - return CallableFutures.failedFuture(new NullPointerException("gateway url is blank")); + return CallableFutures.failedFuture(gatewayUnsupported()); } return CallableFutures.asyncFailableFuture(() -> { + URI image = skin.getImage(); + MinecraftProfileTexture.Type type = skin.getType(); + Map metadata = skin.getMetadata(); + SkinServer.verifyServerConnection(session, SERVER_ID); String model = metadata.getOrDefault("model", "default"); Map data = image == null ? getClearData(session, type) : getUploadData(session, type, model, image); @@ -99,13 +103,18 @@ public class LegacySkinServer implements SkinServer { if (response.startsWith("ERROR: ")) { response = response.substring(7); } - if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) + if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) { throw new IOException(response); + } return new SkinUploadResponse(response); }, HDSkinManager.skinUploadExecutor); } + private UnsupportedOperationException gatewayUnsupported() { + return new UnsupportedOperationException("Server does not have a gateway."); + } + private static Map getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) { return ImmutableMap.of( "user", session.getUsername(), diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java new file mode 100644 index 00000000..3b7be207 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java @@ -0,0 +1,75 @@ +package com.voxelmodpack.hdskins.skins; + +import com.google.common.io.CharStreams; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +/** + * Utility class for getting different response types from a http response. + */ +@FunctionalInterface +public interface MoreHttpResponses extends AutoCloseable { + + CloseableHttpResponse getResponse(); + + default boolean ok() { + return getResponseCode() == HttpStatus.SC_OK; + } + + default int getResponseCode() { + return getResponse().getStatusLine().getStatusCode(); + } + + default InputStream getInputStream() throws IOException { + return getResponse().getEntity().getContent(); + } + + default BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)); + } + + default String text() throws IOException { + try (BufferedReader reader = getReader()) { + return CharStreams.toString(reader); + } + } + + default Stream lines() throws IOException { + try (BufferedReader reader = getReader()) { + return reader.lines(); + } + } + + default T json(Class type) throws IOException { + try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) { + return SkinServer.gson.fromJson(reader, type); + } + } + + default T json(Type type) throws IOException { + try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) { + return SkinServer.gson.fromJson(reader, type); + } + } + + @Override + default void close() throws IOException { + this.getResponse().close(); + } + + static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException { + CloseableHttpResponse response = client.execute(request); + return () -> response; + } +} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/NetClient.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/NetClient.java index 0e91c50d..cfce636d 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/NetClient.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/NetClient.java @@ -1,21 +1,16 @@ package com.voxelmodpack.hdskins.skins; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URI; -import java.util.Map; - -import org.apache.commons.io.IOUtils; +import com.voxelmodpack.hdskins.HDSkinManager; import org.apache.http.HttpEntity; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.HttpClients; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Map; /** * Ew. Why so many builders? >.< @@ -26,8 +21,6 @@ public class NetClient { private Map headers; - private CloseableHttpResponse response = null; - public NetClient(String method, String uri) { rqBuilder = RequestBuilder.create(method).setUri(uri); } @@ -47,56 +40,14 @@ public class NetClient { return this; } - public boolean send() { + public MoreHttpResponses send() throws IOException { HttpUriRequest request = rqBuilder.build(); for (Map.Entry parameter : headers.entrySet()) { request.addHeader(parameter.getKey(), parameter.getValue().toString()); } - try { - response = HttpClients.createSystem().execute(request); - - return getResponseCode() == HttpStatus.SC_OK; - } catch (IOException e) { } - - return false; + return MoreHttpResponses.execute(HDSkinManager.httpClient, request); } - public int getResponseCode() { - if (response == null) { - send(); - } - - return response.getStatusLine().getStatusCode(); - } - - public String getResponseText() { - if (response == null) { - if (!send()) { - return ""; - } - } - - BufferedReader reader = null; - - try { - reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); - - StringBuilder builder = new StringBuilder(); - - int ch; - while ((ch = reader.read()) != -1) { - builder.append((char)ch); - } - - return builder.toString(); - } catch (IOException e) { - - } finally { - IOUtils.closeQuietly(reader); - } - - return ""; - } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java index dce9d855..48f55b51 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java @@ -5,28 +5,22 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.mojang.authlib.GameProfile; import com.mojang.authlib.exceptions.AuthenticationException; -import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.util.UUIDTypeAdapter; import com.mumfrey.liteloader.modconfig.Exposable; - +import com.voxelmodpack.hdskins.HDSkinManager; import net.minecraft.client.Minecraft; import net.minecraft.util.Session; -import java.net.URI; -import java.util.Collections; +import java.io.IOException; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import javax.annotation.Nullable; - public interface SkinServer extends Exposable { - static final Gson gson = new GsonBuilder() + Gson gson = new GsonBuilder() .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) .create(); @@ -34,16 +28,15 @@ public interface SkinServer extends Exposable { "http://skins.voxelmodpack.com", "http://skinmanager.voxelmodpack.com")); - Optional loadProfileData(GameProfile profile); + MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException; - default Map getPreviewTextures(GameProfile profile) { - return loadProfileData(profile).map(MinecraftTexturesPayload::getTextures).orElse(Collections.emptyMap()); + CompletableFuture uploadSkin(Session session, SkinUpload upload); + + default CompletableFuture getPreviewTextures(GameProfile profile) { + return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor); } - CompletableFuture uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map metadata); - - - public static void verifyServerConnection(Session session, String serverId) throws AuthenticationException { + static void verifyServerConnection(Session session, String serverId) throws AuthenticationException { MinecraftSessionService service = Minecraft.getMinecraft().getSessionService(); service.joinServer(session.getProfile(), session.getToken(), serverId); } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinUpload.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinUpload.java new file mode 100644 index 00000000..dbcd20bd --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinUpload.java @@ -0,0 +1,34 @@ +package com.voxelmodpack.hdskins.skins; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; + +import java.net.URI; +import java.util.Map; + +import javax.annotation.Nullable; + +public class SkinUpload { + + private URI image; + private Map metadata; + private MinecraftProfileTexture.Type type; + + public SkinUpload(MinecraftProfileTexture.Type type, @Nullable URI image, Map metadata) { + this.image = image; + this.metadata = metadata; + this.type = type; + } + + @Nullable + public URI getImage() { + return image; + } + + public Map getMetadata() { + return metadata; + } + + public MinecraftProfileTexture.Type getType() { + return type; + } +} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/ValhallaSkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/ValhallaSkinServer.java index dd8f309d..c8af92dd 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/ValhallaSkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/ValhallaSkinServer.java @@ -13,28 +13,19 @@ import net.minecraft.client.Minecraft; import net.minecraft.util.Session; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; import java.net.URI; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -53,83 +44,81 @@ public class ValhallaSkinServer implements SkinServer { } @Override - public Optional loadProfileData(GameProfile profile) { + public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException { - try (CloseableHttpClient client = HttpClients.createSystem(); - CloseableHttpResponse response = client.execute(new HttpGet(getTexturesURI(profile)))) { + try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpGet(getTexturesURI(profile)))) { - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - - return Optional.of(readJson(response, MinecraftTexturesPayload.class)); + if (response.ok()) { + return readJson(response, MinecraftTexturesPayload.class); } - } catch (IOException e) { - e.printStackTrace(); + throw new IOException("Server sent non-ok response code: " + response.getResponseCode()); } - return Optional.empty(); } @Override - public CompletableFuture uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map metadata) { + public CompletableFuture uploadSkin(Session session, SkinUpload skin) { + URI image = skin.getImage(); + Map metadata = skin.getMetadata(); + MinecraftProfileTexture.Type type = skin.getType(); + return CallableFutures.asyncFailableFuture(() -> { - try (CloseableHttpClient client = HttpClients.createSystem()) { - authorize(client, session); + authorize(session); try { - return upload(client, session, image, type, metadata); + return upload(session, image, type, metadata); } catch (IOException e) { if (e.getMessage().equals("Authorization failed")) { accessToken = null; - authorize(client, session); - return upload(client, session, image, type, metadata); + authorize(session); + return upload(session, image, type, metadata); } throw e; } - } }, HDSkinManager.skinUploadExecutor); } - private SkinUploadResponse upload(CloseableHttpClient client, Session session, @Nullable URI image, + private SkinUploadResponse upload(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map metadata) throws IOException { GameProfile profile = session.getProfile(); if (image == null) { - return resetSkin(client, profile, type); + return resetSkin(profile, type); } switch (image.getScheme()) { case "file": - return uploadFile(client, new File(image), profile, type, metadata); + return uploadFile(new File(image), profile, type, metadata); case "http": case "https": - return uploadUrl(client, image, profile, type, metadata); + return uploadUrl(image, profile, type, metadata); default: throw new IOException("Unsupported URI scheme: " + image.getScheme()); } } - private SkinUploadResponse resetSkin(CloseableHttpClient client, GameProfile profile, MinecraftProfileTexture.Type type) throws IOException { - return upload(client, RequestBuilder.delete() + private SkinUploadResponse resetSkin(GameProfile profile, MinecraftProfileTexture.Type type) throws IOException { + return upload(RequestBuilder.delete() .setUri(buildUserTextureUri(profile, type)) .addHeader(HttpHeaders.AUTHORIZATION, this.accessToken) .build()); } - private SkinUploadResponse uploadFile(CloseableHttpClient client, File file, GameProfile profile, MinecraftProfileTexture.Type type, Map metadata) throws IOException { + private SkinUploadResponse uploadFile(File file, GameProfile profile, MinecraftProfileTexture.Type type, Map metadata) throws IOException { MultipartEntityBuilder b = MultipartEntityBuilder.create(); b.addBinaryBody("file", file, ContentType.create("image/png"), file.getName()); metadata.forEach(b::addTextBody); - return upload(client, RequestBuilder.put() + return upload(RequestBuilder.put() .setUri(buildUserTextureUri(profile, type)) .addHeader(HttpHeaders.AUTHORIZATION, this.accessToken) .setEntity(b.build()) .build()); } - private SkinUploadResponse uploadUrl(CloseableHttpClient client, URI uri, GameProfile profile, MinecraftProfileTexture.Type type, Map metadata) throws IOException { + private SkinUploadResponse uploadUrl(URI uri, GameProfile profile, MinecraftProfileTexture.Type type, Map metadata) throws IOException { - return upload(client, RequestBuilder.post() + return upload(RequestBuilder.post() .setUri(buildUserTextureUri(profile, type)) .addHeader(HttpHeaders.AUTHORIZATION, this.accessToken) .addParameter("file", uri.toString()) @@ -139,20 +128,19 @@ public class ValhallaSkinServer implements SkinServer { .build()); } - private SkinUploadResponse upload(CloseableHttpClient client, HttpUriRequest request) throws IOException { - try (CloseableHttpResponse response = client.execute(request)) { + private SkinUploadResponse upload(HttpUriRequest request) throws IOException { + try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request)) { return readJson(response, SkinUploadResponse.class); } } - - private void authorize(CloseableHttpClient client, Session session) throws IOException, AuthenticationException { + private void authorize(Session session) throws IOException, AuthenticationException { if (this.accessToken != null) { return; } GameProfile profile = session.getProfile(); String token = session.getToken(); - AuthHandshake handshake = authHandshake(client, profile.getName()); + AuthHandshake handshake = authHandshake(profile.getName()); if (handshake.offline) { return; @@ -161,33 +149,27 @@ public class ValhallaSkinServer implements SkinServer { // join the session server Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId); - AuthResponse response = authResponse(client, profile.getName(), handshake.verifyToken); + AuthResponse response = authResponse(profile.getName(), handshake.verifyToken); if (!response.userId.equals(profile.getId())) { throw new IOException("UUID mismatch!"); // probably won't ever throw } this.accessToken = response.accessToken; } - private T readJson(HttpResponse resp, Class cl) throws IOException { - String type = resp.getEntity().getContentType().getValue(); + private T readJson(MoreHttpResponses resp, Class cl) throws IOException { + String type = resp.getResponse().getEntity().getContentType().getValue(); if (!"application/json".equals(type)) { - try { - throw new IOException("Server returned a non-json response!"); - } finally { - EntityUtils.consumeQuietly(resp.getEntity()); - } + throw new IOException("Server returned a non-json response!"); } - try (Reader r = new InputStreamReader(resp.getEntity().getContent())) { - if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - // TODO specific error handling - throw new IOException(gson.fromJson(r, JsonObject.class).get("message").getAsString()); - } - return gson.fromJson(r, cl); + if (resp.ok()) { + return resp.json(cl); } + throw new IOException(resp.json(JsonObject.class).get("message").getAsString()); + } - private AuthHandshake authHandshake(CloseableHttpClient client, String name) throws IOException { - try (CloseableHttpResponse resp = client.execute(RequestBuilder.post() + private AuthHandshake authHandshake(String name) throws IOException { + try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post() .setUri(getHandshakeURI()) .addParameter("name", name) .build())) { @@ -195,8 +177,8 @@ public class ValhallaSkinServer implements SkinServer { } } - private AuthResponse authResponse(CloseableHttpClient client, String name, long verifyToken) throws IOException { - try (CloseableHttpResponse resp = client.execute(RequestBuilder.post() + private AuthResponse authResponse(String name, long verifyToken) throws IOException { + try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post() .setUri(getResponseURI()) .addParameter("name", name) .addParameter("verifyToken", String.valueOf(verifyToken)) From 6f1837a46ffa961b8ab18a0bb8abbcd48ca51b88 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 Aug 2018 20:15:55 -0400 Subject: [PATCH 2/2] MoreHttpResponses : use getReader() more --- .../com/voxelmodpack/hdskins/skins/MoreHttpResponses.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java index 3b7be207..52a56b86 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/MoreHttpResponses.java @@ -10,7 +10,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.stream.Stream; @@ -52,13 +51,13 @@ public interface MoreHttpResponses extends AutoCloseable { } default T json(Class type) throws IOException { - try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) { + try (BufferedReader reader = getReader()) { return SkinServer.gson.fromJson(reader, type); } } default T json(Type type) throws IOException { - try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) { + try (BufferedReader reader = getReader()) { return SkinServer.gson.fromJson(reader, type); } }