diff --git a/pom.xml b/pom.xml index a9d5b629..9aeba1c1 100644 --- a/pom.xml +++ b/pom.xml @@ -160,6 +160,16 @@ byteunits 0.9.1 + + io.netty + netty-all + 4.1.8.Final + + + io.netty + netty-resolver-dns + 4.1.8.Final + us.physion osx-keychain @@ -274,7 +284,6 @@ src/assembly/bundle.xml - oksocial-${project.version} com.baulsupp.oksocial.Main diff --git a/src/main/java/com/baulsupp/oksocial/Main.java b/src/main/java/com/baulsupp/oksocial/Main.java index 40ce9db9..9702fa50 100644 --- a/src/main/java/com/baulsupp/oksocial/Main.java +++ b/src/main/java/com/baulsupp/oksocial/Main.java @@ -21,8 +21,8 @@ import com.baulsupp.oksocial.location.BestLocation; import com.baulsupp.oksocial.location.LocationSource; import com.baulsupp.oksocial.network.DnsOverride; -import com.baulsupp.oksocial.network.DnsSelector; import com.baulsupp.oksocial.network.InterfaceSocketFactory; +import com.baulsupp.oksocial.network.NettyDns; import com.baulsupp.oksocial.okhttp.OkHttpResponseFuture; import com.baulsupp.oksocial.output.ConsoleHandler; import com.baulsupp.oksocial.output.DownloadHandler; @@ -51,6 +51,7 @@ import io.airlift.airline.HelpOption; import io.airlift.airline.Option; import io.airlift.airline.SingleCommand; +import io.netty.channel.nio.NioEventLoopGroup; import java.io.File; import java.io.IOException; import java.net.Proxy; @@ -62,6 +63,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.SocketFactory; @@ -245,6 +247,8 @@ public static void main(String... args) { public LocationSource locationSource = new BestLocation(); + private NioEventLoopGroup eventLoopGroup; + private String versionString() { return Util.versionString("/oksocial-version.properties"); } @@ -457,6 +461,10 @@ private void closeClients() { client.dispatcher().executorService().shutdown(); client.connectionPool().evictAll(); } + + if (eventLoopGroup != null) { + eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS); + } } private OutputHandler buildHandler() { @@ -578,7 +586,7 @@ public OkHttpClient.Builder createClientBuilder() throws Exception { builder.readTimeout(readTimeout, SECONDS); } - Dns dns = DnsSelector.byName(ipmode); + Dns dns = NettyDns.byName(ipmode, getEventLoopGroup()); if (resolve != null) { dns = DnsOverride.build(dns, resolve); } @@ -619,6 +627,14 @@ public OkHttpClient.Builder createClientBuilder() throws Exception { return builder; } + private NioEventLoopGroup getEventLoopGroup() { + if (eventLoopGroup == null) { + eventLoopGroup = new NioEventLoopGroup(1); + } + + return eventLoopGroup; + } + private SocketFactory getSocketFactory() throws SocketException { Optional socketFactory = InterfaceSocketFactory.byName(networkInterface); diff --git a/src/main/java/com/baulsupp/oksocial/network/DnsSelector.java b/src/main/java/com/baulsupp/oksocial/network/DnsSelector.java index f504122f..b44612dd 100644 --- a/src/main/java/com/baulsupp/oksocial/network/DnsSelector.java +++ b/src/main/java/com/baulsupp/oksocial/network/DnsSelector.java @@ -1,14 +1,11 @@ package com.baulsupp.oksocial.network; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.logging.Logger; import okhttp3.Dns; @@ -26,8 +23,6 @@ public enum Mode { IPV4_ONLY } - private Map> overrides = Maps.newHashMap(); - private Mode mode; public DnsSelector(Mode mode) { @@ -58,13 +53,7 @@ public static Dns byName(String ipMode) { } @Override public List lookup(String hostname) throws UnknownHostException { - List addresses = overrides.get(hostname.toLowerCase()); - - if (addresses != null) { - return addresses; - } - - addresses = Dns.SYSTEM.lookup(hostname); + List addresses = Dns.SYSTEM.lookup(hostname); switch (mode) { case IPV6_FIRST: @@ -87,8 +76,4 @@ public static Dns byName(String ipMode) { return addresses; } - - public void addOverride(String hostname, InetAddress address) { - overrides.put(hostname.toLowerCase(), Lists.newArrayList(address)); - } } diff --git a/src/main/java/com/baulsupp/oksocial/network/NettyDns.java b/src/main/java/com/baulsupp/oksocial/network/NettyDns.java new file mode 100644 index 00000000..23523ba0 --- /dev/null +++ b/src/main/java/com/baulsupp/oksocial/network/NettyDns.java @@ -0,0 +1,89 @@ +package com.baulsupp.oksocial.network; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.resolver.dns.DnsNameResolver; +import io.netty.resolver.dns.DnsNameResolverBuilder; +import io.netty.util.concurrent.Future; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import okhttp3.Dns; + +import static io.netty.channel.socket.InternetProtocolFamily.IPv4; +import static io.netty.channel.socket.InternetProtocolFamily.IPv6; +import static java.util.stream.Collectors.joining; + +public class NettyDns implements Dns { + private static Logger logger = Logger.getLogger(NettyDns.class.getName()); + + private final DnsNameResolver r; + private final EventLoopGroup group; + + public NettyDns(EventLoopGroup group, Iterable addressTypes) { + this.group = group; + DnsNameResolverBuilder builder = new DnsNameResolverBuilder(this.group.next()) + .channelType(NioDatagramChannel.class) + .optResourceEnabled(false) + .maxQueriesPerResolve(3) + .recursionDesired(true); + + if (logger.isLoggable(Level.FINEST)) { + builder.traceEnabled(true); + } + + if (addressTypes != null) { + builder.resolvedAddressTypes(addressTypes); + } + + r = builder.build(); + } + + @Override public List lookup(String hostname) throws UnknownHostException { + Future> f = r.resolveAll(hostname); + + try { + List addresses = f.get(); + + logger.fine("Dns (" + hostname + "): " + addresses.stream() + .map(Object::toString) + .collect(joining(", "))); + + return addresses; + } catch (InterruptedException e) { + throw new UnknownHostException(e.toString()); + } catch (ExecutionException e) { + throw ((UnknownHostException) new UnknownHostException(e.getCause().getMessage()).initCause( + e.getCause())); + } + } + + public static Dns byName(String ipMode, EventLoopGroup eventLoopGroup) { + List types; + + switch (ipMode) { + case "ipv6": + types = Arrays.asList(IPv6, IPv4); + break; + case "ipv4": + types = Arrays.asList(IPv4, IPv6); + break; + case "ipv6only": + types = Arrays.asList(IPv6); + break; + case "ipv4only": + types = Arrays.asList(IPv4); + break; + default: + types = null; + break; + } + + return new NettyDns(eventLoopGroup, types); + } +} diff --git a/src/main/java/com/baulsupp/oksocial/util/LoggingUtil.java b/src/main/java/com/baulsupp/oksocial/util/LoggingUtil.java index d47ece7d..fb6e0618 100644 --- a/src/main/java/com/baulsupp/oksocial/util/LoggingUtil.java +++ b/src/main/java/com/baulsupp/oksocial/util/LoggingUtil.java @@ -1,6 +1,8 @@ package com.baulsupp.oksocial.util; import com.google.common.collect.Lists; +import io.netty.util.internal.logging.InternalLoggerFactory; +import io.netty.util.internal.logging.JdkLoggerFactory; import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Level; @@ -14,6 +16,8 @@ public class LoggingUtil { private static List activeLoggers = Lists.newArrayList(); public static void configureLogging(boolean debug, boolean showHttp2Frames) { + InternalLoggerFactory.setDefaultFactory(JdkLoggerFactory.INSTANCE); + if (debug || showHttp2Frames) { LogManager.getLogManager().reset(); ConsoleHandler handler = new ConsoleHandler(); @@ -25,8 +29,9 @@ public static void configureLogging(boolean debug, boolean showHttp2Frames) { activeLogger.addHandler(handler); activeLogger.setLevel(Level.ALL); - Logger x = getLogger("org.zeroturnaround.exec.stream"); - x.setLevel(Level.INFO); + getLogger("org.zeroturnaround.exec").setLevel(Level.INFO); + getLogger("io.netty").setLevel(Level.INFO); + getLogger("io.netty.resolver.dns").setLevel(Level.FINE); } else if (showHttp2Frames) { Logger activeLogger = getLogger(Http2.class.getName()); activeLogger.setLevel(Level.FINE); @@ -37,7 +42,10 @@ public static void configureLogging(boolean debug, boolean showHttp2Frames) { } }); activeLogger.addHandler(handler); + getLogger("io.netty.resolver.dns.DnsServerAddresses").setLevel(Level.SEVERE); } + } else { + getLogger("io.netty.resolver.dns.DnsServerAddresses").setLevel(Level.SEVERE); } } diff --git a/src/test/java/com/baulsupp/oksocial/TestMain.java b/src/test/java/com/baulsupp/oksocial/TestMain.java new file mode 100644 index 00000000..48af2e4e --- /dev/null +++ b/src/test/java/com/baulsupp/oksocial/TestMain.java @@ -0,0 +1,7 @@ +package com.baulsupp.oksocial; + +public class TestMain { + public static void main(String[] args) throws Exception { + Main.main("--debug", "https://api.twitter.com/robots.txt"); + } +} diff --git a/src/test/java/com/baulsupp/oksocial/services/google/DiscoveryApiDocPresenterTest.java b/src/test/java/com/baulsupp/oksocial/services/google/DiscoveryApiDocPresenterTest.java index 4ff46ba6..759d3233 100644 --- a/src/test/java/com/baulsupp/oksocial/services/google/DiscoveryApiDocPresenterTest.java +++ b/src/test/java/com/baulsupp/oksocial/services/google/DiscoveryApiDocPresenterTest.java @@ -36,15 +36,13 @@ public void loadPresenter() throws IOException { "url: https://people.googleapis.com/v1/{+resourceName}", "scopes: https://www.googleapis.com/auth/contacts, https://www.googleapis.com/auth/contacts.readonly, https://www.googleapis.com/auth/plus.login, https://www.googleapis.com/auth/user.addresses.read, https://www.googleapis.com/auth/user.birthday.read, https://www.googleapis.com/auth/user.emails.read, https://www.googleapis.com/auth/user.phonenumbers.read, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile", "", - "Provides information about a person resource for a resource name. Use `people/me` to indicate the authenticated user.", - "", - "parameter: resourceName (string)", - "The resource name of the person to provide information about. - To get information about the authenticated user, specify `people/me`. - To get information about any user, specify the resource name that identifies the user, such as the resource names returned by [`people.connections.list`](/people/api/rest/v1/people.connections/list).", - "parameter: requestMask.includeField (string)", - "Comma-separated list of fields to be included in the response. Omitting this field will include all fields. Each path should start with `person.`: for example, `person.names` or `person.photos`." + "Provides information about a person resource for a resource name. Use\n" + + "`people/me` to indicate the authenticated user." ); - assertEquals(es, outputHandler.stdout); + for (String l: es) { + assertTrue(outputHandler.stdout.contains(l), l); + } } @Test public void testExplainsExpandedUrl() throws IOException {