package com.danikula.videocache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; import java.net.Proxy; import java.net.ProxySelector; import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import static com.danikula.videocache.Preconditions.checkArgument; import static com.danikula.videocache.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * Pings {@link HttpProxyCacheServer} to make sure it works. * * @author Alexey Danilov (danikula@gmail.com). */ class Pinger { private static final Logger LOG = LoggerFactory.getLogger("Pinger"); private static final String PING_REQUEST = "ping"; private static final String PING_RESPONSE = "ping ok"; private final ExecutorService pingExecutor = Executors.newSingleThreadExecutor(); private final String host; private final int port; Pinger(String host, int port) { this.host = checkNotNull(host); this.port = port; } boolean ping(int maxAttempts, int startTimeout) { checkArgument(maxAttempts >= 1); checkArgument(startTimeout > 0); int timeout = startTimeout; int attempts = 0; while (attempts < maxAttempts) { try { Future<Boolean> pingFuture = pingExecutor.submit(new PingCallable()); boolean pinged = pingFuture.get(timeout, MILLISECONDS); if (pinged) { return true; } } catch (TimeoutException e) { LOG.warn("Error pinging server (attempt: " + attempts + ", timeout: " + timeout + "). "); } catch (InterruptedException | ExecutionException e) { LOG.error("Error pinging server due to unexpected error", e); } attempts++; timeout *= 2; } String error = String.format(Locale.US, "Error pinging server (attempts: %d, max timeout: %d). " + "If you see this message, please, report at https://github.com/danikula/AndroidVideoCache/issues/134. " + "Default proxies are: %s" , attempts, timeout / 2, getDefaultProxies()); LOG.error(error, new ProxyCacheException(error)); return false; } private List<Proxy> getDefaultProxies() { try { ProxySelector defaultProxySelector = ProxySelector.getDefault(); return defaultProxySelector.select(new URI(getPingUrl())); } catch (URISyntaxException e) { throw new IllegalStateException(e); } } boolean isPingRequest(String request) { return PING_REQUEST.equals(request); } void responseToPing(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); out.write("HTTP/1.1 200 OK\n\n".getBytes()); out.write(PING_RESPONSE.getBytes()); } private boolean pingServer() throws ProxyCacheException { String pingUrl = getPingUrl(); HttpUrlSource source = new HttpUrlSource(pingUrl); try { byte[] expectedResponse = PING_RESPONSE.getBytes(); source.open(0); byte[] response = new byte[expectedResponse.length]; source.read(response); boolean pingOk = Arrays.equals(expectedResponse, response); LOG.info("Ping response: `" + new String(response) + "`, pinged? " + pingOk); return pingOk; } catch (ProxyCacheException e) { LOG.error("Error reading ping response", e); return false; } finally { source.close(); } } private String getPingUrl() { return String.format(Locale.US, "http://%s:%d/%s", host, port, PING_REQUEST); } private class PingCallable implements Callable<Boolean> { @Override public Boolean call() throws Exception { return pingServer(); } } }