From b8dc586bee2c74592236ead18bf428fcfe4c957a Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Mon, 28 Jul 2014 11:42:11 +0100 Subject: [PATCH] Add an option for a global cache for any request on Mojang's api to handle plugins that don't use UserCache (or any cache) diff --git a/src/main/java/org/spigotmc/CachedMojangAPIConnection.java b/src/main/java/org/spigotmc/CachedMojangAPIConnection.java new file mode 100644 index 0000000..4eed6fd --- /dev/null +++ b/src/main/java/org/spigotmc/CachedMojangAPIConnection.java @@ -0,0 +1,145 @@ +package org.spigotmc; + +import com.google.common.base.Charsets; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +public class CachedMojangAPIConnection extends HttpURLConnection +{ + private final CachedStreamHandlerFactory.CachedStreamHandler cachedStreamHandler; + private final Proxy proxy; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private ByteArrayInputStream inputStream; + private InputStream errorStream; + private boolean outClosed = false; + + private static final Cache cache = CacheBuilder.newBuilder() + .maximumSize( 10000 ) + .expireAfterAccess( 1, TimeUnit.HOURS ) + .build(); + + public CachedMojangAPIConnection(CachedStreamHandlerFactory.CachedStreamHandler cachedStreamHandler, URL url, Proxy proxy) + { + super( url ); + this.cachedStreamHandler = cachedStreamHandler; + this.proxy = proxy; + } + + @Override + public void disconnect() + { + + } + + @Override + public boolean usingProxy() + { + return proxy != null; + } + + @Override + public void connect() throws IOException + { + + } + + @Override + public InputStream getInputStream() throws IOException + { + if ( inputStream == null ) + { + outClosed = true; + JsonArray users = new JsonParser().parse( new String( outputStream.toByteArray(), Charsets.UTF_8 ) ).getAsJsonArray(); + StringBuilder reply = new StringBuilder( "[" ); + StringBuilder missingUsers = new StringBuilder( "[" ); + for ( JsonElement user : users ) + { + String username = user.getAsString().toLowerCase(); + String info = cache.getIfPresent( username ); + if ( info != null ) + { + reply.append( info ).append( "," ); + } else + { + missingUsers + .append( "\"" ) + .append( username ) + .append( "\"" ) + .append( "," ); + } + } + if ( missingUsers.length() > 1 ) + { + missingUsers.deleteCharAt( missingUsers.length() - 1 ).append( "]" ); + } + if ( missingUsers.length() > 2 ) + { + HttpURLConnection connection; + if ( proxy == null ) + { + connection = (HttpURLConnection) cachedStreamHandler.getDefaultConnection( url ); + } else + { + connection = (HttpURLConnection) cachedStreamHandler.getDefaultConnection( url, proxy ); + } + connection.setRequestMethod( "POST" ); + connection.setRequestProperty( "Content-Type", "application/json" ); + connection.setDoInput( true ); + connection.setDoOutput( true ); + OutputStream out = connection.getOutputStream(); + out.write( missingUsers.toString().getBytes( Charsets.UTF_8 ) ); + out.flush(); + out.close(); + JsonArray newUsers = new JsonParser().parse( new InputStreamReader( connection.getInputStream(), Charsets.UTF_8 ) ).getAsJsonArray(); + for ( JsonElement user : newUsers ) + { + JsonObject u = user.getAsJsonObject(); + cache.put( u.get( "name" ).getAsString(), u.toString() ); + reply.append( u.toString() ).append( "," ); + } + responseCode = connection.getResponseCode(); + errorStream = connection.getErrorStream(); + } else + { + responseCode = HTTP_OK; + } + if ( reply.length() > 1 ) + { + reply.deleteCharAt( reply.length() - 1 ); + } + inputStream = new ByteArrayInputStream( reply.append( "]" ).toString().getBytes( Charsets.UTF_8 ) ); + } + return inputStream; + } + + @Override + public InputStream getErrorStream() + { + return errorStream; + } + + @Override + public OutputStream getOutputStream() throws IOException + { + if ( outClosed ) + { + throw new RuntimeException( "Write after send" ); + } + return outputStream; + } +} diff --git a/src/main/java/org/spigotmc/CachedStreamHandlerFactory.java b/src/main/java/org/spigotmc/CachedStreamHandlerFactory.java new file mode 100644 index 0000000..b9a8736 --- /dev/null +++ b/src/main/java/org/spigotmc/CachedStreamHandlerFactory.java @@ -0,0 +1,117 @@ +package org.spigotmc; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +public class CachedStreamHandlerFactory implements URLStreamHandlerFactory +{ + public static boolean isSet = false; + + @Override + public URLStreamHandler createURLStreamHandler(String protocol) + { + if ( protocol.equals( "http" ) || protocol.equals( "https" ) ) + { + return new CachedStreamHandler( protocol ); + } + return null; + } + + public class CachedStreamHandler extends URLStreamHandler + { + private final String protocol; + private final URLStreamHandler handler; + private final Method openCon; + private final Method openConProxy; + + public CachedStreamHandler(String protocol) + { + this.protocol = protocol; + if ( protocol.equals( "http" ) ) + { + handler = new sun.net.www.protocol.http.Handler(); + } else + { + handler = new sun.net.www.protocol.https.Handler(); + } + try + { + openCon = handler.getClass().getDeclaredMethod( "openConnection", URL.class ); + openCon.setAccessible( true ); + openConProxy = handler.getClass().getDeclaredMethod( "openConnection", URL.class, Proxy.class ); + openConProxy.setAccessible( true ); + } catch ( NoSuchMethodException e ) + { + throw new RuntimeException( e ); + } + } + + @Override + protected URLConnection openConnection(URL u) throws IOException + { + if ( u.getHost().equals( "api.mojang.com" ) + || u.getPath().startsWith( "/profiles/minecraft" ) ) + { + return cachedConnection( u ); + } + return getDefaultConnection( u ); + } + + @Override + protected URLConnection openConnection(URL u, Proxy p) throws IOException + { + if ( u.getHost().equals( "api.mojang.com" ) + || u.getPath().startsWith( "/profiles/minecraft" ) ) + { + return cachedConnection( u, p ); + } + return getDefaultConnection( u, p ); + } + + private URLConnection cachedConnection(URL u) + { + return cachedConnection( u, null ); + } + + private URLConnection cachedConnection(URL u, Proxy p) + { + return new CachedMojangAPIConnection( this, u, p ); + } + + public URLConnection getDefaultConnection(URL u) + { + try + { + return (URLConnection) openCon.invoke( handler, u ); + } catch ( IllegalAccessException e ) + { + e.printStackTrace(); + } catch ( InvocationTargetException e ) + { + e.printStackTrace(); + } + return null; + } + + public URLConnection getDefaultConnection(URL u, Proxy p) + { + try + { + return (URLConnection) openConProxy.invoke( handler, u, p ); + } catch ( IllegalAccessException e ) + { + e.printStackTrace(); + } catch ( InvocationTargetException e ) + { + e.printStackTrace(); + } + return null; + } + } +} diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java index 4ecb3cd..d3abf1f 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -360,4 +361,15 @@ public class SpigotConfig attackDamage = getDouble( "settings.attribute.attackDamage.max", attackDamage ); ( (AttributeRanged) GenericAttributes.e ).b = attackDamage; } + + private static void globalAPICache() + { + if ( getBoolean( "settings.global-api-cache", false ) && !CachedStreamHandlerFactory.isSet ) + { + Bukkit.getLogger().info( "Global API cache enabled - All requests to Mojang's API will be " + + "handled by Spigot" ); + CachedStreamHandlerFactory.isSet = true; + URL.setURLStreamHandlerFactory(new CachedStreamHandlerFactory()); + } + } } -- 2.1.0