Refactor AuthlibInjectorServer

This commit is contained in:
yushijinhun
2018-11-23 21:34:54 +08:00
parent 1b40916046
commit dcd0a42705
4 changed files with 125 additions and 50 deletions

View File

@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -235,16 +234,7 @@ public final class Accounts {
.filter(server -> url.equals(server.getUrl())) .filter(server -> url.equals(server.getUrl()))
.findFirst() .findFirst()
.orElseGet(() -> { .orElseGet(() -> {
// this usually happens when migrating data from an older version AuthlibInjectorServer server = new AuthlibInjectorServer(url);
AuthlibInjectorServer server;
try {
server = AuthlibInjectorServer.fetchServerInfo(url);
LOG.info("Migrated authlib injector server " + server);
} catch (IOException e) {
server = new AuthlibInjectorServer(url, url);
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
}
config().getAuthlibInjectorServers().add(server); config().getAuthlibInjectorServers().add(server);
return server; return server;
}); });

View File

@@ -137,7 +137,7 @@ public final class Config implements Cloneable, Observable {
private IntegerProperty logLines = new SimpleIntegerProperty(100); private IntegerProperty logLines = new SimpleIntegerProperty(100);
@SerializedName("authlibInjectorServers") @SerializedName("authlibInjectorServers")
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(); private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server });
@SerializedName("updateChannel") @SerializedName("updateChannel")
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE); private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);

View File

@@ -27,14 +27,12 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing; import static java.nio.charset.StandardCharsets.UTF_8;
public class AuthlibInjectorAccount extends YggdrasilAccount { public class AuthlibInjectorAccount extends YggdrasilAccount {
private AuthlibInjectorServer server; private AuthlibInjectorServer server;
@@ -58,9 +56,9 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
} }
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException { private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
CompletableFuture<byte[]> prefetchedMetaTask = CompletableFuture.supplyAsync(() -> { CompletableFuture<String> prefetchedMetaTask = CompletableFuture.supplyAsync(() -> {
try (InputStream in = new URL(server.getUrl()).openStream()) { try {
return readFullyWithoutClosing(in); return server.fetchMetadataResponse();
} catch (IOException e) { } catch (IOException e) {
throw new CompletionException(new ServerDisconnectException(e)); throw new CompletionException(new ServerDisconnectException(e));
} }
@@ -75,7 +73,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
}); });
AuthInfo auth = loginAction.get(); AuthInfo auth = loginAction.get();
byte[] prefetchedMeta; String prefetchedMeta;
AuthlibInjectorArtifactInfo artifact; AuthlibInjectorArtifactInfo artifact;
try { try {
@@ -95,7 +93,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
return auth.withArguments(new Arguments().addJVMArguments( return auth.withArguments(new Arguments().addJVMArguments(
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(), "-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
"-Dauthlibinjector.side=client", "-Dauthlibinjector.side=client",
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta))); "-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8))));
} }
@Override @Override

View File

@@ -20,25 +20,40 @@ package org.jackhuang.hmcl.auth.authlibinjector;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyAsByteArray;
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing; import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.lang.reflect.Type;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jetbrains.annotations.Nullable;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.JsonAdapter;
public class AuthlibInjectorServer { import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
@JsonAdapter(AuthlibInjectorServer.Deserializer.class)
public class AuthlibInjectorServer implements Observable {
private static final int MAX_REDIRECTS = 5; private static final int MAX_REDIRECTS = 5;
private static final Gson GSON = new GsonBuilder().create();
public static AuthlibInjectorServer locateServer(String url) throws IOException { public static AuthlibInjectorServer locateServer(String url) throws IOException {
url = parseInputUrl(url); url = parseInputUrl(url);
@@ -60,8 +75,11 @@ public class AuthlibInjectorServer {
} }
} }
try { try {
return parseResponse(url, readFullyWithoutClosing(conn.getInputStream())); AuthlibInjectorServer server = new AuthlibInjectorServer(url);
server.refreshMetadata(readFullyWithoutClosing(conn.getInputStream()));
return server;
} finally { } finally {
conn.disconnect(); conn.disconnect();
} }
@@ -107,45 +125,82 @@ public class AuthlibInjectorServer {
} }
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException { public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
try (InputStream in = new URL(url).openStream()) { AuthlibInjectorServer server = new AuthlibInjectorServer(url);
return parseResponse(url, readFullyWithoutClosing(in)); server.refreshMetadata();
} return server;
}
private static AuthlibInjectorServer parseResponse(String url, byte[] rawResponse) throws IOException {
try {
JsonObject response = JsonUtils.fromNonNullJson(new String(rawResponse, UTF_8), JsonObject.class);
String name = extractServerName(response).orElse(url);
return new AuthlibInjectorServer(url, name);
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
}
private static Optional<String> extractServerName(JsonObject response){
return tryCast(response.get("meta"), JsonObject.class)
.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString));
} }
private String url; private String url;
private String name; @Nullable
private String metadataResponse;
private long metadataTimestamp;
public AuthlibInjectorServer(String url, String name) { @Nullable
private transient String name;
private transient boolean metadataRefreshed;
private transient ObservableHelper helper = new ObservableHelper(this);
public AuthlibInjectorServer(String url) {
this.url = url; this.url = url;
this.name = name;
} }
public String getUrl() { public String getUrl() {
return url; return url;
} }
public String getName() { public Optional<String> getMetadataResponse() {
return name; return Optional.ofNullable(metadataResponse);
} }
@Override public long getMetadataTimestamp() {
public String toString() { return metadataTimestamp;
return url + " (" + name + ")"; }
public String getName() {
return Optional.ofNullable(name)
.orElse(url);
}
public String fetchMetadataResponse() throws IOException {
if (metadataResponse == null || !metadataRefreshed) {
refreshMetadata();
}
return getMetadataResponse().get();
}
public void refreshMetadata() throws IOException {
refreshMetadata(readFullyAsByteArray(new URL(url).openStream()));
}
private void refreshMetadata(byte[] rawResponse) throws IOException {
long timestamp = System.currentTimeMillis();
try {
setMetadataResponse(new String(rawResponse, UTF_8), timestamp);
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
metadataRefreshed = true;
LOG.info("authlib-injector server metadata refreshed: " + url);
Platform.runLater(helper::invalidate);
}
private void setMetadataResponse(String metadataResponse, long metadataTimestamp) throws JsonParseException {
JsonObject response = GSON.fromJson(metadataResponse, JsonObject.class);
if (response == null) {
throw new JsonParseException("Metadata response is empty");
}
synchronized (this) {
this.metadataResponse = metadataResponse;
this.metadataTimestamp = metadataTimestamp;
Optional<JsonObject> metaObject = tryCast(response.get("meta"), JsonObject.class);
this.name = metaObject.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString))
.orElse(null);
}
} }
@Override @Override
@@ -162,4 +217,36 @@ public class AuthlibInjectorServer {
AuthlibInjectorServer another = (AuthlibInjectorServer) obj; AuthlibInjectorServer another = (AuthlibInjectorServer) obj;
return this.url.equals(another.url); return this.url.equals(another.url);
} }
@Override
public void addListener(InvalidationListener listener) {
helper.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper.removeListener(listener);
}
public static class Deserializer implements JsonDeserializer<AuthlibInjectorServer> {
@Override
public AuthlibInjectorServer deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {
JsonObject jsonObj = json.getAsJsonObject();
AuthlibInjectorServer instance = new AuthlibInjectorServer(jsonObj.get("url").getAsString());
if (jsonObj.has("name")) {
instance.name = jsonObj.get("name").getAsString();
}
if (jsonObj.has("metadataResponse")) {
try {
instance.setMetadataResponse(jsonObj.get("metadataResponse").getAsString(), jsonObj.get("metadataTimestamp").getAsLong());
} catch (JsonParseException e) {
LOG.log(Level.WARNING, "Ignoring malformed metadata response cache: " + jsonObj.get("metadataResponse"), e);
}
}
return instance;
}
}
} }