Skip to content

Commit

Permalink
[yem] more tolerance for offline mode
Browse files Browse the repository at this point in the history
  • Loading branch information
rmannibucau committed Feb 29, 2024
1 parent 52d4889 commit cb58c01
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 4 deletions.
1 change: 1 addition & 0 deletions env-manager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
<configuration>
<installDirectory>${project.parent.basedir}/.node</installDirectory>
<workingDirectory>${project.basedir}/src/main/frontend</workingDirectory>
<skip>${settings.offline}</skip>
</configuration>
</plugin>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
public record HttpConfiguration(
@Property(documentation = "Should SSL errors be ignored.", defaultValue = "false") boolean ignoreSSLErrors,
@Property(defaultValue = "false", documentation = "Force offline mode.") boolean offlineMode,
@Property(defaultValue = "10_000", documentation = "Check offline timeout. Per uri a test is done to verify the system is offline.") int offlineTimeout,
@Property(defaultValue = "5_000", documentation = "Check offline timeout. Per uri a test is done to verify the system is offline.") int offlineTimeout,
@Property(defaultValue = "4", documentation = "Number of NIO threads.") int threads,
@Property(defaultValue = "false", documentation = "Should HTTP calls be logged.") boolean log,
@Property(defaultValue = "60_000L", documentation = "Connection timeout in milliseconds.") long connectTimeout,
@Property(defaultValue = "5_000L", documentation = "Connection timeout in milliseconds, in case of offline mode it should stay low enough to not block any new command too long when set up automatically. You can set in `~/.yupiik/yem/rc` the line `http.offlineMode=true` or `http.connectTimeout = 3000` to limit this effect.") long connectTimeout,
@Property(defaultValue = "900_000L", documentation = "Request timeout in milliseconds.") long requestTimeout,
@Property(defaultValue = "86_400_000L", documentation = "Cache validity of requests (1 day by default) in milliseconds. A negative or zero value will disable cache.") long cacheValidity,
@Property(defaultValue = "System.getProperty(\"user.home\", \"\") + \"/.yupiik/yem/cache/http\"", documentation = "Where to cache slow updates (version fetching). `none` will disable cache.") String cache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@
import java.net.Authenticator;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.PasswordAuthentication;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
Expand All @@ -56,6 +59,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -87,19 +91,36 @@ public class YemHttpClient implements AutoCloseable {
private final Map<String, Boolean> state;
private final Map<AuthKey, Auth> authentications;
private final int offlineTimeout;
private final boolean offline;
private final int interfaces;

private volatile boolean offline;

protected YemHttpClient() { // for subclassing proxy
this.client = null;
this.cache = null;
this.interfaces = 0;
this.offlineTimeout = 0;
this.offline = false;
this.state = null;
this.authentications = null;
}

public YemHttpClient(final HttpConfiguration configuration, final Cache cache) {
this.offline = configuration.offlineMode();
List<NetworkInterface> interfaces;
try {
interfaces = NetworkInterface.networkInterfaces().filter(n -> {
try {
return !n.isLoopback() && !n.isVirtual();
} catch (final SocketException e) {
return false;
}
}).toList();
} catch (SocketException e) {
interfaces = List.of();
}

this.interfaces = interfaces.size();
this.offline = configuration.offlineMode() || detectIsOffline(interfaces);
this.offlineTimeout = configuration.offlineTimeout();
this.cache = cache;
this.state = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -235,6 +256,16 @@ protected PasswordAuthentication getPasswordAuthentication() {
});
}

private boolean detectIsOffline(final List<NetworkInterface> networkInterfaces) {
return networkInterfaces.stream().noneMatch(n -> {
try {
return n.isUp();
} catch (final SocketException e) {
return false;
}
});
}

@Destroy
@Override
public void close() {
Expand Down Expand Up @@ -326,6 +357,18 @@ public CompletionStage<HttpResponse<String>> sendAsync(final HttpRequest request
cache.save(entry.key(), result);
}
return result;
})
.exceptionally(e -> {
if (e instanceof CompletionException ce && ce.getCause() instanceof HttpConnectTimeoutException) {
offline = true;
if (entry != null && entry.hit() != null && entry.hit().headers() != null && entry.hit().payload() != null) {
// refresh the cached entry for next runs
logger.info(() -> "Keeping cached entry for '" + request.uri() + "' metadata since system is offline");
cache.save(entry.key(), entry.hit().headers(), entry.hit().payload());
}
throw ce;
}
throw new IllegalStateException(e);
});
}

Expand Down Expand Up @@ -361,6 +404,10 @@ private void checkOffline(final URI uri) {
throw OfflineException.INSTANCE;
}
if (state.computeIfAbsent(uri.getHost() + ":" + uri.getPort(), k -> {
if (interfaces == 1 && !state.isEmpty()) {
return state.values().iterator().next();
}

try (final var socket = new Socket()) {
final var address = new InetSocketAddress(uri.getHost(), uri.getPort() >= 0 ? uri.getPort() : ("https".equals(uri.getScheme()) ? 443 : 80));
socket.connect(address, offlineTimeout);
Expand Down

0 comments on commit cb58c01

Please sign in to comment.