Skip to content

Commit

Permalink
optimize resource usage
Browse files Browse the repository at this point in the history
- cancel ongoing requests just after reception of the response first arrived
- handles all the exception thrown during DNS resolution by returning DnsRecord object
  • Loading branch information
fritzprix committed Jul 21, 2019
1 parent fed949f commit 34301a5
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 75 deletions.
3 changes: 1 addition & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.doodream</groupId>
<artifactId>robust-dns</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<packaging>jar</packaging>


Expand Down Expand Up @@ -64,7 +64,6 @@
<distribution>repo</distribution>
</license>
</licenses>
<!-- https://github.com/fritzprix/robust-dns.git-->
<build>
<extensions>
<extension>
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/doodream/robustdns/DnsRecord.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.doodream.robustdns;

import java.net.InetAddress;

public class DnsRecord {
private static final DnsRecord FAIL = new DnsRecord();
boolean isSuccessful;
InetAddress address;
long expireAt;

public static DnsRecord fail() {
return FAIL;
}

public static DnsRecord success(InetAddress resolved, long expireAt) {
return new DnsRecord(resolved, expireAt);
}

private DnsRecord() {
isSuccessful = false;
}

private DnsRecord(InetAddress resolved, long expireAt) {
isSuccessful = true;
address = resolved;
this.expireAt = expireAt;
}

public boolean isSuccessful() {
return isSuccessful;
}

public InetAddress getAddress() {
return address;
}
}
119 changes: 47 additions & 72 deletions src/main/java/com/doodream/robustdns/RobustDnsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.*;

import java.net.Inet4Address;
Expand All @@ -13,24 +16,15 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class RobustDnsResolver {

private static final Logger Log = LoggerFactory.getLogger(RobustDnsResolver.class);
private boolean updateOnExpire;
private long cacheTimeoutInMills;

public static class DnsRecord {
InetAddress address;
long expireAt;

public DnsRecord(InetAddress resolved, long expireAt) {
address = resolved;
this.expireAt = expireAt;
}
}

private final ConcurrentHashMap<String, DnsRecord> cache = new ConcurrentHashMap<>();
private boolean cacheEnabled;
Expand Down Expand Up @@ -114,88 +108,69 @@ public static Builder builder() {
private RobustDnsResolver() {
}

public Single<InetAddress> resolve(String name) {
public Single<DnsRecord> resolve(String name) {
if(cache.containsKey(name)) {
final DnsRecord record = cache.get(name);
if(record.expireAt < System.currentTimeMillis()) {
System.out.println("cache miss");
cache.remove(name);
DnsRecord old = cache.remove(name);
if(updateOnExpire) {
resolve(name).subscribe();
return Single.just(record.address);
return Single.just(old);
} else {
return resolve(name);
}
}
return Single.just(record.address);
return Single.just(record);
}
if(name == null || name.isEmpty()) {
return Single.error(new UnknownHostException("invalid hostname : empty"));
}
return Single.<InetAddress>create(emitter -> {
try {
final Record question = Record.newRecord(Name.concatenate(Name.fromString(name), Name.root), Type.A, DClass.IN, TTL.MAX_VALUE);
final Message query = Message.newQuery(question);
return Observable.fromArray(rsv)
.map(resolver -> resolver.send(query))
.map(message -> {
final Record[] records = message.getSectionArray(Section.ANSWER);
if (records == null || records.length == 0) {
return DnsRecord.fail();
}
try {
final InetAddress resolved = Inet4Address.getByName(records[0].rdataToString());
return DnsRecord.success(resolved, System.currentTimeMillis() + cacheTimeoutInMills);
} catch (UnknownHostException e) {
return DnsRecord.fail();
}
}).onErrorReturn(new Function<Throwable, DnsRecord>() {
@Override
public DnsRecord apply(Throwable throwable) throws Exception {
if (throwable instanceof PortUnreachableException) {
try {
final InetAddress resolved = Address.getByName(name);
return DnsRecord.success(resolved, System.currentTimeMillis() + cacheTimeoutInMills);
} catch (Exception ignore) {
// failure case will be handled by failover (java default resolver)
}
}

for (Resolver resolver : rsv) {
resolver.setTimeout(10);
resolver.sendAsync(query, new ResolverListener() {
@Override
public void receiveMessage(Object o, Message message) {
Record[] records = message.getSectionArray(Section.ANSWER);

if(records != null) {
if(records.length == 0) {
return;
}
try {
final InetAddress resolved = Inet4Address.getByName(records[0].rdataToString());
DnsRecord record = new DnsRecord(
resolved,
System.currentTimeMillis() + cacheTimeoutInMills
);

if(cacheEnabled) {
cache.put(name, record);
}
emitter.onSuccess(resolved);
} catch (UnknownHostException e) {
emitter.onError(e);
return DnsRecord.success(InetAddress.getByName(name), System.currentTimeMillis() + cacheTimeoutInMills);
} catch (UnknownHostException ue) {
return DnsRecord.fail();
}
} else {
emitter.onError(new UnknownHostException(String.format(Locale.ENGLISH, "%s not found", name)));
}
}

@Override
public void handleException(Object o, Exception e) {
if(e instanceof PortUnreachableException) {
disposable.add(Observable.just(name)
.subscribeOn(Schedulers.io())
.subscribe(unresolved -> {
try {
final InetAddress resolved = Address.getByName(name);
DnsRecord record = new DnsRecord(
resolved,
System.currentTimeMillis() + cacheTimeoutInMills
);
if (cacheEnabled) {
cache.put(name, record);
}
emitter.onSuccess(resolved);
} catch (Exception uke) {
// emitter.onError(uke);
}
}));
return;
}
try {
emitter.onSuccess(InetAddress.getByName(name));
} catch (UnknownHostException ue) {
emitter.onError(ue);
})
.first(DnsRecord.fail())
.doOnSuccess(record -> {
if (record.isSuccessful() && cacheEnabled) {
cache.put(name, record);
}
}
});
}
}).timeout(10L, TimeUnit.SECONDS).subscribeOn(Schedulers.io());
})
.subscribeOn(Schedulers.io());

} catch (Exception e) {
return Single.just(DnsRecord.fail());
}
}
}
2 changes: 1 addition & 1 deletion src/test/java/com/doodream/robustdns/DnsAgingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ public static Collection<Object> data() {

@Test(timeout = 5000L)
public void test_lookup() throws Exception {
System.out.println(resolver.resolve("www.google.com").blockingGet());
System.out.println(resolver.resolve("www.google.com").blockingGet().getAddress());
}
}

0 comments on commit 34301a5

Please sign in to comment.